diff --git a/javascripts/discourse/initializers/init-nav-bar-additions.js b/javascripts/discourse/initializers/init-nav-bar-additions.js index e7169e9..46c4db6 100644 --- a/javascripts/discourse/initializers/init-nav-bar-additions.js +++ b/javascripts/discourse/initializers/init-nav-bar-additions.js @@ -1,23 +1,86 @@ import { withPluginApi } from "discourse/lib/plugin-api"; +import I18n from "I18n"; + +/** + * Resolve a field value from a nav link, checking for a locale-specific + * translation first. Falls back to the base field value. + */ +function getLocalizedField(link, fieldName) { + const translations = link.translations; + if (!translations || translations.length === 0) { + return link[fieldName]; + } + + const locale = I18n.currentLocale(); + + // Try exact match first (e.g. "pt_BR"), then prefix match (e.g. "pt") + const match = + translations.find((t) => t.locale === locale) || + translations.find((t) => locale.startsWith(t.locale)); + + return (match && match[fieldName]) || link[fieldName]; +} + +// Built-in Discourse filter routes that have associated topic counts +const COUNTED_FILTERS = { + "/new": "new", + "/unread": "unread", +}; + +function getCountForFilter(topicTrackingState, filterType) { + if (!topicTrackingState) { + return 0; + } + + if (filterType === "new") { + return topicTrackingState.countNew?.() ?? topicTrackingState.newCount ?? 0; + } + + if (filterType === "unread") { + return ( + topicTrackingState.countUnread?.() ?? topicTrackingState.unreadCount ?? 0 + ); + } + + return 0; +} export default { name: "nav-links-component", initialize() { withPluginApi((api) => { - for (const { - display_name: displayName, - title, - url, - } of settings.nav_links) { - api.addNavigationBarItem({ + const topicTrackingState = api.container.lookup( + "service:topic-tracking-state" + ); + + for (const link of settings.nav_links) { + const { display_name: displayName, url } = link; + const localizedDisplayName = getLocalizedField(link, "display_name"); + const localizedTitle = getLocalizedField(link, "title"); + const filterType = COUNTED_FILTERS[url]; + const itemConfig = { name: `custom_${displayName.replace(/\s+/g, "-").toLowerCase()}`, - displayName, - title, + title: localizedTitle, href: url, forceActive: (category, args, router) => router.currentURL?.split("?")[0] === url, - }); + }; + + if (settings.Show_counts && filterType && topicTrackingState) { + // Use a function for displayName so Discourse re-evaluates it + // reactively, picking up count changes from TopicTrackingState + itemConfig.displayName = () => { + const count = getCountForFilter(topicTrackingState, filterType); + return count > 0 + ? `${localizedDisplayName} (${count})` + : localizedDisplayName; + }; + } else { + itemConfig.displayName = localizedDisplayName; + } + + api.addNavigationBarItem(itemConfig); } }); }, diff --git a/locales/en.yml b/locales/en.yml index 8d65002..d6bf94d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -14,3 +14,16 @@ en: url: label: URL description: The link (e.g., /latest or https://discourse.org) + translations: + label: Translations + description: Per-language overrides for display name and title + properties: + locale: + label: Language + description: Select the language for this translation + display_name: + label: Display name + description: Translated display name for this language + title: + label: Title + description: Translated title (hover text) for this language diff --git a/settings.yml b/settings.yml index b0ee740..509506f 100644 --- a/settings.yml +++ b/settings.yml @@ -32,6 +32,73 @@ nav_links: min_length: 1 max_length: 2048 url: true + translations: + type: objects + schema: + name: translation + identifier: locale + properties: + locale: + type: enum + required: true + choices: + - ar + - be + - bg + - bs_BA + - ca + - cs + - da + - de + - el + - en + - en_GB + - es + - et + - fa_IR + - fi + - fr + - gl + - he + - hr + - hu + - hy + - id + - it + - ja + - ko + - lt + - lv + - nb_NO + - nl + - pl_PL + - pt + - pt_BR + - ro + - ru + - sk + - sl + - sq + - sr + - sv + - sw + - te + - th + - tr_TR + - ug + - uk + - ur + - vi + - zh_CN + - zh_TW + display_name: + type: string + validations: + max_length: 100 + title: + type: string + validations: + max_length: 1000 Hide_dropdowns: default: false description: @@ -40,3 +107,7 @@ Hide_default_links: default: false description: en: "Hide the default links on both mobile and desktop." +Show_counts: + default: false + description: + en: "Show topic counts for links pointing to /new or /unread (e.g. New (24))." diff --git a/test/javascripts/acceptance/nav-bar-additions-test.js b/test/javascripts/acceptance/nav-bar-additions-test.js new file mode 100644 index 0000000..93108ab --- /dev/null +++ b/test/javascripts/acceptance/nav-bar-additions-test.js @@ -0,0 +1,52 @@ +import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers"; +import { visit } from "@ember/test-helpers"; +import { test } from "qunit"; + +acceptance("Custom Top Navigation Links | Topic Counts", function (needs) { + needs.user(); + + let originalNavLinks; + let originalShowCounts; + + needs.hooks.beforeEach(function () { + originalNavLinks = settings.nav_links; + originalShowCounts = settings.Show_counts; + + settings.nav_links = [ + { display_name: "New Topics", title: "new topics", url: "/new" }, + { display_name: "Unread Topics", title: "unread topics", url: "/unread" }, + ]; + settings.Show_counts = true; + }); + + needs.hooks.afterEach(function () { + settings.nav_links = originalNavLinks; + settings.Show_counts = originalShowCounts; + }); + + test("shows new and unread counts in navigation items", async function (assert) { + const topicTrackingState = this.owner.lookup( + "service:topic-tracking-state" + ); + topicTrackingState.countNew = () => 42; + topicTrackingState.countUnread = () => 17; + + await visit("/latest"); + + assert.ok( + exists(".nav-item_custom_new-topics"), + "custom navigation item for new topics is rendered" + ); + assert + .dom(".nav-item_custom_new-topics a") + .hasText("New Topics (42)", "it shows the custom new topics count"); + + assert.ok( + exists(".nav-item_custom_unread-topics"), + "custom navigation item for unread topics is rendered" + ); + assert + .dom(".nav-item_custom_unread-topics a") + .hasText("Unread Topics (17)", "it shows the custom unread topics count"); + }); +});