mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-23 04:34:15 -04:00
103 lines
4.5 KiB
TypeScript
103 lines
4.5 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
|
|
import { deepListKeys } from '#shared/helpers.ts';
|
|
import { listMissingTranslations } from '#shared/missingTranslations.ts';
|
|
import { DictNode, parse } from '~~/helpers/translations/sumlAst.ts';
|
|
import type { Node } from '~~/helpers/translations/sumlAst.ts';
|
|
import type { Config } from '~~/locale/config.ts';
|
|
import type { Translations } from '~~/locale/translations.ts';
|
|
import { loadSuml } from '~~/server/loader.ts';
|
|
import { rootDir } from '~~/server/paths.ts';
|
|
|
|
export const loadDocument = async (name: string) => {
|
|
return parse(await fs.readFile(`${rootDir}/${name}`, 'utf-8'));
|
|
};
|
|
|
|
export const saveDocument = async (name: string, document: Node) => {
|
|
await fs.writeFile(`${rootDir}/${name}`, `${document.toString()}\n`);
|
|
};
|
|
|
|
export const mergeTranslationProposals = async (locale: string, proposalsFile: string) => {
|
|
const baseTranslationsDocument = await loadDocument('locale/_base/translations.suml');
|
|
const localeTranslationsDocument = await loadDocument(`locale/${locale}/translations.suml`);
|
|
const proposalsDocument = await loadDocument(proposalsFile);
|
|
|
|
if (!(proposalsDocument instanceof DictNode) ||
|
|
!(baseTranslationsDocument instanceof DictNode) ||
|
|
!(localeTranslationsDocument instanceof DictNode)) {
|
|
throw new Error('input nodes must be DictNode');
|
|
}
|
|
|
|
merge(proposalsDocument, baseTranslationsDocument, localeTranslationsDocument);
|
|
await saveDocument(`locale/${locale}/translations.suml`, localeTranslationsDocument);
|
|
};
|
|
|
|
export const merge = (source: DictNode, base: DictNode, target: DictNode) => {
|
|
const baseKeys = base.items.map((entry) => entry.key);
|
|
for (const sourceEntry of source.items) {
|
|
const baseEntry = base.items.find((baseEntry) => baseEntry.key === sourceEntry.key);
|
|
const targetEntry = target.items.find((targetEntry) => targetEntry.key === sourceEntry.key);
|
|
if (targetEntry === undefined) {
|
|
let insertIndex = 0;
|
|
if (baseEntry === undefined) {
|
|
insertIndex = target.items.length;
|
|
} else {
|
|
const baseIndex = baseKeys.indexOf(baseEntry.key);
|
|
const previousKeys = baseKeys.slice(0, baseIndex).toReversed();
|
|
for (const previousKey of previousKeys) {
|
|
const targetIndex = target.items.findIndex((targetEntry) => targetEntry.key === previousKey);
|
|
if (targetIndex !== -1) {
|
|
insertIndex = targetIndex + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
target.items.splice(insertIndex, 0, sourceEntry);
|
|
} else if (targetEntry.value instanceof DictNode) {
|
|
if (!(sourceEntry.value instanceof DictNode)) {
|
|
throw new Error('source entry must be an DictNode when merging');
|
|
}
|
|
const childBase = baseEntry?.value instanceof DictNode ? baseEntry.value : new DictNode([], []);
|
|
merge(sourceEntry.value, childBase, targetEntry.value);
|
|
} else {
|
|
targetEntry.value = sourceEntry.value;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const createTranslationFiles = async (locale: string) => {
|
|
const baseTranslations = await loadSuml<Translations>('locale/_base/translations.suml');
|
|
const config = await loadSuml<Config>('locale/_base/config.suml');
|
|
|
|
const configDocument = await loadDocument('locale/_base/config.suml');
|
|
const translationsDocument = await loadDocument('locale/_base/translations.suml');
|
|
|
|
const requiredTranslationKeys = listMissingTranslations({}, baseTranslations, config);
|
|
for (const key of deepListKeys(baseTranslations)) {
|
|
if (!requiredTranslationKeys.includes(key)) {
|
|
remove(translationsDocument, key.split('.'));
|
|
}
|
|
}
|
|
|
|
await fs.mkdir(`${rootDir}/locale/${locale}`, { recursive: true });
|
|
await saveDocument(`locale/${locale}/config.suml`, configDocument);
|
|
await saveDocument(`locale/${locale}/translations.suml`, translationsDocument);
|
|
};
|
|
|
|
export const remove = (node: Node, path: string[]) => {
|
|
if (!(node instanceof DictNode)) {
|
|
throw new Error('node must be DictNode');
|
|
}
|
|
|
|
if (path.length === 1) {
|
|
node.items = node.items.filter((item) => item.key !== path[0]);
|
|
return;
|
|
}
|
|
const childNode = node.items.find((item) => item.key === path[0])?.value;
|
|
if (!childNode) {
|
|
return;
|
|
}
|
|
remove(childNode, path.slice(1));
|
|
node.items = node.items.filter((item) => !(item.value instanceof DictNode) || item.value.items.length > 0);
|
|
};
|