From 2803d149bc8f340dbe5f82193c7929760f9321c4 Mon Sep 17 00:00:00 2001 From: Mehdi Date: Sat, 14 Feb 2026 18:10:51 +0000 Subject: [PATCH 1/3] feat(renderer): add i18n locale support and language selector --- package-lock.json | 81 ++++++++++- package.json | 2 + src/renderer/components/App.tsx | 14 +- src/renderer/components/ConfigTab.tsx | 103 +++++++------- src/renderer/components/DarkModeToggle.tsx | 6 +- src/renderer/components/FileTree.tsx | 19 ++- src/renderer/components/LanguageSelector.tsx | 42 ++++++ src/renderer/components/ProcessedTab.tsx | 38 +++--- src/renderer/components/SourceTab.tsx | 32 ++--- src/renderer/components/TabBar.tsx | 11 +- src/renderer/context/AppContext.tsx | 23 ++-- src/renderer/i18n/index.ts | 33 +++++ src/renderer/i18n/locales/de/common.json | 127 ++++++++++++++++++ src/renderer/i18n/locales/en/common.json | 127 ++++++++++++++++++ src/renderer/i18n/locales/es/common.json | 127 ++++++++++++++++++ src/renderer/i18n/locales/fr/common.json | 127 ++++++++++++++++++ src/renderer/i18n/settings.ts | 59 ++++++++ src/renderer/index.tsx | 1 + tests/catalog.md | 68 +++++----- tests/e2e/electron-process-flow.spec.ts | 19 +++ tests/setup.ts | 8 ++ .../components/language-selector.test.tsx | 39 ++++++ tests/unit/i18n/locales-parity.test.ts | 43 ++++++ 23 files changed, 1003 insertions(+), 146 deletions(-) create mode 100644 src/renderer/components/LanguageSelector.tsx create mode 100644 src/renderer/i18n/index.ts create mode 100644 src/renderer/i18n/locales/de/common.json create mode 100644 src/renderer/i18n/locales/en/common.json create mode 100644 src/renderer/i18n/locales/es/common.json create mode 100644 src/renderer/i18n/locales/fr/common.json create mode 100644 src/renderer/i18n/settings.ts create mode 100644 tests/unit/components/language-selector.test.tsx create mode 100644 tests/unit/i18n/locales-parity.test.ts diff --git a/package-lock.json b/package-lock.json index 785db75..0dcb467 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,13 @@ "clsx": "^2.1.0", "electron-store": "^11.0.2", "electron-updater": "^6.7.3", + "i18next": "^25.8.7", "minimatch": "^10.1.2", "path-browserify": "^1.0.1", "process": "^0.11.10", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-i18next": "^16.5.4", "react-router-dom": "^7.13.0", "tiktoken": "^1.0.11", "yaml": "^2.3.4" @@ -1935,7 +1937,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -10779,6 +10780,15 @@ "dev": true, "license": "MIT" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -10854,6 +10864,37 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "25.8.7", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.7.tgz", + "integrity": "sha512-ttxxc5+67S/0hhoeVdEgc1lRklZhdfcUSEPp1//uUG2NB88X3667gRsDar+ZWQFdysnOsnb32bcoMsa4mtzhkQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -16451,6 +16492,33 @@ "react": "^19.2.4" } }, + "node_modules/react-i18next": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.4.tgz", + "integrity": "sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.6.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -18709,7 +18777,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -18992,6 +19060,15 @@ "node": ">=0.6.0" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index f70d617..efa435c 100644 --- a/package.json +++ b/package.json @@ -133,11 +133,13 @@ "clsx": "^2.1.0", "electron-store": "^11.0.2", "electron-updater": "^6.7.3", + "i18next": "^25.8.7", "minimatch": "^10.1.2", "path-browserify": "^1.0.1", "process": "^0.11.10", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-i18next": "^16.5.4", "react-router-dom": "^7.13.0", "tiktoken": "^1.0.11", "yaml": "^2.3.4" diff --git a/src/renderer/components/App.tsx b/src/renderer/components/App.tsx index e4c4546..9baf3f1 100755 --- a/src/renderer/components/App.tsx +++ b/src/renderer/components/App.tsx @@ -1,16 +1,20 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { AppProvider, useApp } from '../context/AppContext'; import { DarkModeProvider } from '../context/DarkModeContext'; +import '../i18n'; import ConfigTab from './ConfigTab'; import DarkModeToggle from './DarkModeToggle'; +import LanguageSelector from './LanguageSelector'; import ProcessedTab from './ProcessedTab'; import SourceTab from './SourceTab'; import TabBar from './TabBar'; const ErrorBanner = () => { const { appError, dismissError } = useApp(); + const { t } = useTranslation(); if (!appError) return null; @@ -20,7 +24,7 @@ const ErrorBanner = () => { {providerTestResult && ( @@ -749,8 +753,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { )}

- Changes are automatically saved and will be applied when switching to the Source tab. - Token count estimates help with optimizing context for large repositories. + {t('config.autoSaveHint')}

@@ -761,16 +764,16 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { onClick={() => saveConfig(formState)} className='inline-flex items-center border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none' > - {isSaved ? '✓ Saved' : 'Save Config'} + {isSaved ? t('config.savedConfig') : t('config.saveConfig')}

- Only process files with these extensions + {t('config.includeExtensionsTitle')}

- One extension per line (include the dot) + {t('config.includeExtensionsHint')}