154 lines
3.9 KiB
TypeScript
154 lines
3.9 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import * as fs from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, resolve } from 'node:path';
|
|
import * as yaml from 'js-yaml';
|
|
import ts from 'typescript';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const parameterRegExp = /\{(\w+)\}/g;
|
|
|
|
interface LocaleRecord {
|
|
[key: string]: string | LocaleRecord;
|
|
}
|
|
|
|
function createMemberType(item: string | LocaleRecord): ts.TypeNode {
|
|
if (typeof item !== 'string') {
|
|
return ts.factory.createTypeLiteralNode(createMembers(item));
|
|
}
|
|
const parameters = Array.from(
|
|
item.matchAll(parameterRegExp),
|
|
([, parameter]) => parameter,
|
|
);
|
|
return parameters.length
|
|
? ts.factory.createTypeReferenceNode(
|
|
ts.factory.createIdentifier('ParameterizedString'),
|
|
[
|
|
ts.factory.createUnionTypeNode(
|
|
parameters.map((parameter) =>
|
|
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(parameter)),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
}
|
|
|
|
function createMembers(record: LocaleRecord): ts.TypeElement[] {
|
|
return Object.entries(record).map(([k, v]) => {
|
|
const node = ts.factory.createPropertySignature(
|
|
undefined,
|
|
ts.factory.createStringLiteral(k),
|
|
undefined,
|
|
createMemberType(v),
|
|
);
|
|
if (typeof v === 'string') {
|
|
ts.addSyntheticLeadingComment(
|
|
node,
|
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
`*
|
|
* ${v.replace(/\n/g, '\n * ')}
|
|
`,
|
|
true,
|
|
);
|
|
}
|
|
return node;
|
|
});
|
|
}
|
|
|
|
export async function generateLocaleInterface(localesDir: string): Promise<void> {
|
|
const locale = yaml.load(fs.readFileSync(`${localesDir}/ja-JP.yml`, 'utf-8').toString()) as LocaleRecord;
|
|
const members = createMembers(locale);
|
|
|
|
const elements: ts.Statement[] = [
|
|
ts.factory.createImportDeclaration(
|
|
undefined,
|
|
ts.factory.createImportClause(
|
|
false,
|
|
undefined,
|
|
ts.factory.createNamedImports([
|
|
ts.factory.createImportSpecifier(
|
|
true,
|
|
undefined,
|
|
ts.factory.createIdentifier('ILocale'),
|
|
),
|
|
ts.factory.createImportSpecifier(
|
|
true,
|
|
undefined,
|
|
ts.factory.createIdentifier('ParameterizedString'),
|
|
),
|
|
]),
|
|
),
|
|
ts.factory.createStringLiteral('../types.js'),
|
|
undefined,
|
|
),
|
|
ts.factory.createInterfaceDeclaration(
|
|
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
ts.factory.createIdentifier('Locale'),
|
|
undefined,
|
|
[
|
|
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
|
|
ts.factory.createExpressionWithTypeArguments(
|
|
ts.factory.createIdentifier('ILocale'),
|
|
undefined,
|
|
),
|
|
]),
|
|
],
|
|
members,
|
|
),
|
|
];
|
|
|
|
ts.addSyntheticLeadingComment(
|
|
elements[0],
|
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
' eslint-disable ',
|
|
true,
|
|
);
|
|
ts.addSyntheticLeadingComment(
|
|
elements[0],
|
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
|
' This file is generated by scripts/generateLocaleInterface.ts',
|
|
true,
|
|
);
|
|
ts.addSyntheticLeadingComment(
|
|
elements[0],
|
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
|
' Do not edit this file directly.',
|
|
true,
|
|
);
|
|
|
|
const printed = ts
|
|
.createPrinter({
|
|
newLine: ts.NewLineKind.LineFeed,
|
|
})
|
|
.printList(
|
|
ts.ListFormat.MultiLine,
|
|
ts.factory.createNodeArray(elements),
|
|
ts.createSourceFile(
|
|
'locale.ts',
|
|
'',
|
|
ts.ScriptTarget.ESNext,
|
|
true,
|
|
ts.ScriptKind.TS,
|
|
),
|
|
);
|
|
|
|
const autogenDir = `${__dirname}/../src/autogen`;
|
|
fs.mkdirSync(autogenDir, { recursive: true });
|
|
|
|
// 一瞬ファイルが存在しなくなって途切れる→不安定になるらしいので、リネームで対処
|
|
fs.writeFileSync(`${autogenDir}/_locale.ts`, printed, 'utf-8');
|
|
fs.renameSync(`${autogenDir}/_locale.ts`, `${autogenDir}/locale.ts`);
|
|
}
|
|
|
|
// スクリプトとして直接実行された場合
|
|
const isMain = import.meta.url === `file://${process.argv[1]}`;
|
|
if (isMain) {
|
|
await generateLocaleInterface(resolve(__dirname, '../../../locales'));
|
|
}
|