Skip to content

Commit 5b68303

Browse files
committed
chore: update compare-translations.ts logic
1 parent 79eeee3 commit 5b68303

File tree

2 files changed

+131
-25
lines changed

2 files changed

+131
-25
lines changed

lunaria/prepare-json-files.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,7 @@ export async function prepareJsonFiles() {
3737
await Promise.all(currentLocales.map(l => mergeLocale(l)))
3838
}
3939

40-
async function loadJsonFile(name: string) {
41-
return JSON.parse(await fs.readFile(path.resolve(`${localesFolder}/${name}`), 'utf8'))
42-
}
43-
44-
function getFileName(file: string | { path: string }): string {
45-
return typeof file === 'string' ? file : file.path
46-
}
47-
48-
async function mergeLocale(locale: LocaleObject) {
40+
export async function mergeLocaleObject(locale: LocaleObject) {
4941
const files = locale.files ?? []
5042
if (locale.file || files.length === 1) {
5143
const json = locale.file ?? (files[0] ? getFileName(files[0]) : undefined)
@@ -65,6 +57,19 @@ async function mergeLocale(locale: LocaleObject) {
6557
deepCopy(currentSource, source)
6658
}
6759

60+
return source
61+
}
62+
63+
async function loadJsonFile(name: string) {
64+
return JSON.parse(await fs.readFile(path.resolve(`${localesFolder}/${name}`), 'utf8'))
65+
}
66+
67+
function getFileName(file: string | { path: string }): string {
68+
return typeof file === 'string' ? file : file.path
69+
}
70+
71+
async function mergeLocale(locale: LocaleObject) {
72+
const source = await mergeLocaleObject(locale)
6873
await fs.writeFile(
6974
path.resolve(`${destFolder}/${locale.code}.json`),
7075
JSON.stringify(source, null, 2),

scripts/compare-translations.ts

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/* eslint-disable no-console */
2-
import process from 'node:process'
2+
import * as process from 'node:process'
33
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
4-
import { join } from 'node:path'
4+
import { basename, join } from 'node:path'
55
import { fileURLToPath } from 'node:url'
6+
import { countryLocaleVariants, currentLocales } from '../config/i18n.ts'
7+
import { mergeLocaleObject } from '../lunaria/prepare-json-files.ts'
68

79
const LOCALES_DIRECTORY = fileURLToPath(new URL('../i18n/locales', import.meta.url))
810
const REFERENCE_FILE_NAME = 'en.json'
@@ -17,13 +19,95 @@ const COLORS = {
1719
} as const
1820

1921
type NestedObject = { [key: string]: unknown }
22+
interface LocaleInfo {
23+
filePath: string
24+
locale: string
25+
lang: string
26+
country?: string
27+
forCountry?: boolean
28+
mergeLocale?: boolean
29+
}
30+
31+
const contries = new Map<string, Map<string, LocaleInfo>>()
32+
33+
const extractLocalInfo = (
34+
filePath: string,
35+
forCountry: boolean = false,
36+
mergeLocale: boolean = false,
37+
): LocaleInfo => {
38+
const locale = basename(filePath, '.json')
39+
const [lang, country] = locale.split('-')
40+
return { filePath, locale, lang, country, forCountry, mergeLocale }
41+
}
42+
43+
const populateLocaleCountries = (): void => {
44+
for (const lang of Object.keys(countryLocaleVariants)) {
45+
const variants = countryLocaleVariants[lang]
46+
for (const variant of variants) {
47+
if (!contries.has(lang)) {
48+
contries.set(lang, new Map())
49+
}
50+
if (variant.country) {
51+
contries.get(lang)!.set(lang, extractLocalInfo(lang, true))
52+
contries.get(lang)!.set(variant.code, extractLocalInfo(variant.code, true, true))
53+
} else {
54+
contries.get(lang)!.set(variant.code, extractLocalInfo(variant.code, false, true))
55+
}
56+
}
57+
}
58+
}
2059

21-
const loadJson = (filePath: string): NestedObject => {
60+
/**
61+
* We use ISO 639-1 for the language and ISO 3166-1 for the country (e.g. es-ES), we're preventing here:
62+
* using the language as the JSON file name when there is no country variant.
63+
*
64+
* For example, `az.json` is wrong, should be `az-AZ.json` since it is not included at `countryLocaleVariants`.
65+
*/
66+
const checkCountryVariant = (localeInfo: LocaleInfo): void => {
67+
const { locale, lang, country } = localeInfo
68+
const countryVariant = contries.get(lang)
69+
if (countryVariant) {
70+
if (country) {
71+
const found = countryVariant.get(locale)
72+
if (!found) {
73+
console.error(
74+
`${COLORS.red}Error: Invalid locale file "${locale}", it should be included at "countryLocaleVariants" in config/i18n.ts"${COLORS.reset}`,
75+
)
76+
process.exit(1)
77+
}
78+
localeInfo.forCountry = found.forCountry
79+
localeInfo.mergeLocale = found.mergeLocale
80+
} else {
81+
localeInfo.forCountry = false
82+
localeInfo.mergeLocale = false
83+
}
84+
} else {
85+
if (!country) {
86+
console.error(
87+
`${COLORS.red}Error: Invalid locale file "${locale}", it should be included at "countryLocaleVariants" in config/i18n.ts, or change the name to include country name "${lang}-<country-name>"${COLORS.reset}`,
88+
)
89+
process.exit(1)
90+
}
91+
}
92+
}
93+
94+
const checkJsonName = (filePath: string): LocaleInfo => {
95+
const info = extractLocalInfo(filePath)
96+
checkCountryVariant(info)
97+
return info
98+
}
99+
100+
const loadJson = async ({ filePath, mergeLocale, locale }: LocaleInfo): Promise<NestedObject> => {
22101
if (!existsSync(filePath)) {
23102
console.error(`${COLORS.red}Error: File not found at ${filePath}${COLORS.reset}`)
24103
process.exit(1)
25104
}
26-
return JSON.parse(readFileSync(filePath, 'utf-8')) as NestedObject
105+
106+
if (!mergeLocale) {
107+
return JSON.parse(readFileSync(filePath, 'utf-8')) as NestedObject
108+
}
109+
110+
return await mergeLocaleObject(currentLocales.find(l => l.code === locale)!)
27111
}
28112

29113
type SyncStats = {
@@ -51,7 +135,13 @@ const syncLocaleData = (
51135

52136
if (isNested(refValue)) {
53137
const nextTarget = isNested(target[key]) ? target[key] : {}
54-
result[key] = syncLocaleData(refValue, nextTarget, stats, fix, propertyPath)
138+
const data = syncLocaleData(refValue, nextTarget, stats, fix, propertyPath)
139+
// don't add empty objects: --fix will prevent this
140+
if (Object.keys(data).length === 0) {
141+
delete result[key]
142+
} else {
143+
result[key] = data
144+
}
55145
} else {
56146
stats.referenceKeys.push(propertyPath)
57147

@@ -91,13 +181,14 @@ const logSection = (
91181
keys.forEach(key => console.log(` - ${key}`))
92182
}
93183

94-
const processLocale = (
184+
const processLocale = async (
95185
localeFile: string,
96186
referenceContent: NestedObject,
97187
fix = false,
98-
): SyncStats => {
188+
): Promise<SyncStats> => {
99189
const filePath = join(LOCALES_DIRECTORY, localeFile)
100-
const targetContent = loadJson(filePath)
190+
const localeInfo = checkJsonName(filePath)
191+
const targetContent = await loadJson(localeInfo)
101192

102193
const stats: SyncStats = {
103194
missing: [],
@@ -115,7 +206,11 @@ const processLocale = (
115206
return stats
116207
}
117208

118-
const runSingleLocale = (locale: string, referenceContent: NestedObject, fix = false): void => {
209+
const runSingleLocale = async (
210+
locale: string,
211+
referenceContent: NestedObject,
212+
fix = false,
213+
): Promise<void> => {
119214
const localeFile = locale.endsWith('.json') ? locale : `${locale}.json`
120215
const filePath = join(LOCALES_DIRECTORY, localeFile)
121216

@@ -124,7 +219,7 @@ const runSingleLocale = (locale: string, referenceContent: NestedObject, fix = f
124219
process.exit(1)
125220
}
126221

127-
const { missing, extra, referenceKeys } = processLocale(localeFile, referenceContent, fix)
222+
const { missing, extra, referenceKeys } = await processLocale(localeFile, referenceContent, fix)
128223

129224
console.log(
130225
`${COLORS.cyan}=== Missing keys for ${localeFile}${fix ? ' (with --fix)' : ''} ===${COLORS.reset}`,
@@ -152,7 +247,7 @@ const runSingleLocale = (locale: string, referenceContent: NestedObject, fix = f
152247
console.log('')
153248
}
154249

155-
const runAllLocales = (referenceContent: NestedObject, fix = false): void => {
250+
const runAllLocales = async (referenceContent: NestedObject, fix = false): Promise<void> => {
156251
const localeFiles = readdirSync(LOCALES_DIRECTORY).filter(
157252
file => file.endsWith('.json') && file !== REFERENCE_FILE_NAME,
158253
)
@@ -164,7 +259,7 @@ const runAllLocales = (referenceContent: NestedObject, fix = false): void => {
164259
let totalAdded = 0
165260

166261
for (const localeFile of localeFiles) {
167-
const stats = processLocale(localeFile, referenceContent, fix)
262+
const stats = await processLocale(localeFile, referenceContent, fix)
168263
results.push({
169264
file: localeFile,
170265
...stats,
@@ -232,20 +327,26 @@ const runAllLocales = (referenceContent: NestedObject, fix = false): void => {
232327
console.log('')
233328
}
234329

235-
const run = (): void => {
330+
const run = async (): Promise<void> => {
236331
const referenceFilePath = join(LOCALES_DIRECTORY, REFERENCE_FILE_NAME)
237-
const referenceContent = loadJson(referenceFilePath)
332+
const referenceContent = await loadJson({
333+
filePath: referenceFilePath,
334+
locale: 'en',
335+
lang: 'en',
336+
})
238337

239338
const args = process.argv.slice(2)
240339
const fix = args.includes('--fix')
241340
const targetLocale = args.find(arg => !arg.startsWith('--'))
242341

342+
populateLocaleCountries()
343+
243344
if (targetLocale) {
244345
// Single locale mode
245-
runSingleLocale(targetLocale, referenceContent, fix)
346+
await runSingleLocale(targetLocale, referenceContent, fix)
246347
} else {
247348
// All locales mode: check all and remove extraneous keys
248-
runAllLocales(referenceContent, fix)
349+
await runAllLocales(referenceContent, fix)
249350
}
250351
}
251352

0 commit comments

Comments
 (0)