Skip to content

Commit 77399a4

Browse files
authored
Merge pull request #4222 from headlamp-k8s/i18n-improvements
I18n improvements
2 parents 9b25624 + 7e4c305 commit 77399a4

File tree

20 files changed

+5147
-51
lines changed

20 files changed

+5147
-51
lines changed

docs/development/i18n/contributing.md

Lines changed: 164 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,116 @@ title: Contributing to Internationalization
33
sidebar_label: Contributing
44
---
55

6-
This section introduces some concepts for contributing translations and is
7-
especially important when submitting a new language.
6+
Welcome! This guide will help you contribute translations to Headlamp.
87

9-
**Important:** If you add a new language, keep in mind that while all
8+
## Quick Start
9+
10+
### Where to Edit Translations
11+
12+
**⚠️ IMPORTANT:** All translations should be edited in:
13+
```
14+
frontend/src/i18n/locales/
15+
```
16+
17+
This is the **single source of truth** for all translations. Do not edit translation files in other locations (like `plugins/headlamp-plugin/src/i18n/locales/`) as they are auto-generated during the build process.
18+
19+
### Translation File Structure
20+
21+
Each language has its own directory with three JSON files:
22+
23+
```
24+
locales/
25+
├── en/ # English (reference language)
26+
│ ├── translation.json # Main translations
27+
│ ├── glossary.json # Kubernetes technical terms
28+
│ └── app.json # Desktop app specific strings
29+
├── de/ # German
30+
│ ├── translation.json
31+
│ ├── glossary.json
32+
│ └── app.json
33+
├── fr/ # French
34+
...
35+
```
36+
37+
**Important:** When adding a new language, keep in mind that while all
1038
the specific Kubernetes components' names are translatable, not all of them
1139
will have a corresponding name in your language. Please refer to the
1240
[Kubernetes localized docs](https://kubernetes.io/docs/home/) in your
1341
language (if they exist) to understand which components should
1442
be translated and which should be left in their original form.
1543

44+
## Helper Commands
45+
46+
We provide several commands to make translation easier:
47+
48+
### Check Translation Status
49+
50+
See which languages need translation work:
51+
52+
```bash
53+
npm run i18n:status
54+
```
55+
56+
This shows a colorful table with:
57+
- Translation completion percentage for each language
58+
- Number of translated vs. missing strings
59+
- Visual progress bars
60+
61+
### List Translation Files
62+
63+
See all translation files with their completion status:
64+
65+
```bash
66+
# List all languages
67+
npm run i18n:list
68+
69+
# List specific language (note the -- before language code)
70+
npm run i18n:list -- de
71+
```
72+
73+
This shows:
74+
- All translation files for each language
75+
- Completion percentage for each file
76+
- Missing files (if any)
77+
- File paths
78+
79+
### Extract Empty Translations
80+
81+
Extract untranslated strings to a separate file for easier translation:
82+
83+
```bash
84+
npm run i18n:extract -- frontend/src/i18n/locales/de/translation.json
85+
```
86+
87+
This creates a file (e.g., `translation_empty.json`) containing only the empty translations. You can:
88+
1. Translate the strings in this smaller file
89+
2. Use the copy command (below) to merge them back
90+
91+
Optional: Specify output filename:
92+
```bash
93+
npm run i18n:extract -- frontend/src/i18n/locales/de/translation.json my_translations.json
94+
```
95+
96+
### Copy Translations Between Files
97+
98+
Copy translations from one file to another:
99+
100+
```bash
101+
# Basic usage - only copy missing translations
102+
npm run i18n:copy -- source.json dest.json
103+
104+
# Force overwrite all translations (including non-empty ones)
105+
npm run i18n:copy -- source.json dest.json --force
106+
107+
# Copy all keys from source, even if they don't exist in destination
108+
npm run i18n:copy -- source.json dest.json --all
109+
```
110+
111+
This is useful for:
112+
- Merging completed translations back from an extracted file
113+
- Copying translations between language files
114+
- Updating translations selectively
115+
16116
## Namespaces
17117

18118
We have only two main [i18next namespaces](https://www.i18next.com/principles/namespaces):
@@ -86,39 +186,72 @@ Here's an example of using date formatting:
86186
});
87187
```
88188

89-
## Adding a new language
189+
## Translation Workflow
190+
191+
### For Updating Existing Languages
90192

91-
Create a folder using the locale code in:
92-
`frontend/src/i18n/locales/`
193+
1. **Check translation status:**
194+
```bash
195+
npm run i18n:status
196+
```
93197

94-
Then run `npm run i18n`. This command parses the translatable strings in
95-
the project and creates the corresponding catalog files.
198+
2. **Open the JSON files directly** in `frontend/src/i18n/locales/<lang>/`
199+
- `translation.json` - UI strings
200+
- `glossary.json` - Kubernetes technical terms
201+
- `app.json` - App-specific strings
202+
203+
3. **Find empty strings** (these need translation):
204+
```json
205+
{
206+
"Some key": "", ← Needs translation
207+
"Another key": "Translated text" ← Already done
208+
}
209+
```
210+
211+
4. **Add translations** for empty strings:
212+
```json
213+
{
214+
"Some key": "Your translation here",
215+
"Another key": "Translated text"
216+
}
217+
```
218+
219+
5. **Verify your work:**
220+
```bash
221+
npm run i18n:status
222+
```
223+
224+
6. **Submit a pull request** with your changes
225+
226+
### For Adding a New Language
227+
228+
1. **Create the language directory:**
229+
```bash
230+
mkdir frontend/src/i18n/locales/<lang-code>
231+
```
232+
233+
Language codes should follow the [ISO 639-1 standard](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
234+
235+
2. **Generate translation files:**
236+
```bash
237+
# From the project root
238+
npm run i18n
239+
```
240+
241+
This will create empty translation files for your new language.
242+
243+
3. **Follow the update workflow above** to translate the strings
96244

97245
Integrated components may need to be adjusted (MaterialUI/Monaco etc).
98246

99-
## Translating missing strings
100-
101-
Technical development happens more frequently than translations. Thus, chances
102-
are that developers introduce new strings that need to be translated and will
103-
be stored as empty strings (defaulting to English) in the translation files.
104-
105-
In order to more easily spot and translate the missing strings, we have two CLI
106-
tools:
107-
108-
- _extract-empty-translations.js_: This script (in ./frontend/src/i18n/tools/)
109-
will extract the strings without a corresponding translation from the translation
110-
files, and copy them into a new file.
111-
E.g. `$ node copy-empty-translations.js ../locales/de/translation.json` will
112-
by default create a `../locales/de/translation_empty.json`. This file can be
113-
used to translate the strings in a more isolated way.
114-
- _copy-translations.js_: This script (in ./frontend/src/i18n/tools/)
115-
by default copies any existing translations from one source translation file to
116-
a target one, if the same key is not translated in the destination file.
117-
E.g. `$ node copy-translations.js ../locales/de/translation_no_longer_empty.json ../locales/de/translation.json` will
118-
copy any new translations from the file given as the first argument to the one
119-
given as the second argument, if the same key is not translated in the second.
120-
There are some options to this script, which can be seen by running it with the
121-
`--help` flag.
247+
## Deprecated Tools
248+
249+
**⚠️ DEPRECATED:** The old CLI tools in `frontend/src/i18n/tools/` are deprecated and should no longer be used:
250+
251+
- `extract-empty-translations.js` → Use `npm run i18n:extract` instead
252+
- `copy-translations.js` → Use `npm run i18n:copy` instead
253+
254+
The new i18n tool (see Helper Commands above) provides the same functionality with better error handling and a more consistent interface.
122255

123256
## Material UI
124257

frontend/src/i18n/README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,30 @@
22

33
We try and keep all the i18n related things in here.
44

5-
See the [i18n docs](../../docs/development/i18n.md) for full details.
5+
## For Translators
6+
7+
See the [i18n contributing guide](../../../docs/development/i18n/contributing.md) for a complete guide on how to contribute translations.
8+
9+
### Quick Commands
10+
11+
```bash
12+
npm run i18n:status # Show translation status for all languages
13+
npm run i18n:list # List all translation files with completion status
14+
npm run i18n:list -- de # List translation files for German with completion status
15+
```
16+
17+
**Note:** These commands are run from the project root directory.
18+
19+
## Translation File Location
20+
21+
**⚠️ IMPORTANT:** All translations should be edited in:
22+
23+
```
24+
frontend/src/i18n/locales/
25+
```
26+
27+
This is the **single source of truth** for all Headlamp translations. Do not edit translation files in other locations (like `plugins/headlamp-plugin/src/i18n/locales/`) as they are auto-generated.
28+
29+
## For Developers
30+
31+
See the [i18n docs](../../docs/development/i18n.md) for full technical details.

frontend/src/i18n/locales/en/app.json

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"Failed to fetch plugin info": "",
3-
"An error occurred while fetching plugin info from {{ URL }}.": "",
4-
"Yes": "",
5-
"No": "",
6-
"Plugin Installation": "",
7-
"Do you want to install the plugin \"{{ pluginName }}\"?": "",
8-
"You are about to install a plugin from: {{ url }}\nDo you want to proceed?": "",
2+
"Failed to fetch plugin info": "Failed to fetch plugin info",
3+
"An error occurred while fetching plugin info from {{ URL }}.": "An error occurred while fetching plugin info from {{ URL }}.",
4+
"Yes": "Yes",
5+
"No": "No",
6+
"Plugin Installation": "Plugin Installation",
7+
"Do you want to install the plugin \"{{ pluginName }}\"?": "Do you want to install the plugin \"{{ pluginName }}\"?",
8+
"You are about to install a plugin from: {{ url }}\nDo you want to proceed?": "You are about to install a plugin from: {{ url }}\nDo you want to proceed?",
99
"About": "About",
1010
"Quit": "Quit",
1111
"Select All": "Select All",
@@ -30,11 +30,11 @@
3030
"Zoom In": "Zoom In",
3131
"Zoom Out": "Zoom Out",
3232
"Toggle Fullscreen": "Toggle Fullscreen",
33-
"Navigate": "",
33+
"Navigate": "Navigate",
3434
"Reload": "Reload",
35-
"Go to Home": "",
36-
"Go Back": "",
37-
"Go Forward": "",
35+
"Go to Home": "Go to Home",
36+
"Go Back": "Go Back",
37+
"Go Forward": "Go Forward",
3838
"Window": "Window",
3939
"Minimize": "Minimize",
4040
"Bring All to Front": "Bring All to Front",
@@ -46,12 +46,12 @@
4646
"Continue": "Continue",
4747
"Failed to quit the other running process": "Failed to quit the other running process",
4848
"Could not quit the other running process, PIDs: {{ process_list }}. Please stop that process and relaunch the app.": "Could not quit the other running process, PIDs: {{ process_list }}. Please stop that process and relaunch the app.",
49-
"No available ports": "",
50-
"Could not find an available port. There are processes running on ports {{startPort}}-{{endPort}}. Terminate these processes and retry?": "",
51-
"Terminate and Retry": "",
52-
"Failed to start": "",
53-
"Could not start the server even after terminating existing processes.": "",
54-
"Could not find an available port in the range {{startPort}}-{{endPort}}. Please free up a port and try again.": "",
49+
"No available ports": "No available ports",
50+
"Could not find an available port. There are processes running on ports {{startPort}}-{{endPort}}. Terminate these processes and retry?": "Could not find an available port. There are processes running on ports {{startPort}}-{{endPort}}. Terminate these processes and retry?",
51+
"Terminate and Retry": "Terminate and Retry",
52+
"Failed to start": "Failed to start",
53+
"Could not start the server even after terminating existing processes.": "Could not start the server even after terminating existing processes.",
54+
"Could not find an available port in the range {{startPort}}-{{endPort}}. Please free up a port and try again.": "Could not find an available port in the range {{startPort}}-{{endPort}}. Please free up a port and try again.",
5555
"Invalid URL": "Invalid URL",
5656
"Application opened with an invalid URL: {{ url }}": "Application opened with an invalid URL: {{ url }}"
5757
}

frontend/src/i18n/tools/copy-translations.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17+
// ⚠️ DEPRECATED: This script is deprecated. Use the new i18n tool instead:
18+
// npm run i18n:copy -- <srcFile> <destFile> [--force] [--all]
19+
//
1720
// This script copies the translations from a translations file to an existing file.
1821
// By default, it 1) only overwrites translations that are missing or are empty, in the destination file,
1922
// and 2) only copies translations that are in the destination file.

frontend/src/i18n/tools/extract-empty-translations.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17+
// ⚠️ DEPRECATED: This script is deprecated. Use the new i18n tool instead:
18+
// npm run i18n:extract -- <translationsFile> [outputFile]
19+
//
1720
// This script copies the empty translations from a translations file to a new file. So they can
1821
// be easily spotted and translated.
1922
//

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"npm": ">=10.0.0"
1414
},
1515
"scripts": {
16-
"install:all": "npm run frontend:install && npm run backend:build && npm run app:install",
16+
"install:all": "npm run frontend:install && npm run backend:build && npm run app:install && npm run tools:install",
1717
"install:frontend": "npm run frontend:install",
1818
"install:backend": "npm run backend:build",
1919
"install:app": "npm run app:install",
@@ -47,8 +47,12 @@
4747
"frontend:start": "cd frontend && npm start",
4848
"frontend:storybook": "cd frontend && npm run storybook",
4949
"docs": "cd frontend && npm run build-typedoc",
50-
"i18n": "cd frontend && npm run i18n",
50+
"i18n": "cd frontend && npm run i18n && cd ../app && npm run i18n",
5151
"i18n:check": "cd frontend && npm run i18n -- --fail-on-update",
52+
"i18n:status": "cd tools/i18n && npm run status",
53+
"i18n:list": "cd tools/i18n && npm run list",
54+
"i18n:extract": "cd tools/i18n && npm run extract --",
55+
"i18n:copy": "cd tools/i18n && npm run copy --",
5256
"app:install": "cd app && npm install",
5357
"app:build": "npm run frontend:build && cd app && node ./scripts/setup-plugins.js && npm run build",
5458
"app:build:dir": "npm run frontend:build && cd app && node ./scripts/setup-plugins.js && npm run build -- --dir",
@@ -67,6 +71,7 @@
6771
"start:app": "npm run app:start",
6872
"start:backend": "npm run backend:build && npm run backend:start",
6973
"start:frontend": "npm run frontend:start",
74+
"tools:install": "cd tools && cd i18n && npm install && cd .. && cd releaser && npm install",
7075
"plugins:test": "cd plugins/headlamp-plugin && npm install && ./test-headlamp-plugin.js && ./test-plugins-examples.sh && cd ../pluginctl/src && npm install && node ./plugin-management.e2e.js && cd .. && npx jest src/multi-plugin-management.test.js && npx jest src/plugin-management.test.js && npm run test",
7176
"image:build": "sh -c 'docker buildx build --pull --platform=local -t ghcr.io/headlamp-k8s/headlamp:$(git describe --tags --always --dirty) -f Dockerfile .'",
7277
"image:verify-image-digests": "node tools/verify-image-digests.js"

0 commit comments

Comments
 (0)