[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(i18n): improve update process (#495)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

authored by

jyc.dev
autofix-ci[bot]
and committed by
GitHub
3acb1beb 23a2625f

+218 -31
+8
CONTRIBUTING.md
··· 301 301 pnpm i18n:check ja-JP 302 302 ``` 303 303 304 + To automatically add missing keys with English placeholders, use `--fix`: 305 + 306 + ```bash 307 + pnpm i18n:check:fix fr-FR 308 + ``` 309 + 310 + This will add missing keys with `"EN TEXT TO REPLACE: {english text}"` as placeholder values, making it easier to see what needs translation. 311 + 304 312 #### Country variants (advanced) 305 313 306 314 Most languages only need a single locale file. Country variants are only needed when you want to support regional differences (e.g., `es-ES` for Spain vs `es-419` for Latin America).
+59 -6
i18n/locales/fr-FR.json
··· 124 124 "vulns": "Vulnérabilités", 125 125 "updated": "Mis à jour", 126 126 "view_dependency_graph": "Voir le graphe de dépendances", 127 - "inspect_dependency_tree": "Inspecter l'arbre de dépendances" 127 + "inspect_dependency_tree": "Inspecter l'arbre de dépendances", 128 + "size_tooltip": { 129 + "unpacked": "{size} taille décompressée (ce paquet)", 130 + "total": "{size} taille totale décompressée (incluant les {count} dépendances pour linux-x64)" 131 + } 128 132 }, 129 133 "links": { 130 134 "repo": "dépôt", ··· 178 182 "other_versions": "Autres versions", 179 183 "more_tagged": "{count} de plus avec tag", 180 184 "all_covered": "Toutes les versions sont couvertes par les tags ci-dessus", 181 - "deprecated_title": "{version} (dépréciée)" 185 + "deprecated_title": "{version} (dépréciée)", 186 + "view_all": "Voir la version | Voir les {count} versions" 182 187 }, 183 188 "dependencies": { 184 189 "title": "Dépendances ({count})", 185 190 "list_label": "Dépendances du paquet", 186 191 "show_all": "afficher les {count} dépendances", 187 192 "optional": "optionnelle", 188 - "view_vulnerabilities": "Voir les vulnérabilités" 193 + "view_vulnerabilities": "Voir les vulnérabilités", 194 + "outdated_major": "{count} version majeure en retard (dernière : {latest}) | {count} versions majeures en retard (dernière : {latest})", 195 + "outdated_minor": "{count} version mineure en retard (dernière : {latest}) | {count} versions mineures en retard (dernière : {latest})", 196 + "outdated_patch": "Mise à jour patch disponible (dernière : {latest})" 189 197 }, 190 198 "peer_dependencies": { 191 199 "title": "Dépendances peer ({count})", ··· 246 254 "no_esm": "Pas de support des ES Modules", 247 255 "types_included": "Types inclus", 248 256 "types_available": "Types disponibles via {package}", 249 - "no_types": "Pas de types TypeScript" 257 + "no_types": "Pas de types TypeScript", 258 + "types_label": "Types" 250 259 }, 251 260 "license": { 252 261 "view_spdx": "Voir le texte de la licence sur SPDX" ··· 321 330 "maintainers": "Mainteneurs", 322 331 "keywords": "Mots-clés", 323 332 "versions": "Versions", 324 - "dependencies": "Dépendances" 333 + "dependencies": "Dépendances", 334 + "get_started": "Commencer" 325 335 }, 326 336 "sort": { 327 337 "downloads": "Plus téléchargés", 328 338 "updated": "Récemment mis à jour", 329 339 "name_asc": "Nom (A-Z)", 330 340 "name_desc": "Nom (Z-A)" 341 + }, 342 + "copy_name": "Copier le nom du paquet", 343 + "replacement": { 344 + "title": "Vous n'avez peut-être pas besoin de cette dépendance.", 345 + "native": "Ceci peut être remplacé par {replacement}, disponible depuis Node {nodeVersion}.", 346 + "simple": "La {community} a signalé ce paquet comme redondant, avec ce conseil : {replacement}.", 347 + "documented": "La {community} a signalé que ce paquet a des alternatives plus performantes.", 348 + "none": "Ce paquet a été signalé comme n'étant plus nécessaire, et sa fonctionnalité est probablement disponible nativement dans tous les moteurs.", 349 + "learn_more": "En savoir plus", 350 + "mdn": "MDN", 351 + "community": "communauté" 331 352 } 332 353 }, 333 354 "connector": { ··· 357 378 "warning": "ATTENTION", 358 379 "warning_text": "Cela permet à npmx d'accéder à votre CLI npm. Ne vous connectez qu'aux sites de confiance.", 359 380 "connect": "Connecter", 360 - "connecting": "Connexion..." 381 + "connecting": "Connexion...", 382 + "connected_as_user": "Connecté·e en tant que ~{user}" 361 383 } 362 384 }, 363 385 "operations": { ··· 730 752 "error": "Échec du chargement des organisations", 731 753 "empty": "Aucune organisation trouvée", 732 754 "view_all": "Tout voir" 755 + } 756 + }, 757 + "version": "Version", 758 + "built_at": "compilé {0}", 759 + "alt_logo": "Logo npmx", 760 + "account_menu": { 761 + "connect": "connexion", 762 + "account": "Compte", 763 + "npm_cli": "npm CLI", 764 + "atmosphere": "Atmosphère", 765 + "npm_cli_desc": "Gérer les paquets et orgs", 766 + "atmosphere_desc": "Fonctionnalités sociales et identité", 767 + "connect_npm_cli": "Connexion à npm CLI", 768 + "connect_atmosphere": "Connexion à Atmosphère", 769 + "connecting": "Connexion en cours...", 770 + "ops": "{count} op | {count} ops", 771 + "disconnect": "Déconnexion" 772 + }, 773 + "auth": { 774 + "modal": { 775 + "title": "Atmosphère", 776 + "connected_as": "Connecté·e en tant que {'@'}{handle}", 777 + "disconnect": "Déconnexion", 778 + "connect_prompt": "Connectez-vous avec votre compte Atmosphère", 779 + "handle_label": "Identifiant", 780 + "handle_placeholder": "alice.npmx.social", 781 + "connect": "Connexion", 782 + "create_account": "Créer un nouveau compte", 783 + "connect_bluesky": "Connexion avec Bluesky", 784 + "what_is_atmosphere": "Qu'est-ce qu'un compte Atmosphère ?", 785 + "atmosphere_explanation": "{npmx} utilise {atproto} pour alimenter plusieurs de ses fonctionnalités sociales, permettant aux utilisateurs de posséder leurs données et d'utiliser un seul compte pour toutes les applications compatibles. Une fois votre compte créé, vous pouvez utiliser d'autres applications comme {bluesky} ou {tangled} avec le même compte." 733 786 } 734 787 } 735 788 }
+59 -6
lunaria/files/fr-FR.json
··· 124 124 "vulns": "Vulnérabilités", 125 125 "updated": "Mis à jour", 126 126 "view_dependency_graph": "Voir le graphe de dépendances", 127 - "inspect_dependency_tree": "Inspecter l'arbre de dépendances" 127 + "inspect_dependency_tree": "Inspecter l'arbre de dépendances", 128 + "size_tooltip": { 129 + "unpacked": "{size} taille décompressée (ce paquet)", 130 + "total": "{size} taille totale décompressée (incluant les {count} dépendances pour linux-x64)" 131 + } 128 132 }, 129 133 "links": { 130 134 "repo": "dépôt", ··· 178 182 "other_versions": "Autres versions", 179 183 "more_tagged": "{count} de plus avec tag", 180 184 "all_covered": "Toutes les versions sont couvertes par les tags ci-dessus", 181 - "deprecated_title": "{version} (dépréciée)" 185 + "deprecated_title": "{version} (dépréciée)", 186 + "view_all": "Voir la version | Voir les {count} versions" 182 187 }, 183 188 "dependencies": { 184 189 "title": "Dépendances ({count})", 185 190 "list_label": "Dépendances du paquet", 186 191 "show_all": "afficher les {count} dépendances", 187 192 "optional": "optionnelle", 188 - "view_vulnerabilities": "Voir les vulnérabilités" 193 + "view_vulnerabilities": "Voir les vulnérabilités", 194 + "outdated_major": "{count} version majeure en retard (dernière : {latest}) | {count} versions majeures en retard (dernière : {latest})", 195 + "outdated_minor": "{count} version mineure en retard (dernière : {latest}) | {count} versions mineures en retard (dernière : {latest})", 196 + "outdated_patch": "Mise à jour patch disponible (dernière : {latest})" 189 197 }, 190 198 "peer_dependencies": { 191 199 "title": "Dépendances peer ({count})", ··· 246 254 "no_esm": "Pas de support des ES Modules", 247 255 "types_included": "Types inclus", 248 256 "types_available": "Types disponibles via {package}", 249 - "no_types": "Pas de types TypeScript" 257 + "no_types": "Pas de types TypeScript", 258 + "types_label": "Types" 250 259 }, 251 260 "license": { 252 261 "view_spdx": "Voir le texte de la licence sur SPDX" ··· 321 330 "maintainers": "Mainteneurs", 322 331 "keywords": "Mots-clés", 323 332 "versions": "Versions", 324 - "dependencies": "Dépendances" 333 + "dependencies": "Dépendances", 334 + "get_started": "Commencer" 325 335 }, 326 336 "sort": { 327 337 "downloads": "Plus téléchargés", 328 338 "updated": "Récemment mis à jour", 329 339 "name_asc": "Nom (A-Z)", 330 340 "name_desc": "Nom (Z-A)" 341 + }, 342 + "copy_name": "Copier le nom du paquet", 343 + "replacement": { 344 + "title": "Vous n'avez peut-être pas besoin de cette dépendance.", 345 + "native": "Ceci peut être remplacé par {replacement}, disponible depuis Node {nodeVersion}.", 346 + "simple": "La {community} a signalé ce paquet comme redondant, avec ce conseil : {replacement}.", 347 + "documented": "La {community} a signalé que ce paquet a des alternatives plus performantes.", 348 + "none": "Ce paquet a été signalé comme n'étant plus nécessaire, et sa fonctionnalité est probablement disponible nativement dans tous les moteurs.", 349 + "learn_more": "En savoir plus", 350 + "mdn": "MDN", 351 + "community": "communauté" 331 352 } 332 353 }, 333 354 "connector": { ··· 357 378 "warning": "ATTENTION", 358 379 "warning_text": "Cela permet à npmx d'accéder à votre CLI npm. Ne vous connectez qu'aux sites de confiance.", 359 380 "connect": "Connecter", 360 - "connecting": "Connexion..." 381 + "connecting": "Connexion...", 382 + "connected_as_user": "Connecté·e en tant que ~{user}" 361 383 } 362 384 }, 363 385 "operations": { ··· 730 752 "error": "Échec du chargement des organisations", 731 753 "empty": "Aucune organisation trouvée", 732 754 "view_all": "Tout voir" 755 + } 756 + }, 757 + "version": "Version", 758 + "built_at": "compilé {0}", 759 + "alt_logo": "Logo npmx", 760 + "account_menu": { 761 + "connect": "connexion", 762 + "account": "Compte", 763 + "npm_cli": "npm CLI", 764 + "atmosphere": "Atmosphère", 765 + "npm_cli_desc": "Gérer les paquets et orgs", 766 + "atmosphere_desc": "Fonctionnalités sociales et identité", 767 + "connect_npm_cli": "Connexion à npm CLI", 768 + "connect_atmosphere": "Connexion à Atmosphère", 769 + "connecting": "Connexion en cours...", 770 + "ops": "{count} op | {count} ops", 771 + "disconnect": "Déconnexion" 772 + }, 773 + "auth": { 774 + "modal": { 775 + "title": "Atmosphère", 776 + "connected_as": "Connecté·e en tant que {'@'}{handle}", 777 + "disconnect": "Déconnexion", 778 + "connect_prompt": "Connectez-vous avec votre compte Atmosphère", 779 + "handle_label": "Identifiant", 780 + "handle_placeholder": "alice.npmx.social", 781 + "connect": "Connexion", 782 + "create_account": "Créer un nouveau compte", 783 + "connect_bluesky": "Connexion avec Bluesky", 784 + "what_is_atmosphere": "Qu'est-ce qu'un compte Atmosphère ?", 785 + "atmosphere_explanation": "{npmx} utilise {atproto} pour alimenter plusieurs de ses fonctionnalités sociales, permettant aux utilisateurs de posséder leurs données et d'utiliser un seul compte pour toutes les applications compatibles. Une fois votre compte créé, vous pouvez utiliser d'autres applications comme {bluesky} ou {tangled} avec le même compte." 733 786 } 734 787 } 735 788 }
+1
package.json
··· 15 15 "dev": "nuxt dev", 16 16 "dev:docs": "pnpm run --filter npmx-docs dev --port=3001", 17 17 "i18n:check": "node --experimental-transform-types scripts/compare-translations.ts", 18 + "i18n:check:fix": "node --experimental-transform-types scripts/compare-translations.ts --fix", 18 19 "knip": "knip", 19 20 "knip:fix": "knip --fix", 20 21 "knip:production": "knip --production",
+91 -19
scripts/compare-translations.ts
··· 38 38 return JSON.parse(readFileSync(filePath, 'utf-8')) as NestedObject 39 39 } 40 40 41 + const addMissingKeys = ( 42 + obj: NestedObject, 43 + keysToAdd: string[], 44 + referenceFlat: Record<string, unknown>, 45 + ): NestedObject => { 46 + const result: NestedObject = { ...obj } 47 + 48 + for (const keyPath of keysToAdd) { 49 + const parts = keyPath.split('.') 50 + let current = result 51 + 52 + for (let i = 0; i < parts.length - 1; i++) { 53 + const part = parts[i]! 54 + if (!(part in current) || typeof current[part] !== 'object') { 55 + current[part] = {} 56 + } 57 + current = current[part] as NestedObject 58 + } 59 + 60 + const lastPart = parts[parts.length - 1]! 61 + if (!(lastPart in current)) { 62 + const enValue = referenceFlat[keyPath] 63 + current[lastPart] = `EN TEXT TO REPLACE: ${enValue}` 64 + } 65 + } 66 + 67 + return result 68 + } 69 + 41 70 const removeKeysFromObject = (obj: NestedObject, keysToRemove: string[]): NestedObject => { 42 71 const result: NestedObject = {} 43 72 ··· 89 118 const processLocale = ( 90 119 localeFile: string, 91 120 referenceKeys: string[], 92 - ): { missing: string[]; removed: string[] } => { 121 + referenceFlat: Record<string, unknown>, 122 + fix = false, 123 + ): { missing: string[]; removed: string[]; added: string[] } => { 93 124 const filePath = join(LOCALES_DIRECTORY, localeFile) 94 - const content = loadJson(filePath) 125 + let content = loadJson(filePath) 95 126 const flattenedKeys = Object.keys(flattenObject(content)) 96 127 97 128 const missingKeys = referenceKeys.filter(key => !flattenedKeys.includes(key)) 98 129 const extraneousKeys = flattenedKeys.filter(key => !referenceKeys.includes(key)) 99 130 131 + let modified = false 132 + 100 133 if (extraneousKeys.length > 0) { 101 - // Remove extraneous keys and write back 102 - const cleaned = removeKeysFromObject(content, extraneousKeys) 103 - writeFileSync(filePath, JSON.stringify(cleaned, null, 2) + '\n', 'utf-8') 134 + content = removeKeysFromObject(content, extraneousKeys) 135 + modified = true 104 136 } 105 137 106 - return { missing: missingKeys, removed: extraneousKeys } 138 + if (fix && missingKeys.length > 0) { 139 + content = addMissingKeys(content, missingKeys, referenceFlat) 140 + modified = true 141 + } 142 + 143 + if (modified) { 144 + writeFileSync(filePath, JSON.stringify(content, null, 2) + '\n', 'utf-8') 145 + } 146 + 147 + return { missing: missingKeys, removed: extraneousKeys, added: fix ? missingKeys : [] } 107 148 } 108 149 109 - const runSingleLocale = (locale: string, referenceKeys: string[]): void => { 150 + const runSingleLocale = ( 151 + locale: string, 152 + referenceKeys: string[], 153 + referenceFlat: Record<string, unknown>, 154 + fix = false, 155 + ): void => { 110 156 const localeFile = locale.endsWith('.json') ? locale : `${locale}.json` 111 157 const filePath = join(LOCALES_DIRECTORY, localeFile) 112 158 ··· 115 161 process.exit(1) 116 162 } 117 163 118 - const content = loadJson(filePath) 164 + let content = loadJson(filePath) 119 165 const flattenedKeys = Object.keys(flattenObject(content)) 120 166 const missingKeys = referenceKeys.filter(key => !flattenedKeys.includes(key)) 121 167 122 - console.log(`${COLORS.cyan}=== Missing keys for ${localeFile} ===${COLORS.reset}`) 168 + console.log( 169 + `${COLORS.cyan}=== Missing keys for ${localeFile}${fix ? ' (with --fix)' : ''} ===${COLORS.reset}`, 170 + ) 123 171 console.log(`Reference: ${REFERENCE_FILE_NAME} (${referenceKeys.length} keys)`) 124 172 console.log(`Target: ${localeFile} (${flattenedKeys.length} keys)`) 125 173 126 174 if (missingKeys.length === 0) { 127 175 console.log(`\n${COLORS.green}No missing keys!${COLORS.reset}\n`) 176 + } else if (fix) { 177 + content = addMissingKeys(content, missingKeys, referenceFlat) 178 + writeFileSync(filePath, JSON.stringify(content, null, 2) + '\n', 'utf-8') 179 + console.log( 180 + `\n${COLORS.green}Added ${missingKeys.length} missing key(s) with EN placeholder:${COLORS.reset}`, 181 + ) 182 + missingKeys.forEach(key => console.log(` - ${key}`)) 183 + console.log('') 128 184 } else { 129 185 console.log(`\n${COLORS.yellow}Missing ${missingKeys.length} key(s):${COLORS.reset}`) 130 186 missingKeys.forEach(key => console.log(` - ${key}`)) ··· 132 188 } 133 189 } 134 190 135 - const runAllLocales = (referenceKeys: string[]): void => { 191 + const runAllLocales = ( 192 + referenceKeys: string[], 193 + referenceFlat: Record<string, unknown>, 194 + fix = false, 195 + ): void => { 136 196 const localeFiles = readdirSync(LOCALES_DIRECTORY).filter( 137 197 file => file.endsWith('.json') && file !== REFERENCE_FILE_NAME, 138 198 ) 139 199 140 - console.log(`${COLORS.cyan}=== Translation Audit ===${COLORS.reset}`) 200 + console.log(`${COLORS.cyan}=== Translation Audit${fix ? ' (with --fix)' : ''} ===${COLORS.reset}`) 141 201 console.log(`Reference: ${REFERENCE_FILE_NAME} (${referenceKeys.length} keys)`) 142 202 console.log(`Checking ${localeFiles.length} locale(s)...`) 143 203 144 204 let totalMissing = 0 145 205 let totalRemoved = 0 206 + let totalAdded = 0 146 207 147 208 for (const localeFile of localeFiles) { 148 - const { missing, removed } = processLocale(localeFile, referenceKeys) 209 + const { missing, removed, added } = processLocale(localeFile, referenceKeys, referenceFlat, fix) 149 210 150 211 if (missing.length > 0 || removed.length > 0) { 151 212 console.log(`\n${COLORS.cyan}--- ${localeFile} ---${COLORS.reset}`) 152 213 153 - if (missing.length > 0) { 214 + if (added.length > 0) { 215 + logSection('ADDED MISSING KEYS (with EN placeholder)', added, COLORS.green, '', '') 216 + totalAdded += added.length 217 + } else if (missing.length > 0) { 154 218 logSection( 155 219 'MISSING KEYS (in en.json but not in this locale)', 156 220 missing, ··· 175 239 } 176 240 177 241 console.log(`\n${COLORS.cyan}=== Summary ===${COLORS.reset}`) 242 + if (totalAdded > 0) { 243 + console.log( 244 + `${COLORS.green} Added missing keys (EN placeholder): ${totalAdded}${COLORS.reset}`, 245 + ) 246 + } 178 247 if (totalMissing > 0) { 179 248 console.log(`${COLORS.yellow} Missing keys across all locales: ${totalMissing}${COLORS.reset}`) 180 249 } 181 250 if (totalRemoved > 0) { 182 251 console.log(`${COLORS.magenta} Removed extraneous keys: ${totalRemoved}${COLORS.reset}`) 183 252 } 184 - if (totalMissing === 0 && totalRemoved === 0) { 253 + if (totalMissing === 0 && totalRemoved === 0 && totalAdded === 0) { 185 254 console.log(`${COLORS.green} All locales are in sync!${COLORS.reset}`) 186 255 } 187 256 console.log('') ··· 190 259 const run = (): void => { 191 260 const referenceFilePath = join(LOCALES_DIRECTORY, REFERENCE_FILE_NAME) 192 261 const referenceContent = loadJson(referenceFilePath) 193 - const referenceKeys = Object.keys(flattenObject(referenceContent)) 262 + const referenceFlat = flattenObject(referenceContent) 263 + const referenceKeys = Object.keys(referenceFlat) 194 264 195 - const targetLocale = process.argv[2] 265 + const args = process.argv.slice(2) 266 + const fix = args.includes('--fix') 267 + const targetLocale = args.find(arg => !arg.startsWith('--')) 196 268 197 269 if (targetLocale) { 198 - // Single locale mode: just show missing keys (no modifications) 199 - runSingleLocale(targetLocale, referenceKeys) 270 + // Single locale mode 271 + runSingleLocale(targetLocale, referenceKeys, referenceFlat, fix) 200 272 } else { 201 273 // All locales mode: check all and remove extraneous keys 202 - runAllLocales(referenceKeys) 274 + runAllLocales(referenceKeys, referenceFlat, fix) 203 275 } 204 276 } 205 277