mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 01:13:11 +02:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -31,7 +31,7 @@ export async function render(container, params, router) {
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">${t('admin.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.admin')}</h1>
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/">${t('nav.home')}</a></li>
|
||||
|
||||
@@ -85,12 +85,12 @@ export async function render(container, params, router) {
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">${t('admin_members.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.members')}</h1>
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/">${t('nav.home')}</a></li>
|
||||
<li><a href="/a/">${t('nav.admin')}</a></li>
|
||||
<li>${t('admin_members.title')}</li>
|
||||
<li>${t('entities.members')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@ ${flashHtml}
|
||||
<div class="card-body">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="card-title">${t('admin_members.network_members', { count: members.length })}</h2>
|
||||
<button id="btn-add-member" class="btn btn-primary btn-sm">${t('admin_members.add_member')}</button>
|
||||
<button id="btn-add-member" class="btn btn-primary btn-sm">${t('common.add_entity', { entity: t('entities.member') })}</button>
|
||||
</div>
|
||||
${tableHtml}
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@ ${flashHtml}
|
||||
|
||||
<dialog id="addModal" class="modal">
|
||||
<div class="modal-box w-11/12 max-w-2xl">
|
||||
<h3 class="font-bold text-lg">${t('admin_members.add_new_member')}</h3>
|
||||
<h3 class="font-bold text-lg">${t('common.add_new_entity', { entity: t('entities.member') })}</h3>
|
||||
<form id="add-member-form" class="py-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
@@ -151,7 +151,7 @@ ${flashHtml}
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button type="button" class="btn" id="addCancel">${t('common.cancel')}</button>
|
||||
<button type="submit" class="btn btn-primary">${t('admin_members.add_member')}</button>
|
||||
<button type="submit" class="btn btn-primary">${t('common.add_entity', { entity: t('entities.member') })}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -160,7 +160,7 @@ ${flashHtml}
|
||||
|
||||
<dialog id="editModal" class="modal">
|
||||
<div class="modal-box w-11/12 max-w-2xl">
|
||||
<h3 class="font-bold text-lg">${t('admin_members.edit_member')}</h3>
|
||||
<h3 class="font-bold text-lg">${t('common.edit_entity', { entity: t('entities.member') })}</h3>
|
||||
<form id="edit-member-form" class="py-4">
|
||||
<input type="hidden" name="id" id="edit_id">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@@ -206,7 +206,7 @@ ${flashHtml}
|
||||
|
||||
<dialog id="deleteModal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">${t('admin_members.delete_member')}</h3>
|
||||
<h3 class="font-bold text-lg">${t('common.delete_entity', { entity: t('entities.member') })}</h3>
|
||||
<div class="py-4">
|
||||
<p class="py-4">Are you sure you want to delete member <strong id="delete_member_name"></strong>?</p>
|
||||
<div class="alert alert-error mb-4">
|
||||
@@ -249,7 +249,7 @@ ${flashHtml}
|
||||
try {
|
||||
await apiPost('/api/v1/members', body);
|
||||
container.querySelector('#addModal').close();
|
||||
router.navigate('/a/members?message=' + encodeURIComponent(t('admin_members.member_added')));
|
||||
router.navigate('/a/members?message=' + encodeURIComponent(t('admin_members.entity_added')));
|
||||
} catch (err) {
|
||||
container.querySelector('#addModal').close();
|
||||
router.navigate('/a/members?error=' + encodeURIComponent(err.message));
|
||||
@@ -289,7 +289,7 @@ ${flashHtml}
|
||||
try {
|
||||
await apiPut('/api/v1/members/' + encodeURIComponent(id), body);
|
||||
container.querySelector('#editModal').close();
|
||||
router.navigate('/a/members?message=' + encodeURIComponent(t('admin_members.member_updated')));
|
||||
router.navigate('/a/members?message=' + encodeURIComponent(t('admin_members.entity_updated')));
|
||||
} catch (err) {
|
||||
container.querySelector('#editModal').close();
|
||||
router.navigate('/a/members?error=' + encodeURIComponent(err.message));
|
||||
@@ -314,7 +314,7 @@ ${flashHtml}
|
||||
try {
|
||||
await apiDelete('/api/v1/members/' + encodeURIComponent(activeDeleteId));
|
||||
container.querySelector('#deleteModal').close();
|
||||
router.navigate('/a/members?message=' + encodeURIComponent(t('admin_members.member_deleted')));
|
||||
router.navigate('/a/members?message=' + encodeURIComponent(t('admin_members.entity_deleted')));
|
||||
} catch (err) {
|
||||
container.querySelector('#deleteModal').close();
|
||||
router.navigate('/a/members?error=' + encodeURIComponent(err.message));
|
||||
|
||||
@@ -131,7 +131,7 @@ export async function render(container, params, router) {
|
||||
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">${t('admin_node_tags.add_new_tag')}</h2>
|
||||
<h2 class="card-title">${t('common.add_new_entity', { entity: t('entities.tag') })}</h2>
|
||||
<form id="add-tag-form" class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">${t('common.key')}</span></label>
|
||||
@@ -151,7 +151,7 @@ export async function render(container, params, router) {
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text"> </span></label>
|
||||
<button type="submit" class="btn btn-primary">${t('admin_node_tags.add_tag')}</button>
|
||||
<button type="submit" class="btn btn-primary">${t('common.add_entity', { entity: t('entities.tag') })}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -159,7 +159,7 @@ export async function render(container, params, router) {
|
||||
|
||||
<dialog id="editModal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">${t('admin_node_tags.edit_tag')}</h3>
|
||||
<h3 class="font-bold text-lg">${t('common.edit_entity', { entity: t('entities.tag') })}</h3>
|
||||
<form id="edit-tag-form" class="py-4">
|
||||
<div class="form-control mb-4">
|
||||
<label class="label"><span class="label-text">${t('common.key')}</span></label>
|
||||
@@ -188,7 +188,7 @@ export async function render(container, params, router) {
|
||||
|
||||
<dialog id="moveModal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">${t('admin_node_tags.move_tag_title')}</h3>
|
||||
<h3 class="font-bold text-lg">${t('admin_node_tags.move_to_another_node')}</h3>
|
||||
<form id="move-tag-form" class="py-4">
|
||||
<div class="form-control mb-4">
|
||||
<label class="label"><span class="label-text">${t('admin_node_tags.tag_key')}</span></label>
|
||||
@@ -207,11 +207,11 @@ export async function render(container, params, router) {
|
||||
</div>
|
||||
<div class="alert alert-warning mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
<span>${t('admin_node_tags.move_tag_warning')}</span>
|
||||
<span>${t('admin_node_tags.move_warning')}</span>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button type="button" class="btn" id="moveCancel">${t('common.cancel')}</button>
|
||||
<button type="submit" class="btn btn-warning">${t('admin_node_tags.move_tag')}</button>
|
||||
<button type="submit" class="btn btn-warning">${t('common.move_entity', { entity: t('entities.tag') })}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -220,7 +220,7 @@ export async function render(container, params, router) {
|
||||
|
||||
<dialog id="deleteModal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">${t('admin_node_tags.delete_tag')}</h3>
|
||||
<h3 class="font-bold text-lg">${t('common.delete_entity', { entity: t('entities.tag') })}</h3>
|
||||
<div class="py-4">
|
||||
<p class="py-4">Are you sure you want to delete the tag "<span id="deleteKeyDisplay" class="font-mono font-semibold"></span>"?</p>
|
||||
<div class="alert alert-error mb-4">
|
||||
@@ -267,7 +267,7 @@ export async function render(container, params, router) {
|
||||
|
||||
<dialog id="deleteAllModal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">${t('admin_node_tags.delete_all_title')}</h3>
|
||||
<h3 class="font-bold text-lg">${t('common.delete_all_entity', { entity: t('entities.tags') })}</h3>
|
||||
<div class="py-4">
|
||||
<p class="mb-4">${unsafeHTML(t('admin_node_tags.delete_all_confirm', { count: tags.length, name: nodeName }))}</p>
|
||||
<div class="alert alert-error mb-4">
|
||||
@@ -276,7 +276,7 @@ export async function render(container, params, router) {
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button type="button" class="btn" id="deleteAllCancel">${t('common.cancel')}</button>
|
||||
<button type="button" class="btn btn-error" id="deleteAllConfirm">${t('admin_node_tags.delete_all_tags')}</button>
|
||||
<button type="button" class="btn btn-error" id="deleteAllConfirm">${t('common.delete_all_entity', { entity: t('entities.tags') })}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,12 +302,12 @@ export async function render(container, params, router) {
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">${t('admin_node_tags.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.tags')}</h1>
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/">${t('nav.home')}</a></li>
|
||||
<li><a href="/a/">${t('nav.admin')}</a></li>
|
||||
<li>${t('admin_node_tags.title')}</li>
|
||||
<li>${t('entities.tags')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -371,7 +371,7 @@ ${contentHtml}`, container);
|
||||
await apiPost('/api/v1/nodes/' + encodeURIComponent(selectedPublicKey) + '/tags', {
|
||||
key, value, value_type,
|
||||
});
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.tag_added')));
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.entity_added')));
|
||||
} catch (err) {
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&error=' + encodeURIComponent(err.message));
|
||||
}
|
||||
@@ -403,7 +403,7 @@ ${contentHtml}`, container);
|
||||
value, value_type,
|
||||
});
|
||||
container.querySelector('#editModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.tag_updated')));
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.entity_updated')));
|
||||
} catch (err) {
|
||||
container.querySelector('#editModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&error=' + encodeURIComponent(err.message));
|
||||
@@ -435,7 +435,7 @@ ${contentHtml}`, container);
|
||||
new_public_key: newPublicKey,
|
||||
});
|
||||
container.querySelector('#moveModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.tag_moved')));
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.entity_moved')));
|
||||
} catch (err) {
|
||||
container.querySelector('#moveModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&error=' + encodeURIComponent(err.message));
|
||||
@@ -460,7 +460,7 @@ ${contentHtml}`, container);
|
||||
try {
|
||||
await apiDelete('/api/v1/nodes/' + encodeURIComponent(selectedPublicKey) + '/tags/' + encodeURIComponent(activeTagKey));
|
||||
container.querySelector('#deleteModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.tag_deleted')));
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.entity_deleted')));
|
||||
} catch (err) {
|
||||
container.querySelector('#deleteModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&error=' + encodeURIComponent(err.message));
|
||||
@@ -487,7 +487,7 @@ ${contentHtml}`, container);
|
||||
try {
|
||||
const result = await apiPost('/api/v1/nodes/' + encodeURIComponent(selectedPublicKey) + '/tags/copy-to/' + encodeURIComponent(destKey));
|
||||
container.querySelector('#copyAllModal').close();
|
||||
const msg = t('admin_node_tags.copied_tags', { copied: result.copied, skipped: result.skipped });
|
||||
const msg = t('admin_node_tags.copied_entities', { copied: result.copied, skipped: result.skipped });
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(msg));
|
||||
} catch (err) {
|
||||
container.querySelector('#copyAllModal').close();
|
||||
@@ -511,7 +511,7 @@ ${contentHtml}`, container);
|
||||
try {
|
||||
await apiDelete('/api/v1/nodes/' + encodeURIComponent(selectedPublicKey) + '/tags');
|
||||
container.querySelector('#deleteAllModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.all_tags_deleted')));
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&message=' + encodeURIComponent(t('admin_node_tags.all_entities_deleted')));
|
||||
} catch (err) {
|
||||
container.querySelector('#deleteAllModal').close();
|
||||
router.navigate('/a/node-tags?public_key=' + encodeURIComponent(selectedPublicKey) + '&error=' + encodeURIComponent(err.message));
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function render(container, params, router) {
|
||||
function renderPage(content, { total = null } = {}) {
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">${t('advertisements.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.advertisements')}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
${tzBadge}
|
||||
${total !== null ? html`<span class="badge badge-lg">${t('common.total', { count: total })}</span>` : nothing}
|
||||
|
||||
@@ -142,7 +142,7 @@ export async function render(container, params, router) {
|
||||
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">${t('dashboard.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.dashboard')}</h1>
|
||||
</div>
|
||||
|
||||
${topCount > 0 ? html`
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
function renderRadioConfig(rc) {
|
||||
if (!rc) return nothing;
|
||||
const fields = [
|
||||
[t('home.profile'), rc.profile],
|
||||
[t('links.profile'), rc.profile],
|
||||
[t('home.frequency'), rc.frequency],
|
||||
[t('home.bandwidth'), rc.bandwidth],
|
||||
[t('home.spreading_factor'), rc.spreading_factor],
|
||||
@@ -164,11 +164,11 @@ export async function render(container, params, router) {
|
||||
<div class="flex gap-2 mt-4">
|
||||
<a href="https://meshcore.co.uk/" target="_blank" rel="noopener noreferrer" class="btn btn-outline btn-sm">
|
||||
${iconGlobe('h-4 w-4 mr-1')}
|
||||
${t('home.website')}
|
||||
${t('links.website')}
|
||||
</a>
|
||||
<a href="https://github.com/meshcore-dev/MeshCore" target="_blank" rel="noopener noreferrer" class="btn btn-outline btn-sm">
|
||||
${iconGithub('h-4 w-4 mr-1')}
|
||||
${t('home.github')}
|
||||
${t('links.github')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +173,7 @@ export async function render(container, params, router) {
|
||||
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">${t('map.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.map')}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
${timezoneIndicator()}
|
||||
<span id="node-count" class="badge badge-lg">${t('common.loading')}</span>
|
||||
|
||||
@@ -85,8 +85,8 @@ export async function render(container, params, router) {
|
||||
if (members.length === 0) {
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">${t('members.title')}</h1>
|
||||
<span class="badge badge-lg">${t('members.count', { count: 0 })}</span>
|
||||
<h1 class="text-3xl font-bold">${t('entities.members')}</h1>
|
||||
<span class="badge badge-lg">${t('common.count_entity', { count: 0, entity: t('entities.members').toLowerCase() })}</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
@@ -138,8 +138,8 @@ export async function render(container, params, router) {
|
||||
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">${t('members.title')}</h1>
|
||||
<span class="badge badge-lg">${t('members.count', { count: members.length })}</span>
|
||||
<h1 class="text-3xl font-bold">${t('entities.members')}</h1>
|
||||
<span class="badge badge-lg">${t('common.count_entity', { count: members.length, entity: t('entities.members').toLowerCase() })}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-start">
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function render(container, params, router) {
|
||||
function renderPage(content, { total = null } = {}) {
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">${t('messages.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.messages')}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
${tzBadge}
|
||||
${total !== null ? html`<span class="badge badge-lg">${t('common.total', { count: total })}</span>` : nothing}
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function render(container, params, router) {
|
||||
function renderPage(content, { total = null } = {}) {
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">${t('nodes.title')}</h1>
|
||||
<h1 class="text-3xl font-bold">${t('entities.nodes')}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
${tzBadge}
|
||||
${total !== null ? html`<span class="badge badge-lg">${t('common.total', { count: total })}</span>` : nothing}
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
{
|
||||
"entities": {
|
||||
"home": "Home",
|
||||
"dashboard": "Dashboard",
|
||||
"nodes": "Nodes",
|
||||
"node": "Node",
|
||||
"node_detail": "Node Detail",
|
||||
"advertisements": "Advertisements",
|
||||
"advertisement": "Advertisement",
|
||||
"messages": "Messages",
|
||||
"message": "Message",
|
||||
"map": "Map",
|
||||
"members": "Members",
|
||||
"member": "Member",
|
||||
"admin": "Admin",
|
||||
"tags": "Tags",
|
||||
"tag": "Tag"
|
||||
},
|
||||
"nav": {
|
||||
"home": "Home",
|
||||
"dashboard": "Dashboard",
|
||||
@@ -9,18 +26,6 @@
|
||||
"members": "Members",
|
||||
"admin": "Admin"
|
||||
},
|
||||
"page_title": {
|
||||
"dashboard": "Dashboard - {{network_name}}",
|
||||
"nodes": "Nodes - {{network_name}}",
|
||||
"node_detail": "Node Detail - {{network_name}}",
|
||||
"advertisements": "Advertisements - {{network_name}}",
|
||||
"messages": "Messages - {{network_name}}",
|
||||
"map": "Map - {{network_name}}",
|
||||
"members": "Members - {{network_name}}",
|
||||
"admin": "Admin - {{network_name}}",
|
||||
"admin_node_tags": "Node Tags - Admin - {{network_name}}",
|
||||
"admin_members": "Members - Admin - {{network_name}}"
|
||||
},
|
||||
"common": {
|
||||
"filter": "Filter",
|
||||
"clear": "Clear",
|
||||
@@ -30,7 +35,17 @@
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"move": "Move",
|
||||
"save": "Save",
|
||||
"save_changes": "Save Changes",
|
||||
"add": "Add",
|
||||
"add_entity": "Add {{entity}}",
|
||||
"add_new_entity": "Add New {{entity}}",
|
||||
"edit_entity": "Edit {{entity}}",
|
||||
"delete_entity": "Delete {{entity}}",
|
||||
"delete_all_entity": "Delete All {{entity}}",
|
||||
"move_entity": "Move {{entity}}",
|
||||
"copy_entity": "Copy {{entity}}",
|
||||
"view_entity": "View {{entity}}",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"go_home": "Go Home",
|
||||
@@ -39,12 +54,12 @@
|
||||
"failed_to_load_page": "Failed to load page",
|
||||
"total": "{{count}} total",
|
||||
"shown": "{{count}} shown",
|
||||
"count_entity": "{{count}} {{entity}}",
|
||||
"type": "Type",
|
||||
"name": "Name",
|
||||
"key": "Key",
|
||||
"value": "Value",
|
||||
"time": "Time",
|
||||
"node": "Node",
|
||||
"actions": "Actions",
|
||||
"updated": "Updated",
|
||||
"sign_in": "Sign In",
|
||||
@@ -53,7 +68,6 @@
|
||||
"all_types": "All Types",
|
||||
"all_nodes": "All Nodes",
|
||||
"all_members": "All Members",
|
||||
"member": "Member",
|
||||
"node_type": "Node Type",
|
||||
"show": "Show",
|
||||
"search_placeholder": "Search by name, ID, or public key...",
|
||||
@@ -70,11 +84,17 @@
|
||||
"received_by": "Received By",
|
||||
"receivers": "Receivers",
|
||||
"from": "From",
|
||||
"message": "Message",
|
||||
"close": "close",
|
||||
"unnamed": "Unnamed",
|
||||
"unnamed_node": "Unnamed Node"
|
||||
},
|
||||
"links": {
|
||||
"website": "Website",
|
||||
"github": "GitHub",
|
||||
"discord": "Discord",
|
||||
"youtube": "YouTube",
|
||||
"profile": "Profile"
|
||||
},
|
||||
"time": {
|
||||
"days_ago": "{{count}}d ago",
|
||||
"hours_ago": "{{count}}h ago",
|
||||
@@ -95,15 +115,10 @@
|
||||
"welcome_default": "Welcome to the {{network_name}} mesh network dashboard. Monitor network activity, view connected nodes, and explore message history.",
|
||||
"total_nodes": "Total Nodes",
|
||||
"all_discovered_nodes": "All discovered nodes",
|
||||
"advertisements": "Advertisements",
|
||||
"messages": "Messages",
|
||||
"network_info": "Network Info",
|
||||
"network_activity": "Network Activity",
|
||||
"meshcore_attribution": "Our local off-grid mesh network is made possible by",
|
||||
"meshcore_tagline": "Connecting people and things, without using the internet",
|
||||
"website": "Website",
|
||||
"github": "GitHub",
|
||||
"profile": "Profile",
|
||||
"frequency": "Frequency",
|
||||
"bandwidth": "Bandwidth",
|
||||
"spreading_factor": "Spreading Factor",
|
||||
@@ -111,35 +126,28 @@
|
||||
"tx_power": "TX Power"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"total_nodes": "Total Nodes",
|
||||
"all_discovered_nodes": "All discovered nodes",
|
||||
"advertisements": "Advertisements",
|
||||
"messages": "Messages",
|
||||
"recent_advertisements": "Recent Advertisements",
|
||||
"recent_channel_messages": "Recent Channel Messages",
|
||||
"channel": "Channel {{number}}",
|
||||
"no_advertisements_yet": "No advertisements recorded yet."
|
||||
},
|
||||
"nodes": {
|
||||
"title": "Nodes",
|
||||
"no_nodes_found": "No nodes found.",
|
||||
"node_not_found": "Node not found: {{public_key}}",
|
||||
"back_to_nodes": "Back to Nodes",
|
||||
"scan_to_add": "Scan to add as contact",
|
||||
"recent_advertisements": "Recent Advertisements",
|
||||
"no_advertisements": "No advertisements recorded.",
|
||||
"tags": "Tags",
|
||||
"no_tags": "No tags defined.",
|
||||
"edit_tags": "Edit Tags",
|
||||
"add_tags": "Add Tags"
|
||||
},
|
||||
"advertisements": {
|
||||
"title": "Advertisements",
|
||||
"no_advertisements_found": "No advertisements found."
|
||||
},
|
||||
"messages": {
|
||||
"title": "Messages",
|
||||
"no_messages_found": "No messages found.",
|
||||
"type_direct": "Direct",
|
||||
"type_channel": "Channel",
|
||||
@@ -147,7 +155,6 @@
|
||||
"type_public": "Public"
|
||||
},
|
||||
"map": {
|
||||
"title": "Map",
|
||||
"show_labels": "Show Labels",
|
||||
"infrastructure_only": "Infrastructure Only",
|
||||
"legend": "Legend:",
|
||||
@@ -162,8 +169,6 @@
|
||||
"select_destination_node": "-- Select destination node --"
|
||||
},
|
||||
"members": {
|
||||
"title": "Members",
|
||||
"count": "{{count}} members",
|
||||
"no_members_configured": "No members configured",
|
||||
"no_members_description": "To display network members, create a members.yaml file in your seed directory.",
|
||||
"members_file_format": "Members File Format",
|
||||
@@ -180,78 +185,56 @@
|
||||
"failed_to_load": "Failed to load page"
|
||||
},
|
||||
"admin": {
|
||||
"title": "Admin",
|
||||
"access_denied": "Access Denied",
|
||||
"admin_not_enabled": "The admin interface is not enabled.",
|
||||
"admin_enable_hint": "Set <code>WEB_ADMIN_ENABLED=true</code> to enable admin features.",
|
||||
"auth_required": "Authentication Required",
|
||||
"auth_required_description": "You must sign in to access the admin interface.",
|
||||
"welcome": "Welcome to the admin panel.",
|
||||
"members_title": "Members",
|
||||
"members_description": "Manage network members and operators.",
|
||||
"node_tags_title": "Node Tags",
|
||||
"node_tags_description": "Manage custom tags and metadata for network nodes."
|
||||
},
|
||||
"admin_members": {
|
||||
"title": "Members",
|
||||
"network_members": "Network Members ({{count}})",
|
||||
"add_member": "Add Member",
|
||||
"add_new_member": "Add New Member",
|
||||
"edit_member": "Edit Member",
|
||||
"delete_member": "Delete Member",
|
||||
"member_id": "Member ID",
|
||||
"member_id_hint": "Unique identifier (letters, numbers, underscore)",
|
||||
"no_members_yet": "No members configured yet.",
|
||||
"no_members_hint": "Click \"Add Member\" to create the first member.",
|
||||
"delete_confirm": "Are you sure you want to delete member <strong>{{name}}</strong>?",
|
||||
"cannot_be_undone": "This action cannot be undone.",
|
||||
"member_added": "Member added successfully",
|
||||
"member_updated": "Member updated successfully",
|
||||
"member_deleted": "Member deleted successfully"
|
||||
"entity_added": "Member added successfully",
|
||||
"entity_updated": "Member updated successfully",
|
||||
"entity_deleted": "Member deleted successfully"
|
||||
},
|
||||
"admin_node_tags": {
|
||||
"title": "Node Tags",
|
||||
"select_node": "Select Node",
|
||||
"select_node_placeholder": "-- Select a node --",
|
||||
"load_tags": "Load Tags",
|
||||
"tags_count": "Tags ({{count}})",
|
||||
"add_new_tag": "Add New Tag",
|
||||
"add_tag": "Add Tag",
|
||||
"edit_tag": "Edit Tag",
|
||||
"move_tag": "Move Tag",
|
||||
"move_tag_title": "Move Tag to Another Node",
|
||||
"move_tag_warning": "This will move the tag from the current node to the destination node.",
|
||||
"delete_tag": "Delete Tag",
|
||||
"move_to_another_node": "Move Tag to Another Node",
|
||||
"move_warning": "This will move the tag from the current node to the destination node.",
|
||||
"delete_tag_confirm": "Are you sure you want to delete the tag \"{{key}}\"?",
|
||||
"cannot_be_undone": "This action cannot be undone.",
|
||||
"copy_all": "Copy All",
|
||||
"copy_all_title": "Copy All Tags to Another Node",
|
||||
"copy_all_description": "Copy all {{count}} tag(s) from <strong>{{name}}</strong> to another node.",
|
||||
"copy_all_info": "Tags that already exist on the destination node will be skipped. Original tags remain on this node.",
|
||||
"copy_tags": "Copy Tags",
|
||||
"delete_all": "Delete All",
|
||||
"delete_all_title": "Delete All Tags",
|
||||
"delete_all_confirm": "Are you sure you want to delete all {{count}} tag(s) from <strong>{{name}}</strong>?",
|
||||
"delete_all_warning": "This action cannot be undone. All tags will be permanently deleted.",
|
||||
"delete_all_tags": "Delete All Tags",
|
||||
"view_node": "View Node",
|
||||
"destination_node": "Destination Node",
|
||||
"tag_key": "Tag Key",
|
||||
"no_tags_found": "No tags found for this node.",
|
||||
"no_tags_hint": "Add a new tag below.",
|
||||
"select_a_node": "Select a Node",
|
||||
"select_a_node_description": "Choose a node from the dropdown above to view and manage its tags.",
|
||||
"tag_added": "Tag added successfully",
|
||||
"tag_updated": "Tag updated successfully",
|
||||
"tag_moved": "Tag moved successfully",
|
||||
"tag_deleted": "Tag deleted successfully",
|
||||
"all_tags_deleted": "All tags deleted successfully",
|
||||
"copied_tags": "Copied {{copied}} tag(s), skipped {{skipped}}"
|
||||
"entity_added": "Tag added successfully",
|
||||
"entity_updated": "Tag updated successfully",
|
||||
"entity_moved": "Tag moved successfully",
|
||||
"entity_deleted": "Tag deleted successfully",
|
||||
"all_entities_deleted": "All tags deleted successfully",
|
||||
"copied_entities": "Copied {{copied}} tag(s), skipped {{skipped}}"
|
||||
},
|
||||
"footer": {
|
||||
"powered_by": "Powered by",
|
||||
"discord": "Discord",
|
||||
"github": "GitHub",
|
||||
"youtube": "YouTube"
|
||||
"powered_by": "Powered by"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,15 +150,15 @@
|
||||
{% endif %}
|
||||
{% if network_contact_email and network_contact_discord %} | {% endif %}
|
||||
{% if network_contact_discord %}
|
||||
<a href="{{ network_contact_discord }}" target="_blank" rel="noopener noreferrer" class="link link-hover">{{ t('footer.discord') }}</a>
|
||||
<a href="{{ network_contact_discord }}" target="_blank" rel="noopener noreferrer" class="link link-hover">{{ t('links.discord') }}</a>
|
||||
{% endif %}
|
||||
{% if (network_contact_email or network_contact_discord) and network_contact_github %} | {% endif %}
|
||||
{% if network_contact_github %}
|
||||
<a href="{{ network_contact_github }}" target="_blank" rel="noopener noreferrer" class="link link-hover">{{ t('footer.github') }}</a>
|
||||
<a href="{{ network_contact_github }}" target="_blank" rel="noopener noreferrer" class="link link-hover">{{ t('links.github') }}</a>
|
||||
{% endif %}
|
||||
{% if (network_contact_email or network_contact_discord or network_contact_github) and network_contact_youtube %} | {% endif %}
|
||||
{% if network_contact_youtube %}
|
||||
<a href="{{ network_contact_youtube }}" target="_blank" rel="noopener noreferrer" class="link link-hover">{{ t('footer.youtube') }}</a>
|
||||
<a href="{{ network_contact_youtube }}" target="_blank" rel="noopener noreferrer" class="link link-hover">{{ t('links.youtube') }}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="text-xs opacity-50 mt-2">{% if admin_enabled %}<a href="/a/" class="link link-hover">{{ t('nav.admin') }}</a> | {% endif %}{{ t('footer.powered_by') }} <a href="https://github.com/ipnet-mesh/meshcore-hub" target="_blank" rel="noopener noreferrer" class="link link-hover">MeshCore Hub</a> {{ version }}</p>
|
||||
|
||||
Reference in New Issue
Block a user