frontend: i18n: Add UI support for RTL languages#5695
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: mahmoodalisha The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
9e8cc1f to
1652a37
Compare
illume
left a comment
There was a problem hiding this comment.
Thanks for these changes.
Can you please have a look at the git commits to see if they meet the contribution guidelines? We use a Linux kernel style of git commits. See the contributing guide for general context, and please see previous git commits with git log for examples.
Commits that need attention
chore: Update translations— Missingarea: descriptionprefix — e.g.frontend: HomeButton: Fix so it navigates to homeorbackend: config: Add enable-dynamic-clusters flag.
Commit guidelines
- Use atomic commits focused on a single change.
- Use the title format
<area>: <Description of changes>— description must start with a capital letter. - Keep the title under 72 characters (soft requirement).
- Explain the intention and why the change is needed.
- Make commit titles meaningful and describe what changed.
- Do not add code that a later commit rewrites; squash or reorder commits instead.
- Do not include
Fixes #NNin commit messages.
Good examples:
frontend: HomeButton: Fix so it navigates to homebackend: config: Add enable-dynamic-clusters flag
There was a problem hiding this comment.
Pull request overview
This PR adds initial RTL language support to the frontend i18n flow and introduces Arabic, Hebrew, and Urdu locale bundles.
Changes:
- Adds RTL/LTR direction metadata to supported languages and new RTL language options.
- Updates theme/document direction handling when the active language changes.
- Adds new locale JSON files for Arabic, Hebrew, and Urdu.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/src/i18n/config.ts |
Adds language metadata and RTL language entries. |
frontend/src/i18n/ThemeProviderNexti18n.tsx |
Applies document and theme direction based on i18n language. |
frontend/src/i18n/LocaleSelect/LocaleSelect.tsx |
Reads labels from the new supported language metadata. |
frontend/src/i18n/locales/ar/translation.json |
Adds Arabic translation namespace. |
frontend/src/i18n/locales/ar/glossary.json |
Adds Arabic glossary namespace. |
frontend/src/i18n/locales/ar/app.json |
Adds Arabic app namespace. |
frontend/src/i18n/locales/he/translation.json |
Adds Hebrew translation namespace. |
frontend/src/i18n/locales/he/glossary.json |
Adds Hebrew glossary namespace. |
frontend/src/i18n/locales/he/app.json |
Adds Hebrew app namespace. |
frontend/src/i18n/locales/ur/translation.json |
Adds Urdu translation namespace. |
frontend/src/i18n/locales/ur/glossary.json |
Adds Urdu glossary namespace. |
frontend/src/i18n/locales/ur/app.json |
Adds Urdu app namespace. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 12 comments.
Comments suppressed due to low confidence (12)
frontend/src/i18n/locales/ar/translation.json:293
- Arabic plural categories zero/two/few/many are left empty here, so those counts fall back to English because empty translations are treated as missing. This makes event age text partially English for common Arabic counts such as 0, 2, 3-10, and 11-99.
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_zero": "",
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_one": "{{ eventDate }} ({{ count }} مرة منذ {{ firstEventDate }})",
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_two": "",
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_few": "",
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_many": "",
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_other": "{{ eventDate }} ({{ count }} مرات منذ {{ firstEventDate }})",
frontend/src/i18n/locales/ar/translation.json:392
- Arabic plural categories zero/two/few/many are empty for this label. Since empty values fall back to English, the environment-variable expander will show English text for common counts instead of Arabic.
"Show all environment variables (+{{count}} more)_zero": "",
"Show all environment variables (+{{count}} more)_one": "عرض جميع متغيرات البيئة (+{{count}} إضافية)",
"Show all environment variables (+{{count}} more)_two": "",
"Show all environment variables (+{{count}} more)_few": "",
"Show all environment variables (+{{count}} more)_many": "",
"Show all environment variables (+{{count}} more)_other": "عرض جميع متغيرات البيئة (+{{count}} إضافية)",
frontend/src/i18n/locales/ar/translation.json:425
- Arabic plural categories zero/two/few/many are empty for this label. Since empty values fall back to English, the labels expander will show English text for common counts instead of Arabic.
"Show all labels (+{{count}} more)_zero": "",
"Show all labels (+{{count}} more)_one": "عرض جميع التسميات (+{{count}} إضافية)",
"Show all labels (+{{count}} more)_two": "",
"Show all labels (+{{count}} more)_few": "",
"Show all labels (+{{count}} more)_many": "",
"Show all labels (+{{count}} more)_other": "عرض جميع التسميات (+{{count}} إضافية)",
frontend/src/i18n/locales/ar/translation.json:508
- Arabic plural categories zero/two/few/many are empty for the selected-files message. Because empty translations fall back to English, selecting 0, 2, 3-10, or 11-99 files will render English text in the Arabic UI.
"{{count}} files selected_zero": "",
"{{count}} files selected_one": "تم تحديد {{count}} ملف",
"{{count}} files selected_two": "",
"{{count}} files selected_few": "",
"{{count}} files selected_many": "",
"{{count}} files selected_other": "تم تحديد {{count}} ملفات",
frontend/src/i18n/locales/ar/translation.json:773
- Arabic plural categories zero/two/few/many are empty for the loaded-resources label. With empty translations falling back to English, common Arabic counts will render English text instead of Arabic.
"Loaded Resources ({{count}})_zero": "",
"Loaded Resources ({{count}})_one": "الموارد المحملة ({{count}})",
"Loaded Resources ({{count}})_two": "",
"Loaded Resources ({{count}})_few": "",
"Loaded Resources ({{count}})_many": "",
"Loaded Resources ({{count}})_other": "الموارد المحملة ({{count}})",
frontend/src/i18n/locales/he/translation.json:287
- Hebrew's count=2 plural entry is empty here. Because empty translations fall back to English, the event-age message for exactly two occurrences will render English in the Hebrew UI.
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_one": "{{ eventDate }} ({{ count }} times since {{ firstEventDate }})",
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_two": "",
"{{ eventDate }} ({{ count }} times since {{ firstEventDate }})_other": "{{ eventDate }} ({{ count }} times since {{ firstEventDate }})",
frontend/src/i18n/locales/he/translation.json:383
- Hebrew's count=2 plural entry is empty here. Because empty translations fall back to English, the environment-variable expander will render English for exactly two hidden variables.
"Show all environment variables (+{{count}} more)_one": "Show all environment variables (+{{count}} more)",
"Show all environment variables (+{{count}} more)_two": "",
"Show all environment variables (+{{count}} more)_other": "Show all environment variables (+{{count}} more)",
frontend/src/i18n/locales/he/translation.json:413
- Hebrew's count=2 plural entry is empty here. Because empty translations fall back to English, the labels expander will render English for exactly two hidden labels.
"Show all labels (+{{count}} more)_one": "Show all labels (+{{count}} more)",
"Show all labels (+{{count}} more)_two": "",
"Show all labels (+{{count}} more)_other": "Show all labels (+{{count}} more)",
frontend/src/i18n/locales/he/translation.json:493
- Hebrew's count=2 plural entry is empty here. Because empty translations fall back to English, the selected-files message will render English when exactly two files are selected.
"{{count}} files selected_one": "{{count}} files selected",
"{{count}} files selected_two": "",
"{{count}} files selected_other": "{{count}} files selected",
frontend/src/i18n/locales/he/translation.json:755
- Hebrew's count=2 plural entry is empty here. Because empty translations fall back to English, the loaded-resources label will render English for exactly two resources.
"Loaded Resources ({{count}})_one": "Loaded Resources ({{count}})",
"Loaded Resources ({{count}})_two": "",
"Loaded Resources ({{count}})_other": "Loaded Resources ({{count}})",
frontend/src/i18n/locales/ar/translation.json:574
- These newly added Arabic diagnostic translations are empty. Since empty values are treated as missing, users selecting Arabic will see English fallback text for these diagnostic messages instead of Arabic localization.
"Init container": "",
"Ephemeral container": "",
"Restart count: {{ restartCount }}": "",
"Exit code: {{ exitCode }}": "",
"Signal: {{ signal }}": "",
frontend/src/i18n/locales/he/translation.json:559
- These newly added Hebrew diagnostic translations are empty. Since empty values are treated as missing, users selecting Hebrew will see English fallback text for these diagnostic messages instead of Hebrew localization.
"Init container": "",
"Ephemeral container": "",
"Restart count: {{ restartCount }}": "",
"Exit code: {{ exitCode }}": "",
"Signal: {{ signal }}": "",
illume
left a comment
There was a problem hiding this comment.
Thanks for this PR.
A few of the commits don't quite follow the project guidelines. We use Linux kernel style for git commits — have a look at the contributing guide and previous commits with git log.
Commits that need attention
chore: Update translations— Missingarea: descriptionprefix — e.g.frontend: HomeButton: Fix so it navigates to homeorbackend: config: Add enable-dynamic-clusters flag.
Commit guidelines
- Use atomic commits focused on a single change.
- Use the title format
<area>: <Description of changes>— description must start with a capital letter. - Keep the title under 72 characters (soft requirement).
- Explain the intention and why the change is needed.
- Make commit titles meaningful and describe what changed.
- Do not add code that a later commit rewrites; squash or reorder commits instead.
- Do not include
Fixes #NNin commit messages.
Good examples:
frontend: HomeButton: Fix so it navigates to homebackend: config: Add enable-dynamic-clusters flag
The open review comments from Copilot still need attention — can you have a look? Once addressed, please mark them as resolved.
|
btw. This is really great!! It might be that we disable the languages that you don't know in here until we found someone who can review it for us. Do you speak any of these languages well? |
Yes @illume , I am proficient in Arabic, Urdu. I can read, write, and speak Arabic and Urdu |
1d1d296 to
27a7d97
Compare
|
great! For Hebrew we could either:
|
27a7d97 to
f5da63e
Compare
illume
left a comment
There was a problem hiding this comment.
Thanks. Looks like the tests are failing now.
Can you please have a look?
yes !! solving them |
|
@illume the frontend-i18n check CI job was failing due to missing upstream translation keys. I pulled the latest main, re ran npm run i18n, and pushed the synced locale files and have fixed all the issues raised by copilot in my latest commits. I changed arEG to arSA and imported config file inside ThemeProviderNexti18n.tsx to actually use the ltr/rtl metadata |
illume
left a comment
There was a problem hiding this comment.
Thanks for this PR.
the PR has a merge-main commit; please rebase against main to keep the history clean.
Why this matters
Merge commits from main make the PR history harder to review. Please rebase your branch on top of the latest main instead, then update the PR with the rebased commits.
A few of the commits don't quite follow the project guidelines. We use Linux kernel style for git commits — have a look at the contributing guide and previous commits with git log.
Commits that need attention
chore: Update translations— Missingarea: descriptionprefix — e.g.frontend: HomeButton: Fix so it navigates to homeorbackend: config: Add enable-dynamic-clusters flag.
Commit guidelines
- Use atomic commits focused on a single change.
- Use the title format
<area>: <Description of changes>— description must start with a capital letter. - Keep the title under 72 characters (soft requirement).
- Explain the intention and why the change is needed.
- Make commit titles meaningful and describe what changed.
- Do not add code that a later commit rewrites; squash or reorder commits instead.
- Do not include
Fixes #NNin commit messages.
Good examples:
frontend: HomeButton: Fix so it navigates to homebackend: config: Add enable-dynamic-clusters flag
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (2)
frontend/src/i18n/ThemeProviderNexti18n.tsx:121
- The direction lookup uses the raw language code from i18next, but browser detection/requested languages commonly include regions such as
ar-SA,ur-PK, orhe-ILwhilesupportedLanguagesonly has base keys. In those cases i18next can resolve translations to the base locale, but this lookup falls back toltr, so RTL users with regional browser locales will not get RTL direction.
const dir = supportedLanguages[lng]?.dir || 'ltr';
document.documentElement.lang = lng;
document.documentElement.dir = dir;
document.body.dir = dir;
frontend/src/i18n/ThemeProviderNexti18n.tsx:149
- This theme direction lookup has the same raw-language-code issue as the document direction update: regional RTL locales such as
ar-SAresolve to the Arabic resources but are not keys insupportedLanguages, so the MUI theme direction staysltr. Normalize to the resolved/base language before reading the direction metadata.
direction: supportedLanguages[lang]?.dir || 'ltr',
256cfaa to
bf83c78
Compare
| const theme = createTheme( | ||
| { | ||
| ...props.theme, | ||
| // Use the local config metadata for the theme direction too | ||
| direction: supportedLanguages[lang]?.dir || 'ltr', | ||
| }, | ||
| getLocale(lang) |
| "Authentication": "Authentication", | ||
| "Please paste your authentication token.": "Please paste your authentication token.", | ||
| "ID token": "ID token", | ||
| "Check out how to generate a<1>service account token</1>.": "Check out how to generate a<1>service account token</1>.", |
| "Kubernetes Version": "Kubernetes Version", | ||
| "Cluster": "Cluster", | ||
| "Cluster selector": "Cluster selector", | ||
| "Namespace": "Namespace", | ||
| "Git Commit": "Git Commit", | ||
| "Memory": "Memory", | ||
| "CPU": "CPU", | ||
| "Pods": "Pods", | ||
| "Nodes": "Nodes", | ||
| "Events": "Events", | ||
| "Node": "Node", | ||
| "Namespaces": "Namespaces", | ||
| "Filter": "Filter", | ||
| "a8r.io Metadata": "a8r.io Metadata", | ||
| "toggle field visibility": "toggle field visibility", | ||
| "Condition": "Condition", | ||
| "Last Transition": "Last Transition", | ||
| "Last Update": "Last Update", | ||
| "Container ID": "Container ID", | ||
| "Image Pull Policy": "Image Pull Policy", | ||
| "Image": "Image", | ||
| "Args": "Args", | ||
| "Command": "Command", | ||
| "Environment": "Environment", | ||
| "Liveness Probes": "Liveness Probes", | ||
| "Ports": "Ports", | ||
| "Volume Mounts": "Volume Mounts", | ||
| "Containers": "Containers", | ||
| "Container Spec": "Container Spec", | ||
| "Ephemeral Containers": "Ephemeral Containers", | ||
| "Owner": "Owner", | ||
| "Container": "Container", | ||
| "Config Maps": "Config Maps", | ||
| "Definition": "Definition", | ||
| "CRD: {{ crdName }}": "CRD: {{ crdName }}", | ||
| "Scope": "Scope", |
| "Failed to fetch plugin info": "Failed to fetch plugin info", | ||
| "An error occurred while fetching plugin info from {{ URL }}.": "An error occurred while fetching plugin info from {{ URL }}.", | ||
| "Yes": "Yes", | ||
| "No": "No", | ||
| "Plugin Installation": "Plugin Installation", | ||
| "Do you want to install the plugin \"{{ pluginName }}\"?": "Do you want to install the plugin \"{{ pluginName }}\"?", | ||
| "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?", | ||
| "About": "About", | ||
| "Quit": "Quit", | ||
| "Select All": "Select All", | ||
| "Delete": "Delete", | ||
| "Services": "Services", | ||
| "Hide": "Hide", | ||
| "Hide Others": "Hide Others", | ||
| "Show All": "Show All", | ||
| "File": "File", | ||
| "Close": "Close", | ||
| "Edit": "Edit", | ||
| "Cut": "Cut", | ||
| "Copy": "Copy", | ||
| "Paste": "Paste", | ||
| "Paste and Match Style": "Paste and Match Style", | ||
| "Speech": "Speech", | ||
| "Start Speaking": "Start Speaking", | ||
| "Stop Speaking": "Stop Speaking", | ||
| "View": "View", | ||
| "Toggle Developer Tools": "Toggle Developer Tools", | ||
| "Reset Zoom": "Reset Zoom", | ||
| "Zoom In": "Zoom In", | ||
| "Zoom Out": "Zoom Out", | ||
| "Toggle Fullscreen": "Toggle Fullscreen", | ||
| "Navigate": "Navigate", | ||
| "Reload": "Reload", | ||
| "Go to Home": "Go to Home", | ||
| "Go Back": "Go Back", | ||
| "Go Forward": "Go Forward", | ||
| "Window": "Window", | ||
| "Minimize": "Minimize", | ||
| "Bring All to Front": "Bring All to Front", | ||
| "Help": "Help", | ||
| "Documentation": "Documentation", | ||
| "Open an Issue": "Open an Issue", | ||
| "Another process is running": "Another process is running", | ||
| "Looks like another process is already running. Continue by terminating that process automatically, or quit?": "Looks like another process is already running. Continue by terminating that process automatically, or quit?", | ||
| "Continue": "Continue", | ||
| "Failed to quit the other running process": "Failed to quit the other running process", | ||
| "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.", | ||
| "No available ports": "No available ports", | ||
| "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?", | ||
| "Terminate and Retry": "Terminate and Retry", | ||
| "Failed to start": "Failed to start", | ||
| "Could not start the server even after terminating existing processes.": "Could not start the server even after terminating existing processes.", | ||
| "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.", | ||
| "Invalid URL": "Invalid URL", | ||
| "Application opened with an invalid URL: {{ url }}": "Application opened with an invalid URL: {{ url }}" |



Summary
This PR introduces right-to-left (RTL) language support to Headlamp and adds new localization support for:
The UI now dynamically switches layout direction based on the selected language.
Related Issue
Fixes #ISSUE_NUMBER
Changes
Added new locale directories
frontend/src/i18n/locales/ar
frontend/src/i18n/locales/he
frontend/src/i18n/locales/ur
Updated language configuration
Modified: frontend/src/i18n/config.ts
Changes include:
Added helper:
export const isRTL = (lang: string) =>
supportedLanguages[lang]?.dir === 'rtl';
Updated locale switching logic
Modified: frontend/src/i18n/LocaleSelect/LocaleSelect.tsx
Updated language change handling:
const changeLng = (event: SelectChangeEvent) => {
const lng = event.target.value as string;
i18n.changeLanguage(lng);
};
Result
RTL languages now render correctly
Layout direction updates dynamically based on selected locale
Existing LTR languages remain unaffected
Improved internationalization support
Testing
Tested language switching with:
Verified:
Screenshots
Notes for the Reviewer
Updates i18n config to handle ltr / rtl directions, verified RTL layout switching and existing LTR behavior