mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Fix translation completeness issues (#8472)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
f3d0ae3fbf
commit
3e5b16b348
@ -2,6 +2,7 @@
|
||||
echo "Running frontend checks..."
|
||||
cd frontend
|
||||
npm run check-unlocalized-strings
|
||||
npm run check-translation-completeness
|
||||
npx lint-staged
|
||||
|
||||
# Run backend pre-commit
|
||||
|
||||
@ -68,7 +68,8 @@
|
||||
"lint:fix": "eslint src --ext .ts,.tsx,.js --fix && prettier --write src/**/*.{ts,tsx}",
|
||||
"prepare": "cd .. && husky frontend/.husky",
|
||||
"typecheck": "react-router typegen && tsc",
|
||||
"check-unlocalized-strings": "node scripts/check-unlocalized-strings.cjs"
|
||||
"check-unlocalized-strings": "node scripts/check-unlocalized-strings.cjs",
|
||||
"check-translation-completeness": "node scripts/check-translation-completeness.cjs"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{ts,tsx,js}": [
|
||||
|
||||
88
frontend/scripts/check-translation-completeness.cjs
Executable file
88
frontend/scripts/check-translation-completeness.cjs
Executable file
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Pre-commit hook script to check for translation completeness
|
||||
* This script ensures that all translation keys have entries for all supported languages
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Load the translation file
|
||||
const translationJsonPath = path.join(__dirname, '../src/i18n/translation.json');
|
||||
const translationJson = require(translationJsonPath);
|
||||
|
||||
// Load the available languages from the i18n index file
|
||||
const i18nIndexPath = path.join(__dirname, '../src/i18n/index.ts');
|
||||
const i18nIndexContent = fs.readFileSync(i18nIndexPath, 'utf8');
|
||||
|
||||
// Extract the language codes from the AvailableLanguages array
|
||||
const languageCodesRegex = /\{ label: "[^"]+", value: "([^"]+)" \}/g;
|
||||
const supportedLanguageCodes = [];
|
||||
let match;
|
||||
|
||||
while ((match = languageCodesRegex.exec(i18nIndexContent)) !== null) {
|
||||
supportedLanguageCodes.push(match[1]);
|
||||
}
|
||||
|
||||
// Track missing and extra translations
|
||||
const missingTranslations = {};
|
||||
const extraLanguages = {};
|
||||
let hasErrors = false;
|
||||
|
||||
// Check each translation key
|
||||
Object.entries(translationJson).forEach(([key, translations]) => {
|
||||
// Get the languages available for this key
|
||||
const availableLanguages = Object.keys(translations);
|
||||
|
||||
// Find missing languages for this key
|
||||
const missing = supportedLanguageCodes.filter(
|
||||
(langCode) => !availableLanguages.includes(langCode)
|
||||
);
|
||||
|
||||
if (missing.length > 0) {
|
||||
missingTranslations[key] = missing;
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// Find extra languages for this key
|
||||
const extra = availableLanguages.filter(
|
||||
(langCode) => !supportedLanguageCodes.includes(langCode)
|
||||
);
|
||||
|
||||
if (extra.length > 0) {
|
||||
extraLanguages[key] = extra;
|
||||
hasErrors = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Generate detailed error message if there are missing translations
|
||||
if (Object.keys(missingTranslations).length > 0) {
|
||||
console.error('\x1b[31m%s\x1b[0m', 'ERROR: Missing translations detected');
|
||||
console.error(`Found ${Object.keys(missingTranslations).length} translation keys with missing languages:`);
|
||||
|
||||
Object.entries(missingTranslations).forEach(([key, langs]) => {
|
||||
console.error(`- Key "${key}" is missing translations for: ${langs.join(', ')}`);
|
||||
});
|
||||
|
||||
console.error('\nPlease add the missing translations before committing.');
|
||||
}
|
||||
|
||||
// Generate detailed error message if there are extra languages
|
||||
if (Object.keys(extraLanguages).length > 0) {
|
||||
console.error('\x1b[31m%s\x1b[0m', 'ERROR: Extra languages detected');
|
||||
console.error(`Found ${Object.keys(extraLanguages).length} translation keys with extra languages not in AvailableLanguages:`);
|
||||
|
||||
Object.entries(extraLanguages).forEach(([key, langs]) => {
|
||||
console.error(`- Key "${key}" has translations for unsupported languages: ${langs.join(', ')}`);
|
||||
});
|
||||
|
||||
console.error('\nPlease remove the extra languages before committing.');
|
||||
}
|
||||
|
||||
// Exit with error code if there are issues
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('\x1b[32m%s\x1b[0m', 'All translation keys have complete language coverage!');
|
||||
}
|
||||
@ -1069,7 +1069,6 @@
|
||||
"fr": "Copier dans le presse-papiers",
|
||||
"tr": "Panoya kopyala",
|
||||
"de": "In die Zwischenablage kopieren",
|
||||
"fa": "کپی به کلیپبورد",
|
||||
"uk": "Копіювати в буфер обміну"
|
||||
},
|
||||
"BUTTON$COPIED": {
|
||||
@ -1086,7 +1085,6 @@
|
||||
"fr": "Copié dans le presse-papiers",
|
||||
"tr": "Panoya kopyalandı",
|
||||
"de": "In die Zwischenablage kopiert",
|
||||
"fa": "در کلیپبورد کپی شد",
|
||||
"uk": "Copied to clipboard"
|
||||
},
|
||||
"APP$TITLE": {
|
||||
@ -4245,7 +4243,6 @@
|
||||
"ar": "أو",
|
||||
"no": "Eller",
|
||||
"tr": "veya",
|
||||
"fa": "یا",
|
||||
"uk": "Або"
|
||||
},
|
||||
"SUGGESTIONS$TEST_COVERAGE": {
|
||||
@ -4278,7 +4275,6 @@
|
||||
"ar": "دمج تلقائي لطلبات سحب Dependabot",
|
||||
"no": "Auto-flett Dependabot PRs",
|
||||
"tr": "Otomatik birleştirme",
|
||||
"fa": "ادغام خودکار درخواستهای Dependabot",
|
||||
"uk": "Автоматичне об'єднання Dependabot PR"
|
||||
},
|
||||
"CHAT_INTERFACE$AGENT_STOPPED_MESSAGE": {
|
||||
@ -5951,7 +5947,6 @@
|
||||
"pt": "Captura de tela do navegador",
|
||||
"es": "Captura de pantalla del navegador",
|
||||
"tr": "Tarayıcı ekran görüntüsü",
|
||||
"fa": "تصویر صفحه مرورگر",
|
||||
"uk": "Знімок екрана браузера"
|
||||
},
|
||||
"ERROR_TOAST$CLOSE_BUTTON_LABEL": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user