mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 15:17:43 -05:00
Add check-translation-jsons script
This commit is contained in:
@@ -43,6 +43,7 @@ Things to note:
|
||||
- Some translated `.json` files can also have some lines in English as new lines get added to the site. Those can then be translated.
|
||||
- Some lines have a dynamic part like this one: `"articleBy": "by {{author}}"` in this case `{{author}}` should appear in the translated version unchanged. So in other words don't translate the part inside `{{}}`.
|
||||
- There is one more special syntax to keep in mind. When you translate this line `"project": "Sendou.ink is a project by <2>Sendou</2> with help from contributors:",` the `<2></2>` should appear in the translated version. The text inside these tags can change.
|
||||
- To update a translation file copy the existing file, do any modifications needed and send the updated one.
|
||||
|
||||
Any questions please ask Sendou!
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"create-weapon-json": "node --experimental-specifier-resolution=node --loader ts-node/esm -r tsconfig-paths/register scripts/create-weapon-json.ts",
|
||||
"create-gear-json": "node --experimental-specifier-resolution=node --loader ts-node/esm -r tsconfig-paths/register scripts/create-gear-json.ts",
|
||||
"create-analyzer-json": "node --experimental-specifier-resolution=node --loader ts-node/esm -r tsconfig-paths/register scripts/create-analyzer-json.ts",
|
||||
"check-translation-jsons": "node --experimental-specifier-resolution=node --loader ts-node/esm -r tsconfig-paths/register scripts/check-translation-jsons.ts",
|
||||
"replace-img-names": "node --experimental-specifier-resolution=node --loader ts-node/esm -r tsconfig-paths/register scripts/replace-img-names.ts",
|
||||
"lint:ts": "eslint . --ext .ts,.tsx",
|
||||
"lint:styles": "stylelint \"app/styles/**/*.css\"",
|
||||
|
||||
195
scripts/check-translation-jsons.ts
Normal file
195
scripts/check-translation-jsons.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
// 1) create status.json
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const otherLanguageTranslationPath = (code?: string, fileName?: string) =>
|
||||
path.join(
|
||||
...[__dirname, "..", "public", "locales", code, fileName].filter(
|
||||
(val): val is string => !!val
|
||||
)
|
||||
);
|
||||
|
||||
const allOtherLanguages = fs
|
||||
.readdirSync(otherLanguageTranslationPath())
|
||||
.filter((lang) => lang !== "en");
|
||||
|
||||
const missingTranslations: Record<
|
||||
string,
|
||||
Record<string, Array<string>>
|
||||
> = Object.fromEntries(allOtherLanguages.map((lang) => [lang, {}]));
|
||||
|
||||
const totalTranslationCounts: Record<string, number> = {};
|
||||
|
||||
for (const file of fs.readdirSync(otherLanguageTranslationPath("en"))) {
|
||||
const englishContent = JSON.parse(
|
||||
fs.readFileSync(otherLanguageTranslationPath("en", file), "utf8").trim()
|
||||
) as Record<string, string>;
|
||||
const key = file.replace(".json", "");
|
||||
|
||||
if (file !== "gear.json" && file !== "weapons.json") {
|
||||
totalTranslationCounts[key] = Object.keys(englishContent).length;
|
||||
}
|
||||
|
||||
for (const lang of allOtherLanguages) {
|
||||
try {
|
||||
const otherLanguageContent = JSON.parse(
|
||||
fs.readFileSync(otherLanguageTranslationPath(lang, file), "utf8").trim()
|
||||
) as Record<string, string>;
|
||||
|
||||
validateNoExtraKeysInOther({
|
||||
english: englishContent,
|
||||
other: otherLanguageContent,
|
||||
lang,
|
||||
file,
|
||||
});
|
||||
validateVariables({
|
||||
english: englishContent,
|
||||
other: otherLanguageContent,
|
||||
lang,
|
||||
file,
|
||||
});
|
||||
|
||||
const missingKeys = Object.keys(englishContent).filter(
|
||||
(key) => !Object.keys(otherLanguageContent).includes(key)
|
||||
);
|
||||
|
||||
if (key === "weapons" || key === "gear") {
|
||||
if (missingKeys.length > 0) {
|
||||
console.error(`missing keys in ${lang}/${file}`);
|
||||
}
|
||||
} else {
|
||||
missingTranslations[lang]![key] = missingKeys;
|
||||
}
|
||||
} catch (e) {
|
||||
if ((e as { code: string }).code !== "ENOENT") throw e;
|
||||
|
||||
missingTranslations[lang]![key] = Object.keys(englishContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const markdown = createTranslationProgessMarkdown({
|
||||
missingTranslations,
|
||||
totalTranslationCounts,
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, "..", "translation-progress.md"),
|
||||
markdown
|
||||
);
|
||||
|
||||
function validateNoExtraKeysInOther({
|
||||
english,
|
||||
other,
|
||||
lang,
|
||||
file,
|
||||
}: {
|
||||
english: Record<string, string>;
|
||||
other: Record<string, string>;
|
||||
lang: string;
|
||||
file: string;
|
||||
}) {
|
||||
const validKeys = Object.keys(english);
|
||||
|
||||
for (const key of Object.keys(other)) {
|
||||
if (validKeys.includes(key)) continue;
|
||||
|
||||
console.error(`unknown key in ${lang}/${file}: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateVariables({
|
||||
english,
|
||||
other,
|
||||
lang,
|
||||
file,
|
||||
}: {
|
||||
english: Record<string, string>;
|
||||
other: Record<string, string>;
|
||||
lang: string;
|
||||
file: string;
|
||||
}) {
|
||||
for (const [key, value] of Object.entries(english)) {
|
||||
const otherValue = other[key];
|
||||
if (!otherValue) continue;
|
||||
|
||||
// find all matches inside {{}}
|
||||
const englishMatches = value.match(/{{(.*?)}}/g);
|
||||
const otherMatches = otherValue.match(/{{(.*?)}}/g);
|
||||
|
||||
if (!englishMatches && !otherMatches) continue;
|
||||
|
||||
for (const englishVar of englishMatches ?? []) {
|
||||
if (!otherMatches?.includes(englishVar)) {
|
||||
console.error(
|
||||
`variable mismatch in ${lang}/${file}: ${englishVar} is missing in ${otherValue}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createTranslationProgessMarkdown({
|
||||
missingTranslations,
|
||||
totalTranslationCounts,
|
||||
}: {
|
||||
missingTranslations: Record<string, Record<string, Array<string>>>;
|
||||
totalTranslationCounts: Record<string, number>;
|
||||
}) {
|
||||
const totalKeysCount = Object.values(totalTranslationCounts).reduce(
|
||||
(a, b) => a + b,
|
||||
0
|
||||
);
|
||||
|
||||
return `# Translation Progress
|
||||
${Object.entries(missingTranslations)
|
||||
.map(([lang, missingKeysObj]) => {
|
||||
const totalAmountOfMissingKeys = Object.values(missingKeysObj).reduce(
|
||||
(a, b) => a + b.length,
|
||||
0
|
||||
);
|
||||
const status =
|
||||
totalAmountOfMissingKeys === 0
|
||||
? "🟢 Done"
|
||||
: totalAmountOfMissingKeys === totalKeysCount
|
||||
? "🔴 Not started"
|
||||
: "🟡 In progress";
|
||||
|
||||
const headers = () => {
|
||||
if (status !== "🟡 In progress") return "";
|
||||
|
||||
return Object.entries(missingKeysObj)
|
||||
.map(([file, missingKeys]) => {
|
||||
const statusDot =
|
||||
missingKeys.length === 0
|
||||
? "🟢"
|
||||
: missingKeys.length === totalTranslationCounts[file]!
|
||||
? "🔴"
|
||||
: "🟡";
|
||||
|
||||
return `### ${statusDot} ${file}.json
|
||||
**${
|
||||
totalTranslationCounts[file]! - missingKeys.length
|
||||
}/${totalTranslationCounts[file]!}**
|
||||
${
|
||||
missingKeys.length === 0 ||
|
||||
missingKeys.length === totalTranslationCounts[file]!
|
||||
? ""
|
||||
: `<details>
|
||||
<summary>Missing</summary>
|
||||
|
||||
${missingKeys.map((key) => `- ${key}`).join("\n")}
|
||||
|
||||
</details>`
|
||||
}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
};
|
||||
|
||||
return `## /${lang} (${status})
|
||||
|
||||
${headers()}`;
|
||||
})
|
||||
.join("\n\n---\n\n")}`;
|
||||
}
|
||||
Reference in New Issue
Block a user