Files
meshcore-hub/docs/i18n.md
T
Louis King f8c2a7bb40 Rename channel visibility 'public' to 'community'
- Rename ChannelVisibility.PUBLIC to ChannelVisibility.COMMUNITY
- Update stored value from 'public' to 'community' across model, schema, API, CLI, and frontend
- Add Alembic migration to update existing database rows
- Consolidate upgrade docs: merge v0.11.0, v0.12.0, v0.13.0 into single v0.11.0 section
- Add i18n visibility level translation keys (en, nl)
- Update section headings on channels page to use t() for i18n
- Keep visibility badges lowercase per UI design
2026-06-04 14:07:12 +01:00

504 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Translation Reference Guide
This document provides a comprehensive reference for translating the MeshCore Hub web dashboard.
## File Structure
Translation files are JSON files named by language code (e.g., `en.json`, `es.json`, `fr.json`) and located in `/src/meshcore_hub/web/static/locales/`.
## Variable Interpolation
Many translations use `{{variable}}` syntax for dynamic content. These must be preserved exactly:
```json
"total": "{{count}} total"
```
When translating, keep the variable names unchanged:
```json
"total": "{{count}} au total" // French example
```
## Translation Sections
### 1. `entities`
Core entity names used throughout the application. These are referenced by other translations for composition.
| Key | English | Context |
|-----|---------|---------|
| `home` | Home | Homepage/breadcrumb navigation |
| `dashboard` | Dashboard | Main dashboard page |
| `nodes` | Nodes | Mesh network nodes (plural) |
| `node` | Node | Single mesh network node |
| `node_detail` | Node Detail | Node details page |
| `advertisements` | Adverts | Network advertisements (plural, used in nav menus and hero cards) |
| `advertisement` | Advert | Single advertisement |
| `messages` | Messages | Network messages (plural) |
| `message` | Message | Single message |
| `map` | Map | Network map page |
| `members` | Members | Network members (plural) |
| `member` | Member | Single network member |
| `tags` | Tags | Node metadata tags (plural) |
| `tag` | Tag | Single tag |
| `channel` | Channel | Mesh network channel |
**Usage:** These are used with composite patterns. For example, `t('common.add_entity', { entity: t('entities.node') })` produces "Add Node".
### 2. `common`
Reusable patterns and UI elements used across multiple pages.
#### Actions
| Key | English | Context |
|-----|---------|---------|
| `filter` | Filter | Filter button/action |
| `clear` | Clear | Clear action |
| `clear_filters` | Clear Filters | Reset all filters |
| `search` | Search | Search button/action |
| `cancel` | Cancel | Cancel button in dialogs |
| `delete` | Delete | Delete button |
| `edit` | Edit | Edit button |
| `move` | Move | Move button |
| `save` | Save | Save button |
| `save_changes` | Save Changes | Save changes button |
| `add` | Add | Add button |
| `close` | close | Close button (lowercase for accessibility) |
| `sign_in` | Sign In | Authentication sign in |
| `sign_out` | Sign Out | Authentication sign out |
| `go_home` | Go Home | Return to homepage button |
#### Composite Patterns with Entity
These patterns use `{{entity}}` variable - the entity name is provided dynamically:
| Key | English | Example Output |
|-----|---------|----------------|
| `add_entity` | Add {{entity}} | "Add Node", "Add Tag" |
| `add_new_entity` | Add New {{entity}} | "Add New Member" |
| `edit_entity` | Edit {{entity}} | "Edit Tag" |
| `delete_entity` | Delete {{entity}} | "Delete Member" |
| `delete_all_entity` | Delete All {{entity}} | "Delete All Tags" |
| `move_entity` | Move {{entity}} | "Move Tag" |
| `move_entity_to_another_node` | Move {{entity}} to Another Node | "Move Tag to Another Node" |
| `copy_entity` | Copy {{entity}} | "Copy Tags" |
| `copy_all_entity_to_another_node` | Copy All {{entity}} to Another Node | "Copy All Tags to Another Node" |
| `view_entity` | View {{entity}} | "View Node" |
| `recent_entity` | Recent {{entity}} | "Recent Advertisements" |
| `total_entity` | Total {{entity}} | "Total Nodes" |
| `all_entity` | All {{entity}} | "All Messages" |
#### Empty State Patterns
These patterns indicate when data is absent. Use `{{entity}}` in lowercase (e.g., "nodes", not "Nodes"):
| Key | English | Context |
|-----|---------|---------|
| `no_entity_found` | No {{entity}} found | Search/filter returned no results |
| `no_entity_recorded` | No {{entity}} recorded | No historical records exist |
| `no_entity_defined` | No {{entity}} defined | No configuration/definitions exist |
| `no_entity_in_database` | No {{entity}} in database | Database is empty |
| `no_entity_configured` | No {{entity}} configured | System not configured |
| `no_entity_yet` | No {{entity}} yet | Empty state, expecting data later |
| `entity_not_found_details` | {{entity}} not found: {{details}} | Specific item not found with details |
| `page_not_found` | Page not found | 404 error message |
#### Confirmation Patterns
Used in delete/move dialogs. Variables: `{{entity}}`, `{{name}}`, `{{count}}`:
| Key | English | Context |
|-----|---------|---------|
| `delete_entity_confirm` | Are you sure you want to delete {{entity}} <strong>{{name}}</strong>? | Single item delete confirmation |
| `delete_all_entity_confirm` | Are you sure you want to delete all {{count}} {{entity}} from <strong>{{name}}</strong>? | Bulk delete confirmation |
| `cannot_be_undone` | This action cannot be undone. | Warning in delete dialogs |
#### Success Messages
Toast/flash messages after successful operations:
| Key | English | Context |
|-----|---------|---------|
| `entity_added_success` | {{entity}} added successfully | After creating new item |
| `entity_updated_success` | {{entity}} updated successfully | After updating item |
| `entity_deleted_success` | {{entity}} deleted successfully | After deleting item |
| `entity_moved_success` | {{entity}} moved successfully | After moving tag to another node |
| `all_entity_deleted_success` | All {{entity}} deleted successfully | After bulk delete |
| `copy_all_entity_description` | Copy all {{count}} {{entity}} from <strong>{{name}}</strong> to another node. | Copy operation description |
#### Navigation & Status
| Key | English | Context |
|-----|---------|---------|
| `previous` | Previous | Pagination previous |
| `next` | Next | Pagination next |
| `loading` | Loading... | Loading indicator |
| `error` | Error | Error state |
| `failed_to_load_page` | Failed to load page | Page load error |
#### Counts & Metrics
| Key | English | Context |
|-----|---------|---------|
| `total` | {{count}} total | Total count display |
| `shown` | {{count}} shown | Filtered count display |
| `count_entity` | {{count}} {{entity}} | Generic count with entity |
#### Form Fields & Labels
| Key | English | Context |
|-----|---------|---------|
| `type` | Type | Type field/column header |
| `name` | Name | Name field/column header |
| `key` | Key | Key field (for tags) |
| `value` | Value | Value field (for tags) |
| `time` | Time | Time column header |
| `actions` | Actions | Actions column header |
| `updated` | Updated | Last updated timestamp |
| `view_details` | View Details | View details link |
| `all_types` | All Types | "All types" filter option |
| `all_channels` | All Channels | "All channels" filter option |
| `all_members` | All Members | "All members" filter option (only shown when OIDC is enabled) |
| `all_operators` | All Operators | "All operators" filter option for operator-only filter dropdowns |
| `filter_member_label` | Member | Label for member filter dropdown |
| `filter_operator_label` | Operator | Label for operator filter dropdown (used on Nodes, Advertisements, Map pages) |
| `node_type` | Node Type | Node type field |
| `show` | Show | Show/display action |
| `search_placeholder` | Search by name, ID, or public key... | Search input placeholder |
| `contact` | Contact | Contact information field |
| `description` | Description | Description field |
| `callsign` | Callsign | Amateur radio callsign field |
| `tags` | Tags | Tags label/header |
| `last_seen` | Last Seen | Last seen timestamp (table header) |
| `first_seen_label` | First seen: | First seen label (inline with colon) |
| `last_seen_label` | Last seen: | Last seen label (inline with colon) |
| `location` | Location | Geographic location |
| `public_key` | Public Key | Node public key |
| `received` | Received | Received timestamp |
| `received_by` | Received By | Received by field |
| `receivers` | Receivers | Multiple receivers |
| `from` | From | Message sender |
| `hops` | Hops | Number of mesh hops (table header) |
| `observers` | Observers | Observers of an event |
| `snr_db` | SNR (dB) | Signal-to-noise ratio in decibels (table header) |
| `unnamed` | Unnamed | Fallback for unnamed items |
| `unnamed_node` | Unnamed Node | Fallback for unnamed nodes |
| `sort_by` | Sort by | Label before mobile sort dropdown |
**Note:** Keys ending in `_label` have colons and are used inline. Keys without `_label` are for table headers.
### 3. `links`
Platform and external link labels:
| Key | English | Context |
|-----|---------|---------|
| `website` | Website | Website link label |
| `github` | GitHub | GitHub link label (preserve capitalization) |
| `discord` | Discord | Discord link label |
| `youtube` | YouTube | YouTube link label (preserve capitalization) |
| `profile` | Profile | Radio profile label |
### 4. `auto_refresh`
Auto-refresh controls for list pages (nodes, advertisements, messages):
| Key | English | Context |
|-----|---------|---------|
| `pause` | Pause auto-refresh | Tooltip on pause button when auto-refresh is active |
| `resume` | Resume auto-refresh | Tooltip on play button when auto-refresh is paused |
### 5. `time`
Time-related labels and formats:
| Key | English | Context |
|-----|---------|---------|
| `days_ago` | {{count}}d ago | Days ago (abbreviated) |
| `hours_ago` | {{count}}h ago | Hours ago (abbreviated) |
| `minutes_ago` | {{count}}m ago | Minutes ago (abbreviated) |
| `less_than_minute` | <1m ago | Less than one minute ago |
| `last_7_days` | Last 7 days | Last 7 days label |
| `per_day_last_7_days` | Per day (last 7 days) | Per day over last 7 days |
| `over_time_last_7_days` | Over time (last 7 days) | Over time last 7 days |
| `activity_per_day_last_7_days` | Activity per day (last 7 days) | Activity chart label |
### 6. `node_types`
Mesh network node type labels:
| Key | English | Context |
|-----|---------|---------|
| `chat` | Chat | Chat node type |
| `repeater` | Repeater | Repeater/relay node type |
| `companion` | Companion | Companion/observer node type |
| `room` | Room Server | Room server/group node type |
| `unknown` | Unknown | Unknown node type fallback |
### 7. `home`
Homepage-specific content:
| Key | English | Context |
|-----|---------|---------|
| `welcome_default` | Welcome to the {{network_name}} mesh network dashboard. Monitor network activity, view connected nodes, and explore message history. | Default welcome message |
| `all_discovered_nodes` | All discovered nodes | Stat description |
| `network_info` | Network Info | Network info card title |
| `network_activity` | Network Activity | Activity chart title |
| `frequency` | Frequency | Radio frequency label |
| `bandwidth` | Bandwidth | Radio bandwidth label |
| `spreading_factor` | Spreading Factor | LoRa spreading factor label |
| `coding_rate` | Coding Rate | LoRa coding rate label |
| `tx_power` | TX Power | Transmit power label |
| `advertisements` | Adverts | Homepage stat label |
| `messages` | Messages | Homepage stat label |
**Note:** MeshCore tagline "Off-Grid, Open-Source Encrypted Messaging" is hardcoded in English and should not be translated (trademark).
### 8. `dashboard`
Dashboard page content:
| Key | English | Context |
|-----|---------|---------|
| `all_discovered_nodes` | All discovered nodes | Stat label |
| `recent_channel_messages` | Recent Channel Messages | Recent messages card title |
| `channel` | Channel {{number}} | Channel label with number |
### 9. `nodes`
Node detail page labels:
| Key | English | Context |
|-----|---------|---------|
| `scan_to_add` | Scan to add as contact | QR code instruction |
| `ownership` | Ownership | Adoption card heading on node detail page |
| `adopt` | Adopt | Adopt button (operator/admin only) |
| `release` | Release | Release button (owner or admin only) |
| `adopted_by` | Adopted by {{name}} | Display name of adopting user ({{name}} = user name or ID) |
| `not_adopted` | This node has not been adopted by any operator. | Shown when node is unadopted and user is operator/admin |
| `adopt_success` | Node adopted successfully | Flash message after adopting |
| `release_success` | Node released successfully | Flash message after releasing |
| `release_confirm` | Are you sure you want to release this node? | Confirmation dialog for release |
#### Sort Options (`nodes.sort`)
Mobile sort dropdown labels for the nodes list page:
| Key | English | Context |
|-----|---------|---------|
| `sort.last_seen_newest` | Last Seen (newest) | Default sort: most recently seen first |
| `sort.last_seen_oldest` | Last Seen (oldest) | Least recently seen first |
| `sort.name_az` | Name (AZ) | Alphabetical by display name |
| `sort.name_za` | Name (ZA) | Reverse alphabetical by display name |
| `sort.key_asc` | Public Key (ascending) | Sorted by public key ascending |
| `sort.key_desc` | Public Key (descending) | Sorted by public key descending |
### 10. `advertisements`
Route type filter and sort options for the advertisements list page:
#### Route Type Filter
| Key | English | Context |
|-----|---------|---------|
| `filter_route_type_label` | Advert Type | Label for route type filter dropdown |
| `route_type_all` | All | Show all route types (no filter) |
| `route_type_flood` | Flood & Relay | Show flood and transport_flood adverts (default) |
| `route_type_direct` | Zero-hop only | Show only direct (zero-hop) adverts |
| `route_type_unknown` | Unknown | Displayed when route_type is NULL (historical records) |
| `col_route_type` | Type | Table column header for route type |
#### Sort Options (`advertisements.sort`)
Mobile sort dropdown labels for the advertisements list page:
| Key | English | Context |
|-----|---------|---------|
| `sort.newest` | Time (newest) | Default sort: most recent first |
| `sort.oldest` | Time (oldest) | Oldest first |
| `sort.node_az` | Node (AZ) | Alphabetical by node display name |
| `sort.node_za` | Node (ZA) | Reverse alphabetical by node display name |
| `sort.key_asc` | Public Key (ascending) | Sorted by public key ascending |
| `sort.key_desc` | Public Key (descending) | Sorted by public key descending |
### 11. `messages`
Message type labels:
| Key | English | Context |
|-----|---------|---------|
| `type_direct` | Direct | Direct message type |
| `type_channel` | Channel | Channel message type |
| `type_contact` | Contact | Contact message type |
| `type_public` | Public | Public message type |
#### Sort Options (`messages.sort`)
Mobile sort dropdown labels for the messages list page:
| Key | English | Context |
|-----|---------|---------|
| `sort.newest` | Time (newest) | Default sort: most recent first |
| `sort.oldest` | Time (oldest) | Oldest first |
| `sort.type_az` | Type (AZ) | Alphabetical by message type |
| `sort.type_za` | Type (ZA) | Reverse alphabetical by message type |
| `sort.from_az` | From (AZ) | Alphabetical by sender name |
| `sort.from_za` | From (ZA) | Reverse alphabetical by sender name |
| `sort.message_az` | Message (AZ) | Alphabetical by message text |
| `sort.message_za` | Message (ZA) | Reverse alphabetical by message text |
### 12. `map`
Map page content:
| Key | English | Context |
|-----|---------|---------|
| `show_labels` | Show Labels | Toggle to show node labels |
| `infrastructure_only` | Infrastructure Only | Toggle to show only infrastructure nodes (OIDC required, only rendered when `OIDC_ENABLED=true`; data sourced from node adoption, not tags) |
| `legend` | Legend: | Map legend header (only rendered when `OIDC_ENABLED=true`) |
| `infrastructure` | Infrastructure | Infrastructure node category (only rendered when `OIDC_ENABLED=true`; based on adoption status) |
| `public` | Public | Public node category |
| `nodes_on_map` | {{count}} nodes on map | Status text with coordinates |
| `nodes_none_have_coordinates` | {{count}} nodes (none have coordinates) | Status text without coordinates |
| `gps_description` | Nodes are placed on the map based on GPS coordinates from node reports or manual tags. | Map data source explanation |
| `owner` | Owner: | Node owner label |
| `role` | Role: | Member role label |
| `select_destination_node` | -- Select destination node -- | Dropdown placeholder |
### 13. `members`
Members page content:
| Key | English | Context |
|-----|---------|---------|
| `empty_state_description` | No members yet. | Empty state heading |
| `empty_description` | Members will appear here once users log in and adopt nodes. | Empty state description |
### 14. `channels`
Channel management and filter UI:
| Key | English | Context |
|-----|---------|---------|
| `title` | Channels | Page title |
| `add_channel` | Add Channel | Add button label |
| `edit_channel` | Edit Channel | Edit modal title |
| `delete_channel` | Delete Channel | Delete modal title |
| `delete_confirm` | Are you sure you want to delete channel {{name}}? | Delete confirmation message |
| `name_label` | Channel Name | Form label |
| `key_label` | Channel Key (hex) | Form label |
| `visibility_label` | Visibility | Form label |
| `visibility_community` | Community | Community visibility section heading |
| `visibility_member` | Member | Member visibility section heading |
| `visibility_operator` | Operator | Operator visibility section heading |
| `visibility_admin` | Admin | Admin visibility section heading |
| `enabled_label` | Enabled | Form label |
| `channel_hash_label` | Hash | Column header |
| `disabled` | Disabled | Disabled channel badge |
| `optgroup_standard` | Standard | Optgroup label for built-in channels (Public, Test) in channel filter dropdown |
| `optgroup_custom` | Custom | Optgroup label for user-defined channels in channel filter dropdown |
### 15. `not_found`
404 page content:
| Key | English | Context |
|-----|---------|---------|
| `description` | The page you're looking for doesn't exist or has been moved. | 404 description |
### 16. `custom_page`
Custom markdown page errors:
| Key | English | Context |
|-----|---------|---------|
| `failed_to_load` | Failed to load page | Page load error |
### 17. `auth`
Authentication UI:
| Key | English | Context |
|-----|---------|---------|
| `login` | Login | Login button text |
| `logout` | Logout | Logout menu item |
| `login_required` | Login required | Login required heading |
| `admin_required` | Admin access required | Admin access denied message |
| `login_hint` | Log in to access admin features | Hint shown to non-admin users |
| `logged_in_as` | Logged in as {{name}} | Logged in status ({{name}} = user display name) |
| `session_expired` | Session expired, please log in again | Session expiry notice |
| `role_admin` | admin | Admin role badge text |
| `role_member` | member | Member role badge text |
### 18. `footer`
Footer content:
| Key | English | Context |
|-----|---------|---------|
| `powered_by` | Powered by | "Powered by" attribution |
### 19. `user_profile`
User profile page (OIDC authenticated users):
| Key | English | Context |
|-----|---------|---------|
| `title` | Profile | Page heading |
| `your_profile` | Your Profile | Profile card heading |
| `profile_updated` | Profile updated successfully | Flash message after save |
| `save_profile` | Save Profile | Save button label |
| `name_label` | Display Name | Form label |
| `name_placeholder` | Your name or preferred name | Input placeholder |
| `callsign_label` | Callsign | Form label |
| `callsign_placeholder` | Amateur radio callsign (e.g., W1ABC) | Input placeholder |
| `description_label` | Description | Form label |
| `description_placeholder` | A short bio or description | Input placeholder |
| `url_label` | Website / Profile Link | Form label |
| `url_placeholder` | https://... | Input placeholder |
| `adopted_nodes` | Adopted Nodes | Adopted nodes card heading |
| `no_adopted_nodes` | No adopted nodes | Empty state |
| `login_to_view` | Log in to view your profile | Unauthenticated notice |
## Translation Tips
1. **Preserve HTML tags:** Some strings contain `<code>`, `<strong>`, or `<br/>` tags - keep these intact.
2. **Preserve variables:** Keep `{{variable}}` placeholders exactly as-is, only translate surrounding text.
3. **Entity composition:** Many translations reference `entities.*` keys. When translating entities, consider how they'll work in composite patterns (e.g., "Add {{entity}}" should make sense with "Node", "Tag", etc.).
4. **Capitalization:**
- Entity names should follow your language's capitalization rules for UI elements
- Inline labels (with colons) typically use sentence case
- Table headers typically use title case
- Action buttons can vary by language convention
5. **Colons:** Keys ending in `_label` include colons in English. Adjust punctuation to match your language's conventions for inline labels.
6. **Plurals:** Some languages have complex plural rules. You may need to add plural variants for `{{count}}` patterns. Consult the i18n library documentation for plural support.
7. **Length:** UI space is limited. Try to keep translations concise, especially for button labels and table headers.
8. **Brand names:** Preserve "MeshCore", "GitHub", "YouTube" capitalization.
## Testing Your Translation
1. Create your translation file: `locales/xx.json` (where `xx` is your language code)
2. Copy the structure from `en.json`
3. Translate all values, preserving all variables and HTML
4. Test in the application by setting the language
5. Check all pages for:
- Text overflow/truncation
- Proper variable interpolation
- Natural phrasing in context
## Getting Help
If you're unsure about the context of a translation key, check:
1. The "Context" column in this reference
2. The JavaScript files in `/src/meshcore_hub/web/static/js/spa/pages/`
3. Grep for the key: `grep -r "t('section.key')" src/`