jan/scripts/find-missing-translations.js
Sam Hoang Van c32dd092d0
Enhance i18n and add missing i18n for all component (#5314)
* Refactor translation imports and update text for localization across settings and system monitor routes

- Changed translation import from 'react-i18next' to '@/i18n/react-i18next-compat' in multiple files.
- Updated various text strings to use translation keys for better localization support in:
  - Local API Server settings
  - MCP Servers settings
  - Privacy settings
  - Provider settings
  - Shortcuts settings
  - System Monitor
  - Thread details
- Ensured consistent use of translation keys for all user-facing text.

Update web-app/src/routes/settings/appearance.tsx

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

Update web-app/src/routes/settings/appearance.tsx

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

Update web-app/src/locales/vn/settings.json

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

Update web-app/src/containers/dialogs/DeleteMCPServerConfirm.tsx

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

Update web-app/src/locales/id/common.json

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Add Chinese (Simplified and Traditional) localization files for various components

- Created `tools.json`, `updater.json`, `assistants.json`, `chat.json`, `common.json`, `hub.json`, `logs.json`, `mcp-servers.json`, `provider.json`, `providers.json`, `settings.json`, `setup.json`, `system-monitor.json`, `tool-approval.json` in both `zh-CN` and `zh-TW` locales.
- Added translations for tool approval, updater notifications, assistant management, chat interface, common UI elements, hub interactions, logging messages, MCP server configurations, provider management, settings options, setup instructions, and system monitoring.

* Refactor localization strings for improved clarity and consistency in English, Indonesian, and Vietnamese settings files

* Fix missing key and reword

* fix pr comment

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-06-20 15:33:54 +07:00

253 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Script to find missing translations in locale files for Jan
*
* Usage:
* node scripts/find-missing-translations.js [options]
*
* Options:
* --locale=<locale> Only check a specific locale (e.g. --locale=id)
* --file=<file> Only check a specific file (e.g. --file=common.json)
* --help Show this help message
*/
const fs = require("fs")
const path = require("path")
// Process command line arguments
const args = process.argv.slice(2).reduce(
(acc, arg) => {
if (arg === "--help") {
acc.help = true
} else if (arg.startsWith("--locale=")) {
acc.locale = arg.split("=")[1]
} else if (arg.startsWith("--file=")) {
acc.file = arg.split("=")[1]
}
return acc
},
{}
)
// Show help if requested
if (args.help) {
console.log(`
Find Missing Translations for Jan
A utility script to identify missing translations across locale files.
Compares non-English locale files to the English ones to find any missing keys.
Usage:
node scripts/find-missing-translations.js [options]
Options:
--locale=<locale> Only check a specific locale (e.g. --locale=id)
--file=<file> Only check a specific file (e.g. --file=common.json)
--help Show this help message
Output:
- Generates a report of missing translations for the web-app
`)
process.exit(0)
}
// Path to the locales directory
const LOCALES_DIR = path.join(__dirname, "../web-app/src/locales")
// Recursively find all keys in an object
function findKeys(obj, parentKey = "") {
let keys = []
for (const [key, value] of Object.entries(obj)) {
const currentKey = parentKey ? `${parentKey}.${key}` : key
if (typeof value === "object" && value !== null) {
// If value is an object, recurse
keys = [...keys, ...findKeys(value, currentKey)]
} else {
// If value is a primitive, add the key
keys.push(currentKey)
}
}
return keys
}
// Get value at a dotted path in an object
function getValueAtPath(obj, path) {
const parts = path.split(".")
let current = obj
for (const part of parts) {
if (current === undefined || current === null) {
return undefined
}
current = current[part]
}
return current
}
// Function to check translations
function checkTranslations() {
// Get all locale directories (or filter to the specified locale)
const allLocales = fs.readdirSync(LOCALES_DIR).filter((item) => {
const stats = fs.statSync(path.join(LOCALES_DIR, item))
return stats.isDirectory() && item !== "en" // Exclude English as it's our source
})
// Filter to the specified locale if provided
const locales = args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales
if (args.locale && locales.length === 0) {
console.error(`Error: Locale '${args.locale}' not found in ${LOCALES_DIR}`)
process.exit(1)
}
console.log(`Checking ${locales.length} non-English locale(s): ${locales.join(", ")}`)
// Get all English JSON files
const englishDir = path.join(LOCALES_DIR, "en")
let englishFiles = fs.readdirSync(englishDir).filter((file) => file.endsWith(".json") && !file.startsWith("."))
// Filter to the specified file if provided
if (args.file) {
if (!englishFiles.includes(args.file)) {
console.error(`Error: File '${args.file}' not found in ${englishDir}`)
process.exit(1)
}
englishFiles = englishFiles.filter((file) => file === args.file)
}
// Load file contents
let englishFileContents
try {
englishFileContents = englishFiles.map((file) => ({
name: file,
content: JSON.parse(fs.readFileSync(path.join(englishDir, file), "utf8")),
}))
} catch (e) {
console.error(`Error: File '${englishDir}' is not a valid JSON file`)
process.exit(1)
}
console.log(
`Checking ${englishFileContents.length} translation file(s): ${englishFileContents.map((f) => f.name).join(", ")}`
)
// Results object to store missing translations
const missingTranslations = {}
// For each locale, check for missing translations
for (const locale of locales) {
missingTranslations[locale] = {}
for (const { name, content: englishContent } of englishFileContents) {
const localeFilePath = path.join(LOCALES_DIR, locale, name)
// Check if the file exists in the locale
if (!fs.existsSync(localeFilePath)) {
missingTranslations[locale][name] = { file: "File is missing entirely" }
continue
}
// Load the locale file
let localeContent
try {
localeContent = JSON.parse(fs.readFileSync(localeFilePath, "utf8"))
} catch (e) {
console.error(`Error: File '${localeFilePath}' is not a valid JSON file`)
process.exit(1)
}
// Find all keys in the English file
const englishKeys = findKeys(englishContent)
// Check for missing keys in the locale file
const missingKeys = []
for (const key of englishKeys) {
const englishValue = getValueAtPath(englishContent, key)
const localeValue = getValueAtPath(localeContent, key)
if (localeValue === undefined) {
missingKeys.push({
key,
englishValue,
})
}
}
if (missingKeys.length > 0) {
missingTranslations[locale][name] = missingKeys
}
}
}
return outputResults(missingTranslations)
}
// Function to output results
function outputResults(missingTranslations) {
let hasMissingTranslations = false
console.log(`\nMissing Translations Report:\n`)
for (const [locale, files] of Object.entries(missingTranslations)) {
if (Object.keys(files).length === 0) {
console.log(`${locale}: No missing translations`)
continue
}
hasMissingTranslations = true
console.log(`📝 ${locale}:`)
for (const [fileName, missingItems] of Object.entries(files)) {
if (missingItems.file) {
console.log(` - ${fileName}: ${missingItems.file}`)
continue
}
console.log(` - ${fileName}: ${missingItems.length} missing translations`)
for (const { key, englishValue } of missingItems) {
console.log(` ${key}: "${englishValue}"`)
}
}
console.log("")
}
return hasMissingTranslations
}
// Main function to find missing translations
function findMissingTranslations() {
try {
console.log("Starting translation check for Jan web-app...")
const hasMissingTranslations = checkTranslations()
// Summary
if (!hasMissingTranslations) {
console.log("\n✅ All translations are complete!")
} else {
console.log("\n✏ To add missing translations:")
console.log("1. Add the missing keys to the corresponding locale files")
console.log("2. Translate the English values to the appropriate language")
console.log("3. You can use yq commands to update JSON files efficiently:")
console.log(" yq -i '.namespace.key = \"Translation\"' web-app/src/locales/<locale>/<file>.json")
console.log("4. Run this script again to verify all translations are complete")
// Exit with error code to fail CI checks
process.exit(1)
}
} catch (error) {
console.error("Error:", error.message)
console.error(error.stack)
process.exit(1)
}
}
// Run the main function
findMissingTranslations()