# 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 (A–Z) | Alphabetical by display name |
| `sort.name_za` | Name (Z–A) | 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 (A–Z) | Alphabetical by node display name |
| `sort.node_za` | Node (Z–A) | 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 (A–Z) | Alphabetical by message type |
| `sort.type_za` | Type (Z–A) | Reverse alphabetical by message type |
| `sort.from_az` | From (A–Z) | Alphabetical by sender name |
| `sort.from_za` | From (Z–A) | Reverse alphabetical by sender name |
| `sort.message_az` | Message (A–Z) | Alphabetical by message text |
| `sort.message_za` | Message (Z–A) | 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/`
