mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-06 17:02:59 +02:00
f8c2a7bb40
- 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
504 lines
21 KiB
Markdown
504 lines
21 KiB
Markdown
# 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/`
|