From 85129e528ec5a630adf818929258b53c5116ee4d Mon Sep 17 00:00:00 2001 From: Louis King Date: Fri, 13 Feb 2026 21:19:02 +0000 Subject: [PATCH] Refactor i18n translations for better maintainability - Remove page_title section, compose titles dynamically as "{{entity}} - {{network_name}}" - Add entities section for centralized entity names (nodes, members, tags, etc.) - Replace specific action translations with composed patterns (add_entity, edit_entity, etc.) - Create links section for common platform names (github, discord, youtube) - Remove redundant page-specific title fields, use entity names instead - Update all page components to use new translation structure - Keep user-defined strings (network_name) separate from translatable content This follows i18n best practices by using composition over duplication, centralizing reusable terms, and making it easier to add new languages. Co-Authored-By: Claude Sonnet 4.5 --- src/meshcore_hub/web/static/js/spa/app.js | 33 ++++-- .../web/static/js/spa/pages/admin/index.js | 2 +- .../web/static/js/spa/pages/admin/members.js | 20 ++-- .../static/js/spa/pages/admin/node-tags.js | 34 +++--- .../web/static/js/spa/pages/advertisements.js | 2 +- .../web/static/js/spa/pages/dashboard.js | 2 +- .../web/static/js/spa/pages/home.js | 6 +- .../web/static/js/spa/pages/map.js | 2 +- .../web/static/js/spa/pages/members.js | 8 +- .../web/static/js/spa/pages/messages.js | 2 +- .../web/static/js/spa/pages/nodes.js | 2 +- src/meshcore_hub/web/static/locales/en.json | 111 ++++++++---------- src/meshcore_hub/web/templates/spa.html | 6 +- 13 files changed, 112 insertions(+), 118 deletions(-) diff --git a/src/meshcore_hub/web/static/js/spa/app.js b/src/meshcore_hub/web/static/js/spa/app.js index aba65f2..0ccc89b 100644 --- a/src/meshcore_hub/web/static/js/spa/app.js +++ b/src/meshcore_hub/web/static/js/spa/app.js @@ -126,6 +126,17 @@ function updateNavActiveState(pathname) { } } +/** + * Compose a page title from entity name and network name. + * @param {string} entityKey - Translation key for entity (e.g., 'entities.dashboard') + * @returns {string} + */ +function composePageTitle(entityKey) { + const networkName = config.network_name || 'MeshCore Network'; + const entity = t(entityKey); + return `${entity} - ${networkName}`; +} + /** * Update the page title based on the current route. * @param {string} pathname @@ -134,24 +145,24 @@ function updatePageTitle(pathname) { const networkName = config.network_name || 'MeshCore Network'; const titles = { '/': networkName, - '/a': t('page_title.admin', { network_name: networkName }), - '/a/': t('page_title.admin', { network_name: networkName }), - '/a/node-tags': t('page_title.admin_node_tags', { network_name: networkName }), - '/a/members': t('page_title.admin_members', { network_name: networkName }), + '/a': composePageTitle('entities.admin'), + '/a/': composePageTitle('entities.admin'), + '/a/node-tags': `${t('entities.tags')} - ${t('entities.admin')} - ${networkName}`, + '/a/members': `${t('entities.members')} - ${t('entities.admin')} - ${networkName}`, }; // Add feature-dependent titles - if (features.dashboard !== false) titles['/dashboard'] = t('page_title.dashboard', { network_name: networkName }); - if (features.nodes !== false) titles['/nodes'] = t('page_title.nodes', { network_name: networkName }); - if (features.messages !== false) titles['/messages'] = t('page_title.messages', { network_name: networkName }); - if (features.advertisements !== false) titles['/advertisements'] = t('page_title.advertisements', { network_name: networkName }); - if (features.map !== false) titles['/map'] = t('page_title.map', { network_name: networkName }); - if (features.members !== false) titles['/members'] = t('page_title.members', { network_name: networkName }); + if (features.dashboard !== false) titles['/dashboard'] = composePageTitle('entities.dashboard'); + if (features.nodes !== false) titles['/nodes'] = composePageTitle('entities.nodes'); + if (features.messages !== false) titles['/messages'] = composePageTitle('entities.messages'); + if (features.advertisements !== false) titles['/advertisements'] = composePageTitle('entities.advertisements'); + if (features.map !== false) titles['/map'] = composePageTitle('entities.map'); + if (features.members !== false) titles['/members'] = composePageTitle('entities.members'); if (titles[pathname]) { document.title = titles[pathname]; } else if (pathname.startsWith('/nodes/')) { - document.title = t('page_title.node_detail', { network_name: networkName }); + document.title = composePageTitle('entities.node_detail'); } else if (pathname.startsWith('/pages/')) { // Custom pages set their own title in the page module document.title = networkName; diff --git a/src/meshcore_hub/web/static/js/spa/pages/admin/index.js b/src/meshcore_hub/web/static/js/spa/pages/admin/index.js index 408c08e..4040bf9 100644 --- a/src/meshcore_hub/web/static/js/spa/pages/admin/index.js +++ b/src/meshcore_hub/web/static/js/spa/pages/admin/index.js @@ -31,7 +31,7 @@ export async function render(container, params, router) { litRender(html`
-

${t('admin.title')}

+

${t('entities.admin')}