diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e51630..6baa4ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,6 +94,12 @@ You can add a new language to Scrum Helper for your own use or contribute it to 1. **Create a Locale Folder** - Go to `src/_locales`. - Create a new folder named with the [ISO language code](https://developer.chrome.com/docs/extensions/reference/i18n/#localeTable) (e.g., `it` for Italian, `fr` for French). + - **Important:** Use canonical locale codes to avoid duplicates: + - For Chinese (Simplified): use `zh_CN` (not `zh_Hans` or `zh-CN`) + - For Chinese (Traditional): use `zh_TW` (not `zh_Hant` or `zh-TW`) + - For Portuguese (Brazil): use `pt_BR` (not `pt-BR`) + - For English: use `en` (not `en_US` or `en-US`) + - Run `npm run validate:locales` to check for duplicate locale codes before submitting. 2. **Add a `messages.json` File** - Copy the `messages.json` from `src/_locales/en/messages.json` or any other language as a template. diff --git a/package.json b/package.json index 7f533e8..b31e2b3 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ }, "scripts": { "format": "biome format --write", - "check": "biome check .", - "fix": "biome lint --write" + "check": "biome check . && node scripts/validate-locales.js", + "fix": "biome lint --write", + "validate:locales": "node scripts/validate-locales.js" }, "keywords": [ "scrum", diff --git a/scripts/validate-locales.js b/scripts/validate-locales.js new file mode 100644 index 0000000..82b1ac9 --- /dev/null +++ b/scripts/validate-locales.js @@ -0,0 +1,110 @@ +#!/usr/bin/env node + +/** + * Locale Duplication Validator + * + * This script prevents duplicate locale folders that map to the same language. + * It helps avoid conflicts in translation management systems like Weblate. + * + * For example, both 'zh_CN' and 'zh_Hans' map to "Chinese (Simplified)", + * which causes duplication issues. + */ + +const fs = require('fs'); +const path = require('path'); + +// Map of locale codes that should be considered duplicates +// Key: canonical locale to keep, Value: array of duplicate codes to avoid +const LOCALE_DUPLICATES = { + zh_CN: ['zh_Hans', 'zh-CN', 'zh-Hans'], + zh_TW: ['zh_Hant', 'zh-TW', 'zh-Hant'], + pt_BR: ['pt-BR'], + en: ['en_US', 'en-US'], +}; + +// Flatten the duplicate map to check against +const getAllDuplicates = () => { + const duplicates = {}; + for (const [canonical, alternates] of Object.entries(LOCALE_DUPLICATES)) { + for (const alt of alternates) { + duplicates[alt] = canonical; + } + } + return duplicates; +}; + +const validateLocales = () => { + const localesDir = path.join(__dirname, '..', 'src', '_locales'); + + if (!fs.existsSync(localesDir)) { + console.error('❌ Error: _locales directory not found'); + process.exit(1); + } + + const folders = fs.readdirSync(localesDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + + const duplicateMap = getAllDuplicates(); + const errors = []; + const warnings = []; + + // Check for known duplicates + for (const folder of folders) { + if (duplicateMap[folder]) { + errors.push( + `❌ Duplicate locale detected: '${folder}' should use '${duplicateMap[folder]}' instead` + ); + } + } + + // Check if multiple canonical versions exist for the same language + const canonicalPresent = {}; + for (const [canonical, alternates] of Object.entries(LOCALE_DUPLICATES)) { + if (folders.includes(canonical)) { + canonicalPresent[canonical] = true; + } + for (const alt of alternates) { + if (folders.includes(alt)) { + if (canonicalPresent[canonical]) { + errors.push( + `❌ Conflict: Both '${canonical}' and '${alt}' exist. Keep only '${canonical}'` + ); + } + } + } + } + + // Validate each locale folder has messages.json + for (const folder of folders) { + const messagesPath = path.join(localesDir, folder, 'messages.json'); + if (!fs.existsSync(messagesPath)) { + warnings.push(`⚠️ Warning: '${folder}' is missing messages.json`); + } + } + + // Report results + console.log('\n🔍 Locale Validation Report\n'); + console.log(`Found ${folders.length} locale folder(s): ${folders.join(', ')}`); + console.log(''); + + if (errors.length > 0) { + console.error('Errors found:\n'); + errors.forEach(err => console.error(err)); + console.error('\n💡 Tip: Use the canonical locale codes defined in Chrome i18n standards.'); + console.error(' See: https://developer.chrome.com/docs/extensions/reference/api/i18n\n'); + process.exit(1); + } + + if (warnings.length > 0) { + console.warn('Warnings:\n'); + warnings.forEach(warn => console.warn(warn)); + console.warn(''); + } + + console.log('✅ All locale folders are valid. No duplicates detected.\n'); + process.exit(0); +}; + +// Run validation +validateLocales();