forked from AxTps/Starlight_Lancher
141 lines
3.3 KiB
JavaScript
141 lines
3.3 KiB
JavaScript
import { readFile } from 'node:fs/promises'
|
|
import { parse, TYPE } from '@formatjs/icu-messageformat-parser'
|
|
|
|
const localePairs = [
|
|
[
|
|
'apps/app-frontend/src/locales/en-US/index.json',
|
|
'apps/app-frontend/src/locales/zh-CN/index.json',
|
|
],
|
|
['packages/ui/src/locales/en-US/index.json', 'packages/ui/src/locales/zh-CN/index.json'],
|
|
]
|
|
|
|
const failures = []
|
|
const allowedUntranslatedMessages = new Set([
|
|
'Chaos Cubed',
|
|
'MINECON Earth 2017',
|
|
'Modrinth',
|
|
'.minecraft',
|
|
'Striding Hero',
|
|
'Axolotl Launcher',
|
|
'Explore high-quality Minecraft content on Modrinth.',
|
|
'example.modrinth.gg',
|
|
'{title} - {count}',
|
|
'Hooks',
|
|
'/path/to/java',
|
|
'https://example.com/api/yggdrasil',
|
|
'Fabric',
|
|
'Forge',
|
|
'Paper',
|
|
'Cleanroom',
|
|
'LiteLoader',
|
|
'LogShare.CN',
|
|
'NeoForge',
|
|
'OptiFine',
|
|
'Quilt',
|
|
'Mr Pack',
|
|
'TNT',
|
|
'CurseForge',
|
|
'BBCode',
|
|
'mclo.gs',
|
|
'CMI',
|
|
'CSV',
|
|
'AI',
|
|
'HTML',
|
|
'Mojang G1GC',
|
|
'PCL G1GC',
|
|
'PCL',
|
|
'Shenandoah',
|
|
'ZGC',
|
|
'MineDown',
|
|
'MiniMessage',
|
|
'TabooLib',
|
|
'TrChat',
|
|
'TNT',
|
|
'Java {version}',
|
|
'Studio',
|
|
'https://api-free.deepl.com/v2/translate',
|
|
'Java',
|
|
'Minecraft EULA',
|
|
'server.properties',
|
|
'{type} · {version}',
|
|
'{value} MB',
|
|
'> {command}',
|
|
'Beta',
|
|
'Release',
|
|
])
|
|
|
|
function messageText(value) {
|
|
return typeof value === 'string' ? value : (value?.message ?? value?.defaultMessage ?? '')
|
|
}
|
|
|
|
function hasExplicitTranslation(value) {
|
|
return typeof value === 'string' || typeof value?.message === 'string'
|
|
}
|
|
|
|
function argumentNames(message) {
|
|
const names = new Set()
|
|
const argumentTypes = new Set([
|
|
TYPE.argument,
|
|
TYPE.number,
|
|
TYPE.date,
|
|
TYPE.time,
|
|
TYPE.select,
|
|
TYPE.plural,
|
|
])
|
|
|
|
function visit(elements) {
|
|
for (const element of elements) {
|
|
if (argumentTypes.has(element.type)) names.add(element.value)
|
|
if (element.options) {
|
|
for (const option of Object.values(element.options)) visit(option.value)
|
|
}
|
|
if (element.children) visit(element.children)
|
|
}
|
|
}
|
|
|
|
visit(parse(message))
|
|
return [...names].sort()
|
|
}
|
|
|
|
for (const [sourcePath, translationPath] of localePairs) {
|
|
const source = JSON.parse(await readFile(sourcePath, 'utf8'))
|
|
const translation = JSON.parse(await readFile(translationPath, 'utf8'))
|
|
|
|
for (const key of Object.keys(source)) {
|
|
if (!(key in translation)) {
|
|
failures.push(`${translationPath}: missing ${key}`)
|
|
continue
|
|
}
|
|
|
|
try {
|
|
const sourceMessage = messageText(source[key])
|
|
const translationMessage = messageText(translation[key])
|
|
const sourceArguments = argumentNames(sourceMessage)
|
|
const translationArguments = argumentNames(translationMessage)
|
|
if (sourceArguments.join('\0') !== translationArguments.join('\0')) {
|
|
failures.push(
|
|
`${translationPath}: ICU arguments for ${key} are [${translationArguments.join(', ')}], expected [${sourceArguments.join(', ')}]`,
|
|
)
|
|
}
|
|
|
|
if (
|
|
sourceMessage === translationMessage &&
|
|
hasExplicitTranslation(translation[key]) &&
|
|
/[A-Za-z]{2}/.test(sourceMessage) &&
|
|
!allowedUntranslatedMessages.has(sourceMessage)
|
|
) {
|
|
failures.push(`${translationPath}: untranslated ${key}`)
|
|
}
|
|
} catch (error) {
|
|
failures.push(`${translationPath}: invalid ICU message ${key}: ${error.message}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error(`Simplified Chinese coverage check failed:\n${failures.join('\n')}`)
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log('Simplified Chinese key coverage and ICU argument checks passed.')
|