mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-01 20:19:39 +00:00
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
// The keys of the translations that the code asks for have to exist, which nothing else checks:
|
|||
|
|
// a missing key shows as the key itself.
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { readdirSync, readFileSync } from 'node:fs';
|
||
|
|
import { describe, it } from 'node:test';
|
||
|
|
import { fileURLToPath } from 'node:url';
|
||
|
|
import { join } from 'node:path';
|
||
|
|
import { languages } from '$lib/i18n/languages';
|
||
|
|
|
||
|
|
const src = fileURLToPath(new URL('../..', import.meta.url));
|
||
|
|
|
||
|
|
function sources(dir: string): string[] {
|
||
|
|
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||
|
|
const path = join(dir, entry.name);
|
||
|
|
if (entry.isDirectory()) {
|
||
|
|
return ['locales', 'docs'].includes(entry.name) ? [] : sources(path);
|
||
|
|
}
|
||
|
|
return /\.(ts|svelte)$/.test(entry.name) && !entry.name.endsWith('.test.ts') ? [path] : [];
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/** The value at a key (`a.b.c`) of a dictionary, `undefined` if there is none. */
|
||
|
|
function valueAt(dictionary: object, key: string): unknown {
|
||
|
|
let value: unknown = dictionary;
|
||
|
|
for (const part of key.split('.')) {
|
||
|
|
if (value === null || typeof value !== 'object' || !(part in value)) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
value = (value as Record<string, unknown>)[part];
|
||
|
|
}
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
|
||
|
|
function has(dictionary: object, key: string): boolean {
|
||
|
|
return typeof valueAt(dictionary, key) === 'string';
|
||
|
|
}
|
||
|
|
|
||
|
|
function keysOf(dictionary: object, prefix = ''): string[] {
|
||
|
|
return Object.entries(dictionary).flatMap(([key, value]) =>
|
||
|
|
typeof value === 'object' && value !== null
|
||
|
|
? keysOf(value, `${prefix}${key}.`)
|
||
|
|
: [`${prefix}${key}`]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const english = JSON.parse(readFileSync(join(src, 'locales/en.json'), 'utf-8'));
|
||
|
|
|
||
|
|
describe('translations', () => {
|
||
|
|
it('exist in english for every key that the code asks for', () => {
|
||
|
|
const missing: string[] = [];
|
||
|
|
let checked = 0;
|
||
|
|
for (const file of sources(src)) {
|
||
|
|
const code = readFileSync(file, 'utf-8');
|
||
|
|
for (const match of code.matchAll(/i18n\._\(\s*(['"])([a-z0-9_.-]+)\1\s*[,)]/gi)) {
|
||
|
|
checked++;
|
||
|
|
if (!has(english, match[2])) {
|
||
|
|
missing.push(`${match[2]} (${file.replace(src, '')})`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// the pattern does find the keys
|
||
|
|
assert.ok(checked > 100, `${checked}`);
|
||
|
|
assert.deepEqual(missing, []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('have no empty text in english', () => {
|
||
|
|
const empty = keysOf(english).filter((key) => {
|
||
|
|
const value = valueAt(english, key);
|
||
|
|
return typeof value === 'string' && value.trim() === '';
|
||
|
|
});
|
||
|
|
assert.deepEqual(empty, []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('are valid JSON for every language of the site, with only keys that english has', () => {
|
||
|
|
const known = new Set(keysOf(english));
|
||
|
|
for (const language of Object.keys(languages)) {
|
||
|
|
const dictionary = JSON.parse(
|
||
|
|
readFileSync(join(src, `locales/${language}.json`), 'utf-8')
|
||
|
|
);
|
||
|
|
const unknown = keysOf(dictionary).filter((key) => !known.has(key));
|
||
|
|
assert.deepEqual(unknown, [], language);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|