diff --git a/ui/.build/src/i18n.ts b/ui/.build/src/i18n.ts index 19b1f5ade9b6..ef557da09a4d 100644 --- a/ui/.build/src/i18n.ts +++ b/ui/.build/src/i18n.ts @@ -18,13 +18,13 @@ const isFormat = /%(?:[\d]\$)?s/; const tsPrelude = `// Generated interface I18nFormat { - (...args: (string | number)[]): string; - asArray: (...args: T[]) => (T | string)[], // vdom + (...args: (string | number)[]): string; // formatted + asArray: (...args: T[]) => (T | string)[]; // vdom } interface I18nPlural { - (quantity: number, ...args: (string | number)[]): string, // pluralSame - raw: (quantity: number, ...args: (string | number)[]) => string, // plural - asArray: (quantity: number, ...args: T[]) => (T | string)[], // vdomPlural + (quantity: number, ...args: (string | number)[]): string; // pluralSame + raw: (quantity: number, ...args: (string | number)[]) => string; // plural + asArray: (quantity: number, ...args: T[]) => (T | string)[]; // vdomPlural } interface I18n { /** Global noarg key lookup (only if absolutely necessary). */ @@ -98,8 +98,9 @@ export async function i18n(isBoot = true): Promise { } async function compileTypings(): Promise { + const typingsPathname = path.join(env.typesDir, 'lichess', `i18n.d.ts`); const [tstat] = await Promise.all([ - fs.promises.stat(path.join(env.typesDir, 'lichess', `i18n.d.ts`)).catch(() => undefined), + fs.promises.stat(typingsPathname).catch(() => undefined), fs.promises.mkdir(env.i18nJsDir).catch(() => {}), ]); const catStats = await Promise.all(cats.map(d => updated(d))); @@ -114,24 +115,32 @@ async function compileTypings(): Promise { ), ), ); - const code = + await fs.promises.writeFile( + typingsPathname, tsPrelude + - [...dicts] - .map( - ([cat, dict]) => - ` ${cat}: {\n` + - [...dict.entries()] - .map(([k, v]) => { - const tpe = typeof v !== 'string' ? 'I18nPlural' : isFormat.test(v) ? 'I18nFormat' : 'string'; - const comment = typeof v === 'string' ? v.split('\n')[0] : v['other']?.split('\n')[0]; - return ` /** ${comment} */\n ${k}: ${tpe};`; - }) - .join('\n') + - '\n };\n', - ) - .join('') + - '}\n'; - return fs.promises.writeFile(path.join(env.typesDir, 'lichess', `i18n.d.ts`), code); + [...dicts] + .map( + ([cat, dict]) => + ` ${cat}: {\n` + + [...dict.entries()] + .map(([k, v]) => { + if (!/^[A-Za-z_]\w*$/.test(k)) k = `'${k}'`; + const tpe = + typeof v !== 'string' ? 'I18nPlural' : isFormat.test(v) ? 'I18nFormat' : 'string'; + const comment = typeof v === 'string' ? v.split('\n')[0] : v['other']?.split('\n')[0]; + return ` /** ${comment} */\n ${k}: ${tpe};`; + }) + .join('\n') + + '\n };\n', + ) + .join('') + + '}\n', + ); + const mstat = catStats.reduce( + (a, b) => (a && b && quantize(a.mtimeMs) > quantize(b.mtimeMs) ? a : b), + tstat || false, + ); + if (mstat) await fs.promises.utimes(typingsPathname, mstat.mtime, mstat.mtime); } } @@ -205,7 +214,7 @@ async function updated(cat: string, locale?: string): Promise const jsPath = path.join(env.i18nJsDir, `${cat}.${locale ?? 'en-GB'}.js`); const [xml, js] = await Promise.allSettled([fs.promises.stat(xmlPath), fs.promises.stat(jsPath)]); return xml.status === 'rejected' || - (js.status !== 'rejected' && quantize(xml.value.mtimeMs, 2000) <= quantize(js.value.mtimeMs, 2000)) + (js.status !== 'rejected' && quantize(xml.value.mtimeMs) <= quantize(js.value.mtimeMs)) ? false : xml.value; } diff --git a/ui/@types/lichess/i18n.d.ts b/ui/@types/lichess/i18n.d.ts index 376e7f58087d..6ac9d4d23994 100644 --- a/ui/@types/lichess/i18n.d.ts +++ b/ui/@types/lichess/i18n.d.ts @@ -1,6 +1,6 @@ // Generated interface I18nFormat { - (...args: (string | number)[]): string; + (...args: (string | number)[]): string; // formatted asArray: (...args: T[]) => (T | string)[]; // vdom } interface I18nPlural {