Skip to content

feat: Add i18n infrastructure with Chinese and English translations#2314

Open
norulers wants to merge 2 commits intobluerobotics:masterfrom
norulers:feature/i18n-support
Open

feat: Add i18n infrastructure with Chinese and English translations#2314
norulers wants to merge 2 commits intobluerobotics:masterfrom
norulers:feature/i18n-support

Conversation

@norulers
Copy link

@norulers norulers commented Jan 7, 2026

  • Add vue-i18n@9 for internationalization support
  • Create translation files (en.json, zh.json) with UI strings
  • Add LanguageSwitcher component for language selection
  • Integrate i18n plugin into main app
  • Add Vuetify Chinese locale support (zhHans)
  • Add language switcher to main menu

@CLAassistant
Copy link

CLAassistant commented Jan 7, 2026

CLA assistant check
All committers have signed the CLA.

@ES-Alexander
Copy link
Contributor

Hi @norulers, thanks for the contribution!
I've been wanting this since I raised #409, and I'm super happy to see someone working on it! ❤️

We can't merge this until you've signed the contributor license agreement (see the comment above).

I haven't had a chance to review yet, but thanks for taking the time to work on the changes Rafael requested. In future it would be preferable to force push over the original pull request instead of making a new one, but that's ok this time :-)

@rafaellehmkuhl
Copy link
Member

@norulers could you check on the tests that are making the CI to fail? They are basically about linting and missing JSDocs.

Copy link
Contributor

@ES-Alexander ES-Alexander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some great work so far on getting things translation-ready - thanks!

Some requests focused on consistent application of "common" translations, keeping the commit history logical, and avoiding value: value translation mappings:

"language": "Language",
"default": "default"
},
"usernameDialog": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think all these translations should be added in the first infrastructure-focused commit, without the corresponding code variables to actually use them.

Maybe just include the "common" section in the first commit, so the files exist, and move the rest to the second commit (where they're first used)?

const randomMissionName = coolMissionNames.random()
const randomMissionName = computed(() => {
const englishName = coolMissionNames.random()
return englishName ? t(`missionNames["${englishName}"]`) : ''
Copy link
Contributor

@ES-Alexander ES-Alexander Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given how these are used, I think it likely makes more sense to implement a per-language set of names, adjectives, etc in the funny-name library, rather than maintaining a 1:1 translation with the English ones. That also provides some extra scope for translators to have locale-specific jokes and wordplay.

This would match the "list of values" approach used for the splash screen startup messages.

return description.slice(0, 128) + '...'

// Try to translate the description
const translationKey = `tools.mavlink.messageDescriptions.${description}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should use the messageName for the end of the key, as that's shorter and very unlikely to change, whereas the descriptions may change from time to time.

Comment on lines 861 to 877
"System ID of target system": "System ID of target system",
"Component ID of target component": "Component ID of target component",
"Command ID": "Command ID",
"Confirmation value (0: first transmission, 1-255: confirmations)": "Confirmation value (0: first transmission, 1-255: confirmations)",
"Parameter 1": "Parameter 1",
"Parameter 2": "Parameter 2",
"Parameter 3": "Parameter 3",
"Parameter 4": "Parameter 4",
"Parameter 5": "Parameter 5",
"Parameter 6": "Parameter 6",
"Parameter 7": "Parameter 7",
"Coordinate frame": "Coordinate frame",
"Current sequence number": "Current sequence number",
"Autocontinue bit": "Autocontinue bit",
"Latitude": "Latitude",
"Longitude": "Longitude",
"Altitude": "Altitude"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using values directly as the keys likely indicates a translation is happening in the wrong place. In this case these should be associated with the src/libs/actions/mavlink-message-actions-message-definitions.ts file instead of the vue file that displays them.

Comment on lines 97 to 102
const translateFieldDescription = (description: string): string => {
const translationKey = `configuration.actions.mavlinkAction.fieldDescriptions.${description}`
const translated = t(translationKey)
// If translation key doesn't exist, return original description
return translated === translationKey ? description : translated
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this translation should happen at the definition, in src/libs/actions/mavlink-message-actions-message-definitions.ts, rather than here. That would also allow using variable names for the translation keys, rather than just duplicating the descriptions.

<v-card-actions>
<div class="flex justify-between items-center pa-2 w-full h-full" style="color: rgba(255, 255, 255, 0.5)">
<v-btn @click="closeActionDialog">Cancel</v-btn>
<v-btn @click="closeActionDialog">{{ $t('configuration.actions.mavlinkAction.cancel') }}</v-btn>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<v-btn @click="closeActionDialog">{{ $t('configuration.actions.mavlinkAction.cancel') }}</v-btn>
<v-btn @click="closeActionDialog">{{ $t('common.cancel') }}</v-btn>

<v-btn @click="closeActionDialog">{{ $t('configuration.actions.mavlinkAction.cancel') }}</v-btn>
<div class="flex gap-x-10">
<v-btn @click="resetNewAction">Reset</v-btn>
<v-btn @click="resetNewAction">{{ $t('configuration.actions.mavlinkAction.reset') }}</v-btn>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<v-btn @click="resetNewAction">{{ $t('configuration.actions.mavlinkAction.reset') }}</v-btn>
<v-btn @click="resetNewAction">{{ $t('common.reset') }}</v-btn>

<v-btn @click="resetNewAction">{{ $t('configuration.actions.mavlinkAction.reset') }}</v-btn>
<v-btn :disabled="!isFormValid" class="text-white" @click="saveActionConfig">
{{ editMode ? 'Save' : 'Create' }}
{{ editMode ? $t('configuration.actions.mavlinkAction.save') : $t('configuration.actions.mavlinkAction.create') }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{{ editMode ? $t('configuration.actions.mavlinkAction.save') : $t('configuration.actions.mavlinkAction.create') }}
{{ editMode ? $t('common.save') : $t('configuration.actions.mavlinkAction.create') }}

Comment on lines 896 to 897
"cancel": "Cancel",
"save": "Save",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"common" fields should not be replicated in internal ones.

"reset": "Reset",
"noMessagesFound": "No messages found",
"messageValues": "Message Values",
"messageDescriptions": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are coming from a submodule, I don't think they should be included in the English translations - it can instead rely on the fallback behaviour of directly using the imported value given an undefined translation.

@norulers norulers force-pushed the feature/i18n-support branch from 8980922 to af729b0 Compare January 8, 2026 01:45
norulers added a commit to norulers/cockpit that referenced this pull request Jan 8, 2026
- Use common.cancel/save/reset across all components instead of duplicates
- Refactor MAVLink field descriptions to use variable names as translation keys
  - Change 'description' to 'descriptionKey' in message definitions
  - Use field names (target_system, command, etc) as keys instead of values
- Update MAVLink message descriptions to use messageName as translation key
  - Change from description values to message names (HEARTBEAT, BATTERY_STATUS, etc)
- Remove English MAVLink descriptions from en.json (rely on library fallback)
- Update zh.json to use message names as keys for all MAVLink translations
- Add JSDoc comments to vite.config.ts plugin functions

Addresses ES-Alexander architectural feedback in PR bluerobotics#2314
@norulers norulers force-pushed the feature/i18n-support branch 2 times, most recently from 7c13450 to 2bc9053 Compare January 10, 2026 08:40
@norulers norulers requested a review from ES-Alexander January 12, 2026 00:38
@ES-Alexander
Copy link
Contributor

ES-Alexander commented Jan 14, 2026

This PR is continually growing in a way that makes it very challenging to review. If you are not "done" yet then please "Convert to draft" (in the bottom right corner, above the "add a comment" button) for now, then mark it as "Ready for review" again once you are finished changing it.

More generally:

  1. If there are various sections/files that are included / not included in the translation it would be helpful for them to be listed out as a checklist in the PR description, so we can more easily tell what has been completed
    • If there are sections or files that get intentionally left out, it would be helpful to include a reason for them (e.g. "no text", "too complicated for now", "needs discussion", etc)
    • If there are parts you are unsure about (e.g. for what the translation key should be, or whether something can be included as a list vs needing direct translations), please feel free to ask, either as comments here or in Support multiple languages for the interface #409
  2. We likely need to have a discussion about the order the translations are included in the translation file, so it is straightforward to determine where a new variable should be added
    • What process/reasoning are you following currently?
  3. Given this PR adds a new functionality, before we review it I would recommend rebasing it into just three commits, where the first one adds the base i18n infrastructure, then the second one converts the inline strings to variables for translation, with the English translation file so things keep working, and the third one adds the Chinese translations of those variables and the ability to switch to Chinese
    • Alternatively the first two or last two suggestions could be combined into a single commit, if that makes more sense with how the functionality works
    • At the moment there are mixed infrastructure and translation changes spread between several commits, which makes it hard to follow and review, and rolling back a single commit may break multiple commits after it (which is then messy/difficult to maintain)
  4. Our software team does not currently include anyone well-versed in Chinese language, but it is still important to us to verify the translation quality / accuracy
    • This is unlikely to significantly affect your development efforts, but I'm just flagging it because it will likely cause the review process to take a longer time period while we figure out a process for that which makes sense
  5. In order for this to be sustainable, we also need to make sure this is maintainable, and extendable to other languages
    • Do you happen to have experience with Crowdin or other similar communal translation platforms?
    • I'm not yet familiar with what kind of files they require for translation updates to work effectively (your current approach may already be fine - I'm not sure)
      • Ideally it would also be possible for developers and/or users to flag a translation as needing improvement/clarification, but I'm unsure whether that kind of functionality is already built in to such platforms
    • I'm also unfamiliar with best practices for ensuring an updated or new variable has reasonable fallback behaviour (presumably without manually programming that behaviour into every translation call), rather than crashing the application / causing lots of errors
      • What happens at the moment if the translation keys are not fully aligned? If necessary we can add a CI test for that for subsequent pull requests, but I think ideally failing to find a key for a language should fall back to the default language (English), and there should be some way of flagging to prospective translators that a key does not yet have a valid translation

I am aware that updates to the main codebase also require rebasing and updating this PR, but we need to understand and agree on the approach before it can be reviewed properly, and if the commits are functionally separated that should be easier to do as well. Once a shared understanding is established we can do an initial review of the infrastructure, and once that seems fine we can set aside a "no merging" time period while the final translations get updated and reviewed, after which we can merge this PR and switch to maintenance mode (where changes trigger translation requests from relevant contributors).

@norulers norulers marked this pull request as draft January 15, 2026 01:03
@norulers norulers force-pushed the feature/i18n-support branch from 00a52e5 to 68e573e Compare January 15, 2026 01:22
@norulers norulers marked this pull request as ready for review January 15, 2026 01:40
@norulers norulers force-pushed the feature/i18n-support branch 2 times, most recently from 0ee096d to 0a80d57 Compare January 15, 2026 02:02
@ES-Alexander
Copy link
Contributor

Noting that the last two commits you've added should be rebased in to follow the convention of the earlier ones (as outlined in point 3 of my previous comment).

It would be helpful if you could respond to at least point 2 of my previous comment, so we can try to understand (and discuss) the logic being used for where a given string gets added in the translation file.

@norulers
Copy link
Author

Noting that the last two commits you've added should be rebased in to follow the convention of the earlier ones (as outlined in point 3 of my previous comment).

It would be helpful if you could respond to at least point 2 of my previous comment, so we can try to understand (and discuss) the logic being used for where a given string gets added in the translation file.

Thanks for the feedback! Regarding the organization logic for the translation files (point 2):

Current Organization Structure
The translation keys in en.json and zh.json follow this hierarchy:

common - Universal UI elements used across multiple components (buttons, actions, status messages)
errors - All error messages
success - Success notifications
info - Informational messages
warning - Warning messages
Component/Feature sections - Organized alphabetically by component/feature name:
about, armSafety, architectureWarning, configuration, electronMenu, map, menu, mission, tutorial, update, vehicle, video, widgets, etc.
Logic for Adding New Translations
When adding a new translation:

Check if it's a common element (cancel, save, ok, etc.) → add to common
Check if it's an error/success/warning/info message → add to respective section
Component-specific translations → add under the component's section (create if doesn't exist)
New component sections are added alphabetically among existing component sections
Within each component section, keys are organized by UI hierarchy (e.g., dialogs → forms → buttons)
Example
For a new "Settings" component with a save button:

Use common.save for the button (already exists)
Create settings: { title: "Settings", ... } section in alphabetical position
Does this structure make sense? I'm open to adjustments if there's a preferred convention I should follow.

I'll also work on rebasing the last commits as suggested in point 3.

@norulers norulers force-pushed the feature/i18n-support branch 4 times, most recently from 702404a to 64e0af9 Compare January 28, 2026 09:13
@rafaellehmkuhl
Copy link
Member

rafaellehmkuhl commented Jan 28, 2026

@norulers @ES-Alexander my suggestion to make it easier to find the translations (as well as automate that in the future) would be to use the component path itself as the location inside the translation file:

Example translations for ‎src/components/configuration/ActionLinkConfig.vue:

...
"components": {
  {
    configurations: {
      "ActionLinkConfig.vue": {
        "noFasterThan": "No faster than",
        "changesPerSecond": "Changes per second",
        ...
      },
      ...
    },
    ...
  },
  ...
}

What do you think?

@ES-Alexander
Copy link
Contributor

@rafaellehmkuhl, that's likely more verbose, but it does seem like it would make it easier to support naive automation approaches in future, and reduces ambiguity over where something should go (without us needing to extensively consider and discuss a rule-based standard). I'm unsure whether that has any kind of meaningful impact on performance (due to extra layers to traverse) 🤷‍♂️ . I imagine the memory usage would be a bit higher, but likely not significantly given the data is all text.

Either way, I do think it makes sense to at least have a separated "common" (or "standard"?) section for the interface terms that are intended to be re-used in multiple places (save, cancel, continue, etc), to avoid the possibility of inconsistent translations for the same term. If all such interface components get pulled out into a consistent library or something then I suppose that would be even better, but in the absence of that being implemented in the code I'd want at least that one shared translation section to minimise duplicates.

@rafaellehmkuhl
Copy link
Member

@rafaellehmkuhl, that's likely more verbose, but it does seem like it would make it easier to support naive automation approaches in future, and reduces ambiguity over where something should go (without us needing to extensively consider and discuss a rule-based standard). I'm unsure whether that has any kind of meaningful impact on performance (due to extra layers to traverse) 🤷‍♂️ . I imagine the memory usage would be a bit higher, but likely not significantly given the data is all text.

Either way, I do think it makes sense to at least have a separated "common" (or "standard"?) section for the interface terms that are intended to be re-used in multiple places (save, cancel, continue, etc), to avoid the possibility of inconsistent translations for the same term. If all such interface components get pulled out into a consistent library or something then I suppose that would be even better, but in the absence of that being implemented in the code I'd want at least that one shared translation section to minimise duplicates.

Agree. Common should stay.

And performance-wise the longer paths won't make any difference.

@norulers norulers force-pushed the feature/i18n-support branch from f3063a1 to 2c467f3 Compare February 3, 2026 02:10
@norulers norulers force-pushed the feature/i18n-support branch 9 times, most recently from cbd4e54 to 465591e Compare February 7, 2026 02:25
@norulers norulers force-pushed the feature/i18n-support branch 2 times, most recently from 1c86479 to bca591a Compare February 27, 2026 06:53
- Add vue-i18n dependency and i18n plugin
- Create English and Chinese translation files (en.json, zh.json)
- Add Vuetify Chinese locale support
- Configure i18n with fallback locale and locale persistence
@norulers norulers force-pushed the feature/i18n-support branch from bca591a to 9ef60af Compare February 28, 2026 02:36
- Import and use vue-i18n in all components and views
- Add LanguageSwitcher component for runtime language switching
- Replace all hardcoded strings with translation keys
- Add getChatMessage to electron menu for language switching support
- Fix ConfigurationLogsView.vue conflict with upstream monaco editor changes
- Use common.* keys for shared translations (cancel, save, reset)
- Add JSDoc to all new i18n helper functions
@norulers norulers force-pushed the feature/i18n-support branch from 9ef60af to df6ba4d Compare February 28, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants