summaryrefslogtreecommitdiff
path: root/packages/i18n/scripts/verify.ts
blob: 877966e49f60d937df622e781936ae941d97f286 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*
 * SPDX-FileCopyrightText: syuilo and misskey-project
 * SPDX-License-Identifier: AGPL-3.0-only
 */

let valid = true;

interface LocaleRecord {
	[key: string]: string | LocaleRecord;
}

interface ErrorData {
	expected?: string;
	actual?: string;
	parameter?: string;
}

function writeError(type: string, lang: string, tree: string, data: ErrorData): void {
	process.stderr.write(JSON.stringify({ type, lang, tree, data }));
	process.stderr.write('\n');
	valid = false;
}

function verify(expected: LocaleRecord, actual: LocaleRecord, lang: string, trace?: string): void {
	for (const key in expected) {
		if (!Object.prototype.hasOwnProperty.call(actual, key)) {
			continue;
		}
		if (typeof expected[key] === 'object') {
			if (typeof actual[key] !== 'object') {
				writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'object', actual: typeof actual[key] });
				continue;
			}
			verify(expected[key] as LocaleRecord, actual[key] as LocaleRecord, lang, trace ? `${trace}.${key}` : key);
		} else if (typeof expected[key] === 'string') {
			switch (typeof actual[key]) {
				case 'object':
					writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'string', actual: 'object' });
					break;
				case 'undefined':
					continue;
				case 'string': {
					const expectedParameters = new Set((expected[key] as string).match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1)));
					const actualParameters = new Set((actual[key] as string).match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1)));
					for (const parameter of expectedParameters) {
						if (!actualParameters.has(parameter)) {
							writeError('missing_parameter', lang, trace ? `${trace}.${key}` : key, { parameter });
						}
					}
				}
			}
		}
	}
}

// index.tsはtsのまま動かすことを想定していない(ビルド成果物を外部に公開する).
// よってビルド後のものを検証する
const locales = await import('../built/index.js');
const { 'ja-JP': original, ...verifiees } = locales as unknown as Record<string, LocaleRecord>;

for (const lang in verifiees) {
	if (!Object.prototype.hasOwnProperty.call(locales, lang)) {
		continue;
	}
	verify(original, verifiees[lang], lang);
}

if (!valid) {
	process.exit(1);
}