diff --git a/AGENTS.md b/AGENTS.md index 11e93e7..54b00fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,20 +6,27 @@ Lightweight rules for automated agents and contributors in this repository. - Keep changes focused and minimal. - Do not make unrelated refactors. +- Keep project code structure with clear scoping for each module; do not mix up code. +- Planning docs are in `docs/plan` and are never committed. They can live in backlog or local files. ## Tests and Quality - Before proposing merge-ready changes, run: - `npm run lint` - `npm test -- --runInBand` + - Ensure all linting warnings and errors are cleared. - If UI behavior/layout is changed, also run: - `npm run qa:screenshot` - Do not mark work complete while required CI checks are failing. +- Read PR comments, evaluate them, and fact-check. - Use `tests/catalog.md` as the source of truth for test targets and use cases. +- Locally before pushing, run `make sonar` and fix all issues. +- After pushing a PR, check the SonarCloud report again and clear any reported issues. ## `.env` Policy (Local-Only) -- `.env` in this repo is for local development bootstrap only. +- `.env` in this repo is for local development bootstrap only, and we commit it because it is not sensitive. +- Ignore agent review warnings for `vault-agent-env.hcl` hardcoded env. - Keep `.env` tracked; do not remove/rename it. - Do not over-engineer or heavily refactor `.env` for non-local use. - Never commit secrets/tokens in `.env`. @@ -30,3 +37,6 @@ Lightweight rules for automated agents and contributors in this repository. - Treat `.env` changes as sensitive even when local-only. - Ensure `.env` updates are minimal, intentional, and documented in PR notes. - Reject any hardcoded secret, token, or credential exposure in code, docs, or PR text. +- Check agent reviews in context; if a tool is used only in dev, evaluate benefit and risk for the change. +- Focus feedback on our core app features. +- Use feature flags if a new feature requires additional steps, and enable it only in dev mode for testing. 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..e9d84d8 100755 --- a/src/renderer/components/App.tsx +++ b/src/renderer/components/App.tsx @@ -1,26 +1,33 @@ 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; + const translatedMessage = appError.translationKey + ? t(appError.translationKey, appError.translationOptions) + : appError.message; return (
- {appError.message} + {translatedMessage}
@@ -468,7 +471,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { {/* File Filtering section */}

- File Filtering + {t('config.fileFilteringTitle')}

@@ -484,7 +487,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='use-custom-includes' className='ml-2 block text-sm text-gray-700 dark:text-gray-300' > - Filter by file extensions + {t('config.filterByExtensions')}
@@ -500,7 +503,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='use-custom-excludes' className='ml-2 block text-sm text-gray-700 dark:text-gray-300' > - Use exclude patterns + {t('config.useExcludePatterns')}
@@ -516,7 +519,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='use-gitignore' className='ml-2 block text-sm text-gray-700 dark:text-gray-300' > - Apply .gitignore rules + {t('config.applyGitignoreRules')} @@ -532,7 +535,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='enable-secret-scanning' className='ml-2 block text-sm text-gray-700 dark:text-gray-300' > - Scan content for secrets + {t('config.scanSecrets')} @@ -548,7 +551,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='exclude-suspicious-files' className='ml-2 block text-sm text-gray-700 dark:text-gray-300' > - Exclude suspicious files + {t('config.excludeSuspiciousFiles')} @@ -557,7 +560,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { {/* Output Formatting section */}

- Output Formatting + {t('config.outputFormattingTitle')}

@@ -573,7 +576,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='include-tree-view' className='ml-2 block text-sm text-gray-700 dark:text-gray-300' > - Include file tree in output + {t('config.includeFileTree')}
@@ -589,7 +592,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='show-token-count' className='ml-2 block text-sm text-gray-700 dark:text-gray-300' > - Display token counts + {t('config.displayTokenCounts')}
@@ -598,16 +601,17 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='export-format' className='mb-1 block text-sm text-gray-700 dark:text-gray-300' > - Export format + {t('config.exportFormat')} @@ -617,10 +621,10 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { {aiSurfacesEnabled && (

- Provider Setup Assistant + {t('config.providerSetupTitle')}

- Configure a model provider and run a connection test before saving. + {t('config.providerSetupDescription')}

@@ -629,7 +633,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='provider-id' className='mb-1 block text-sm text-gray-700 dark:text-gray-300' > - Provider + {t('config.provider')} { setField('providerModel', event.target.value); resetProviderFeedback(); @@ -677,7 +681,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { htmlFor='provider-base-url' className='mb-1 block text-sm text-gray-700 dark:text-gray-300' > - Base URL (optional) + {t('config.baseUrlOptional')} { htmlFor='provider-api-key' className='mb-1 block text-sm text-gray-700 dark:text-gray-300' > - API key (optional for Ollama) + {t('config.apiKeyOptionalOllama')} { setField('providerApiKey', event.target.value); resetProviderFeedback(); @@ -722,7 +726,7 @@ const ConfigTab = ({ configContent, onConfigChange }: ConfigTabProps) => { disabled={isTestingProviderConnection} className='inline-flex items-center border border-transparent bg-indigo-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-400 focus:outline-none' > - {isTestingProviderConnection ? 'Testing...' : 'Test Connection'} + {isTestingProviderConnection ? t('config.testing') : t('config.testConnection')} {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')}