Skip to content

Conversation

IDisposable
Copy link
Contributor

@IDisposable IDisposable commented Oct 2, 2025

Localization

The browser/client frontend uses the paraglide-js plug-in from the inlang.com project to allow compile-time validated localization of all user-facing UI strings in the browser/client UI. This includes title, text, name, description, placeholder, label, aria-label, message attributes (such as confirmText, unit, badge, tag, or flag), HTML element text (such as <h?>, <span>, or <p> elements), notifications messages, and option label strings, etc.

We do not translate the console log messages, CSS class names, theme names, nor the various value strings (e.g. for value/label pair options), nor URL routes.

The localizations are stored in .json files in the ui/localizations/messages directory, with one language-per-file using the ISO 3166-1 alpha-2 country code (e.g. en for English, de for German, etc.)

m-function-matcher

The translations are extracted into language files (e.g. en.json for English) and then paraglide-js compiles them into helpers for use with the m-function-matcher. An example:

<SettingsPageHeader
   title={m.extensions_atx_power_control()}
   description={m.extensions_atx_power_control_description()}
/>

shakespere plug-in

If you enable the Sherlock plug-in, the localized text "tooltip" is shown in the VSCode editor after any localized text in the language you've selected for preview. In this image, it's the blue text at the end of the line :

Showing the translation preview

Process

Localizing a UI
  1. Locate a string that is visible to the end user on the client/browser

  2. Assign that string a "key" that reflects the logical meaning of the string in snake-case (look at existing localizations for examples), for example if there's a string This is a test on the thing edit page it would be "thing_edit_this_is_a_test"

    "thing_edit_this_is_a_test": "This is a test",
  3. Add the key and string to the en.json like this:

    • Note if the string has replacement parameters (line a user-entered name), the syntax for the localized string has { } around the replacement token (e.g. This is your name: {name}). An complex example:
    {m.mount_button_showing_results({
       from: indexOfFirstFile + 1,
       to: Math.min(indexOfLastFile, onStorageFiles.length),
       total: onStorageFiles.length
    })}
    
  4. Save the en.json file and execute npm run i18n to resort the language files, validate the translations, and create the m-functions

  5. Edit the .tsx file and replace the string with the calls to the new m-function which will be the key-string you chose in snake-case. For example This is a test in thing edit page turns into m.thing_edit_this_is_a_test()

    • Note if the string has a replacement token, supply that to the m-function, for example for the literal I will call you {name}, use m.profile_i_will_call_you({ name: edit.value })
  6. When all your strings are extracted, run npm run i18n:machine-translate to get a first-stab at the translations for the other supported languages. Make sure you use an LLM (you can use aifiesta to use multiple LLMs) or a translator of some form to back-translate each new machine-generation in each language to ensure those terms translate reasonably.

Adding a new language

  1. Get the ISO 3166-1 alpha-2 country code (for example AT for Austria)
  2. Create a new file in the ui/localization/messages directory (example at.json)
  3. Add the new country code to the ui/localizations/settings.json file in both the "locales" and the "languageTags" section (inlang and Sherlock aren't exactly current to each other, so we need it in both places).
  4. That file also declares the baseLocale/sourceLanguageTag which is "en" because this project started out in English. Do NOT change that.
  5. Run npm run i18n:machine-translate to do an initial pass at localizing all existing messages to the new language.
    • Note you will get an error DB has been closed, ignore that message, we're not using a database.
    • Note you likely will get errors while running this command due to rate limits and such (it uses anonymous Google Translate). Just keep running the command over and over... it'll translate a bunch each time until it says Machine translate complete

Other notes

  • Run npm run i18n:validate to ensure that language files and settings are well-formed.
  • Run npm run i18n:find-excess to look for extra keys in other language files that have been deleted from the master-list in en.json.
  • Run npm run i18n:find-dupes to look for multiple keys in en.json that have the same translated value (this is normal)
  • Run npm run i18n:find-unused to look for keys in en.json that are not referenced in the UI anywhere.
    • Note there are a few that are not currently used, only concern yourself with ones you obsoleted.
  • Run npm run i18n:audit to do all the above checks.
  • Using inlang CLI to support the npm commands.
  • You can install the Sherlock VS Code extension in your devcontainer.

@IDisposable IDisposable force-pushed the feat/add-localization branch 3 times, most recently from cabdfe4 to d81a497 Compare October 2, 2025 02:00
@IDisposable IDisposable mentioned this pull request Oct 2, 2025
@adamshiervani adamshiervani added this to the 0.5.1 milestone Oct 3, 2025
@adamshiervani adamshiervani moved this to In progress in JetKVM Oct 3, 2025
@IDisposable IDisposable force-pushed the feat/add-localization branch 4 times, most recently from f383397 to cd9a55d Compare October 4, 2025 00:28
@IDisposable IDisposable marked this pull request as ready for review October 4, 2025 05:46
@IDisposable IDisposable requested a review from Copilot October 4, 2025 05:46
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces internationalization (i18n) support to the JetKVM UI using the inlang paraglide-js localization framework. The implementation enables multilingual support with translations for 9 languages and includes a comprehensive setup for managing localized strings throughout the React frontend.

  • Added inlang paraglide-js as the localization framework with configuration for 9 languages (en, da, de, es, fr, it, nb, sv, zh)
  • Replaced hardcoded UI strings with localized message functions across multiple components
  • Updated build configuration and TypeScript paths to support the new localization structure

Reviewed Changes

Copilot reviewed 30 out of 31 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ui/vite.config.ts Added paraglide Vite plugin configuration for localization build integration
ui/tsconfig.json Updated with new path aliases and compiler options for localization support
ui/tsconfig.node.json Simplified configuration by removing redundant options
ui/package.json Added inlang dependencies and updated scripts to include localization compilation
ui/src/ components and routes Replaced hardcoded strings with localized message functions
ui/localization/ Added complete localization setup with message files for 9 languages
ui/index.html Minor formatting and path corrections
ui/eslint.config.cjs Updated import resolution mapping for new path aliases
Files not reviewed (1)
  • ui/package-lock.json: Language not supported

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@IDisposable IDisposable force-pushed the feat/add-localization branch from cd9a55d to 7ea1942 Compare October 7, 2025 17:14
@IDisposable IDisposable force-pushed the feat/add-localization branch 2 times, most recently from 9aa9b44 to 985b53c Compare October 7, 2025 23:47
@IDisposable IDisposable requested a review from Copilot October 10, 2025 22:37
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 89 out of 108 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • ui/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)

ui/src/routes/devices.$id.tsx:1

  • Corrected 'server' to 'serve' in comment"
import { lazy, useCallback, useEffect, useMemo, useRef, useState } from "react";

ui/src/hooks/hidRpc.ts:1

  • [nitpick] Variable declaration moved inside the loop when it could be declared outside for better readability. Consider moving the offset calculation before the macroBinary creation."
import { hidKeyBufferSize, KeyboardLedState, KeysDownState } from "./stores";

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@IDisposable IDisposable force-pushed the feat/add-localization branch 4 times, most recently from a61b707 to 5363baa Compare October 14, 2025 11:41
@IDisposable IDisposable changed the title Start localization with inlang paraglide-js Localize the client/browser UI with inlang paraglide-js Oct 14, 2025
@IDisposable IDisposable requested a review from Copilot October 14, 2025 18:08
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 106 out of 124 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • ui/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)

ui/src/routes/devices.$id.settings.macros.edit.tsx:1

  • The 'text' prop appears to be missing its value. This should likely be text={m.macros_delete_macro()}.
import { useState, useEffect } from "react";

ui/src/routes/devices.$id.settings.macros.edit.tsx:1

  • Line 99 shows incomplete 'text' prop assignment. The line should include the complete prop assignment but appears to be cut off or malformed.
import { useState, useEffect } from "react";

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@IDisposable
Copy link
Contributor Author

I think this is ready for testing, the Update page is still borked, but will fix that ASAP. You can use a cookie-editor to switch languages until someone weighs in on how we want to present the UI language switcher on the client

@IDisposable IDisposable force-pushed the feat/add-localization branch 6 times, most recently from f4b3053 to 83d544f Compare October 15, 2025 15:10
@IDisposable IDisposable requested a review from Copilot October 15, 2025 15:10
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 109 out of 126 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • ui/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)

ui/src/hooks/hidRpc.ts:1

  • Moving the offset calculation outside the loop and incrementing it manually could lead to incorrect offsets if the loop logic changes or if steps have variable sizes. The original approach of calculating offset = 6 + i * 9 was more robust and less error-prone.
import { hidKeyBufferSize, KeyboardLedState, KeysDownState } from "./stores";

ui/src/routes/devices.$id.mount.tsx:1

  • The URL validation logic now runs in useEffect on every URL change, which could cause unnecessary re-renders. The original inline validation approach urlRef.current?.validity.valid was more efficient as it only checked validity when needed during render or form submission.
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@IDisposable IDisposable force-pushed the feat/add-localization branch from 83d544f to beb7bde Compare October 15, 2025 23:02
@IDisposable IDisposable force-pushed the feat/add-localization branch 6 times, most recently from bcdeae4 to c6d4fff Compare October 17, 2025 05:04
@IDisposable
Copy link
Contributor Author

I've got the documentation created and all the remaining translations I missed (I think?) done.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 114 out of 132 changed files in this pull request and generated 6 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Remove the temporary directory after extracting buildkit
Localize the extension popovers.
Update package and fix tsconfig.json
Expand development directory guide
Move messages under localization
Popovers and sidebar
Update Chinese translations
Accidentally lost the changes that @ym provided, brought them back
File formatting pass
Localized all components, hooks, providers, hooks
Localize all pages except Settings
Bump packages
Settings Access page
Settings local auth page
Fix ref lint warning
Settings Advanced page
Fix UI lint warnings there were a bunch of ref and useEffect violations.
Settings appearance page
Settings general pages
Settings hardware page
Settings keyboard page
Settings macros pages
Settings mouse page
Settings page
Settings video page
Settings network page
Fix compilation issues
Ran machine translate
Use getLocale for date, relative time, and money formatting
Fix eslint
Delete unused messages
Added setting to choose locale
Merged in dev hotfix
Fix update status rendering
@IDisposable IDisposable force-pushed the feat/add-localization branch from 9e9311c to 3391fa6 Compare October 18, 2025 01:16
@IDisposable
Copy link
Contributor Author

Rebased to pull in the new sleep mode settings.

Thanks CoPilot!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

3 participants