4 Commits
9 changed files with 3353 additions and 1093 deletions
+1
View File
@@ -0,0 +1 @@
/node_modules/
File diff suppressed because it is too large Load Diff
+15
View File
@@ -25,3 +25,18 @@ These might look incorrect but with `%s` inserted it will be things like
- `destroyed skin` and `ripped skin` refer to NoN's ripped-clothing appearance. Prefer wording about a damaged appearance, outfit, or clothing when a literal translation would imply bodily injury.
- Keep filenames and paths such as `destroyed_skin.png`, `destroyed_skin_mask_%s.png`, and `config/needsofnature` unchanged.
- Keep mod and project names such as `Quick Skin`, `Skin Overrides`, `CPM`, `Figura`, `NoFigura`, and `NoN` unchanged.
## Translation Utility
make.ts merges multiple language files into one for easy editing, then splits them back into separate files. This tool lets you:
- find the original text without searching another file
- automatically remove duplicate text to save space
- sort your i18n files according to the official order
### Usage
1. Open `make.ts` and edit the `targetLangFiles` and the `workFile` fields, change `zh_cn` to your language. please notice that The `suffix` and `file` values must be unique.
2. Run `./make.ts` (or `ts-node make.ts` / `deno make.ts`). The language files will be merged into the `workFile` location (e.g. `zh_cn.json`).
3. Edit the output file.
4. Run `./make.ts` again. If the workfile is newer than the other files, the script will write the changes back to the language files (at the locations specified by `targetLangFiles`).
5. If you don't want a language to be written back, add `ro: true` to its entry in `targetLangFiles`.
+31 -31
View File
@@ -64,6 +64,13 @@
"key.needsofnature.player_preferences": "打开玩家偏好",
"key.needsofnature.give_up": "放弃",
"key.needsofnature.model_alignment": "调整动画参与者对齐",
"key.needsofnature.block_requirement_visualization": "显示动画方块需求",
"message.needsofnature.block_requirement_visualization.enabled": "已启用动画方块需求视图",
"message.needsofnature.block_requirement_visualization.legend": "蓝色:需要 | 红色:空 | 橙色:反向匹配",
"message.needsofnature.block_requirement_visualization.disabled": "已禁用动画方块需求视图",
"message.needsofnature.block_requirement_visualization.none": "此动画无方块需求",
"message.needsofnature.block_requirement_visualization.truncated": "动画方块需求过大,已截断",
"message.needsofnature.block_requirement_visualization.unsupported": "服务器不支持动画方块需求",
"screen.needsofnature.model_alignment.title": "动画参与者对齐",
"screen.needsofnature.model_alignment.actor": "%s/%s %s | %s",
"screen.needsofnature.model_alignment.model": "%s:%s",
@@ -71,6 +78,7 @@
"screen.needsofnature.model_alignment.role": "角色:%s",
"screen.needsofnature.model_alignment.scope.model": "模型范围",
"screen.needsofnature.model_alignment.scope.role": "动画角色",
"screen.needsofnature.model_alignment.scope.advanced": "Advanced",
"screen.needsofnature.model_alignment.step": "步骤 %s",
"screen.needsofnature.model_alignment.value_tooltip": "滚动调整",
"screen.needsofnature.model_alignment.left": "左",
@@ -89,6 +97,9 @@
"screen.needsofnature.model_alignment.profile_mismatch": "此配置文件是为不同的型号创建的。它仍然可以使用,但它的偏移量可能不适合。",
"screen.needsofnature.model_alignment.read_only_player": "只读:%s 控制自己的偏移量",
"screen.needsofnature.model_alignment.read_only_mob": "只读:%s 控制此角色",
"screen.needsofnature.model_alignment.read_only_advanced": "Read-only: %s is the advanced controller",
"screen.needsofnature.model_alignment.advanced_disabled": "Advanced offsets are disabled by the server",
"screen.needsofnature.model_alignment.advanced_unavailable": "No advanced controller is active",
"hud.needsofnature.give_up.prompt": "按住 %s 放弃",
"gui.needsofnature.repair_skin": "修复损坏的衣服",
"gui.needsofnature.repair_skin.needs_loom": "附近需要织布机方块",
@@ -339,6 +350,7 @@
"config.needsofnature.offsets.description.model_wide.label": "整个模型:",
"config.needsofnature.offsets.description.model_wide.body": "应用于当前模型参与的所有动画。",
"config.needsofnature.offsets.description.combined": "整个模型的偏移量与动画角色的偏移量会叠加使用。",
"config.needsofnature.offsets.description.advanced": "When enabled by the server, an opted-in advanced controller can add a separate role-based offset of up to two blocks to any actor. Personal offsets remain intact.",
"config.needsofnature.offsets.description.profiles": "使用偏移量配置文件可为不同模型或玩家尺寸分别保存调整。如果安装了 Figura,也可以将偏移量直接保存到当前头像中。",
"config.needsofnature.description_card.narration.expanded": "%s,已展开",
"config.needsofnature.description_card.narration.collapsed": "%s,已折叠",
@@ -377,6 +389,9 @@
"config.needsofnature.figura_offsets_section": "偏移量",
"config.needsofnature.figura_alignment.store": "在头像中存储偏移量",
"config.needsofnature.figura_alignment.show": "显示偏移",
"config.needsofnature.advanced_offset_controller": "Advanced offset controller:",
"config.needsofnature.tooltip.advanced_offset_controller": "Opt in to control the additional role-based offsets for every actor when the server permits advanced controllers.",
"config.needsofnature.tooltip.advanced_offset_controller_disabled": "Advanced offset controllers are disabled by this server.",
"config.needsofnature.tooltip.figura_alignment.store": "将此 Figura 头像的模型范围偏移量和所有每个动画的动画参与者偏移量移至其 NoN 配置文件中。头像必须是解压后的本地文件夹。",
"config.needsofnature.tooltip.figura_alignment.show": "显示每个便携式偏移配置文件以及保存在活动 Figura 头像中的偏移。",
"config.needsofnature.offset_profiles.current_model": "当前型号:",
@@ -433,6 +448,7 @@
"screen.needsofnature.figura_offsets.model": "%s:%s",
"screen.needsofnature.figura_offsets.model_wide": "模型范围",
"screen.needsofnature.figura_offsets.animation_role": "%s / %s",
"screen.needsofnature.figura_offsets.advanced_role": "Advanced: %s / %s",
"screen.needsofnature.figura_offsets.value": "X %s Y %s Z %s",
"screen.needsofnature.offset_profiles.select_title": "选择偏移配置文件",
"screen.needsofnature.offset_profiles.select_for": "%s:%s",
@@ -1055,19 +1071,19 @@
"debug.needsofnature.missing_pregnancy_block": "中奖结果缺少方块 %s。",
"item.needsofnature.entity_liquid_bottle": "%s 胶水瓶",
"item.minecraft.potion.effect.needsofnature.liquid": "胶水瓶",
"stat.needsofnature.animations_started_total": "NoN:已开始动画",
"stat.needsofnature.animations_started_total": "NoN:开始动画次数",
"stat.needsofnature.peaked_animations_total": "NoN:收尾动画",
"stat.needsofnature.liquid_gained_ml_total": "NoN:获取胶水总量(毫升)",
"stat.needsofnature.multi_actor_animations_total": "NoN:3+参与者动画",
"stat.needsofnature.multi_actor_animations_total": "NoN:3+参与者动画次数",
"stat.needsofnature.water_animations_total": "NoN:水中动画",
"stat.needsofnature.attack_animations_started_total": "NoN:已开始攻击动画",
"stat.needsofnature.attack_escapes_success_total": "NoN:攻击逃脱",
"stat.needsofnature.m_injector_peaks_ml_total": "NoN:口交胶水量(毫升)",
"stat.needsofnature.manual_peaks_total": "NoN:手动动画",
"stat.needsofnature.bottles_filled_total": "NoN:已填充瓶",
"stat.needsofnature.pregnancies_started_total": "NoN:已中奖",
"stat.needsofnature.pregnancies_completed_total": "NoN:已兑奖",
"stat.needsofnature.offspring_spawned_total": "NoN:已生成后代",
"stat.needsofnature.attack_animations_started_total": "NoN:开始被攻击动画次数",
"stat.needsofnature.attack_escapes_success_total": "NoN:从攻击中逃脱次数",
"stat.needsofnature.m_injector_peaks_ml_total": "NoN:摄入胶水量(毫升)",
"stat.needsofnature.manual_peaks_total": "NoN:手动动画次数",
"stat.needsofnature.bottles_filled_total": "NoN:收集胶水瓶次数",
"stat.needsofnature.pregnancies_started_total": "NoN:中奖次数",
"stat.needsofnature.pregnancies_completed_total": "NoN:兑奖次数",
"stat.needsofnature.offspring_spawned_total": "NoN:产下后代数量",
"advancements.needsofnature.root.title": "自然之需",
"advancements.needsofnature.root.description": "春天来了,万物复苏...",
"advancements.needsofnature.natural_curiosity.title": "自然之趣",
@@ -1075,7 +1091,7 @@
"advancements.needsofnature.filled_to_the_brim.title": "热腾腾的",
"advancements.needsofnature.filled_to_the_brim.description": "装满胶水。",
"advancements.needsofnature.favouritism.title": "三千宠爱于一身",
"advancements.needsofnature.favouritism.description": "只钟爱某一种生物的胶水,并把它填满了整个罐子。",
"advancements.needsofnature.favouritism.description": "使用同一品牌的胶水填满整个罐子。",
"advancements.needsofnature.first_pregnancy.title": "第一胎",
"advancements.needsofnature.first_pregnancy.description": "一个新生命悄然萌芽。",
"advancements.needsofnature.mother_nature.title": "自然之母",
@@ -1096,8 +1112,8 @@
"advancements.needsofnature.productive_pregnancy.description": "多胞胎。",
"advancements.needsofnature.udderly_unfortunate.title": "乳糖不耐",
"advancements.needsofnature.udderly_unfortunate.description": "有些时候不适合喝牛奶。",
"advancements.needsofnature.emergency_snack.title": "酸面包",
"advancements.needsofnature.emergency_snack.description": "喝下胶水瓶的胶水或被动喝下胶水。",
"advancements.needsofnature.emergency_snack.title": "§o酸面包",
"advancements.needsofnature.emergency_snack.description": "喝下胶水。",
"advancements.needsofnature.stable_diet.title": "百家饭",
"advancements.needsofnature.stable_diet.description": "连续喝下三次胶水,中间不食用其他食物。",
"advancements.needsofnature.acquire_hardware.title": "安装插件",
@@ -1111,7 +1127,7 @@
"advancements.needsofnature.sweet_tooth.title": "甜食爱好者",
"advancements.needsofnature.sweet_tooth.description": "把蜂蜜瓶装填进喷液枪里,然后瞄准关键部位。",
"advancements.needsofnature.shocking_experience.title": "触电般的体验",
"advancements.needsofnature.shocking_experience.description": "与一只闪电苦力怕共舞。",
"advancements.needsofnature.shocking_experience.description": "与一只闪电苦力怕“共舞”。",
"advancements.needsofnature.baby_on_board.title": "宝宝巴士",
"advancements.needsofnature.baby_on_board.description": "中奖状态下骑上任意载具。",
"advancements.needsofnature.head_in_the_clouds.title": "云层高空PLAY",
@@ -1310,21 +1326,5 @@
"toast.needsofnature.model_profile.figura": "您当前的 Figura 模型需要 NoN 纹理配置文件。打开 Minecraft 玩家皮肤/模型 > Figura 来生成它。",
"config.needsofnature.search": "搜索设置",
"config.needsofnature.search.placeholder": "搜索设置...",
"config.needsofnature.tooltip.search.clear": "清除搜索",
"key.needsofnature.block_requirement_visualization": "显示动画方块需求",
"message.needsofnature.block_requirement_visualization.enabled": "已启用动画方块需求视图",
"message.needsofnature.block_requirement_visualization.legend": "蓝色:需要 | 红色:空 | 橙色:反向匹配",
"message.needsofnature.block_requirement_visualization.disabled": "已禁用动画方块需求视图",
"message.needsofnature.block_requirement_visualization.none": "此动画无方块需求",
"message.needsofnature.block_requirement_visualization.truncated": "动画方块需求过大,已截断",
"message.needsofnature.block_requirement_visualization.unsupported": "服务器不支持动画方块需求",
"screen.needsofnature.model_alignment.scope.advanced": "Advanced",
"screen.needsofnature.model_alignment.read_only_advanced": "Read-only: %s is the advanced controller",
"screen.needsofnature.model_alignment.advanced_disabled": "Advanced offsets are disabled by the server",
"screen.needsofnature.model_alignment.advanced_unavailable": "No advanced controller is active",
"config.needsofnature.offsets.description.advanced": "When enabled by the server, an opted-in advanced controller can add a separate role-based offset of up to two blocks to any actor. Personal offsets remain intact.",
"config.needsofnature.advanced_offset_controller": "Advanced offset controller:",
"config.needsofnature.tooltip.advanced_offset_controller": "Opt in to control the additional role-based offsets for every actor when the server permits advanced controllers.",
"config.needsofnature.tooltip.advanced_offset_controller_disabled": "Advanced offset controllers are disabled by this server.",
"screen.needsofnature.figura_offsets.advanced_role": "Advanced: %s / %s"
"config.needsofnature.tooltip.search.clear": "清除搜索"
}
Executable
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env ts-node
import fs from 'fs';
// a sample tool that merge (zh|en)(sfw|nsfw) ver together for edit faster
// for other language translator: if you need to use this script, just edit `targetLangFiles` and `workFile`.
const targetLangFiles: LanguageInstance[] = [
{suffix: "en_us_nsfw", file: "NSFW (Default Pack)/en_us.json", overrideSuffix: "en_us_sfw", ro: true},
{suffix: "en_us_sfw", file: "SFW (baked in NoN)/en_us.json", ro: true},
{suffix: "zh_cn_nsfw", file: "NSFW (Default Pack)/zh_cn.json", overrideSuffix: "zh_cn_sfw"},
{suffix: "zh_cn_sfw", file: "SFW (baked in NoN)/zh_cn.json"}
]
const workFile = "zh_cn.json"
type LanguageInstance = {
suffix: string,
file: string,
data?: Record<string, string>,
overrideSuffix?: string,
override?: LanguageInstance,
ro?: boolean // read only, don't edit.
}
const mainLangFile = "SFW (baked in NoN)/en_us.json";
const targetLangFilesBySuffix: Record<string, LanguageInstance> = {}
for (let l of targetLangFiles) {
if (!l.suffix.match(/^[a-z0-9-_]+$/i)) throw new Error(`bad Suffix: ${JSON.stringify(l.suffix)} that not match ^[a-z0-9-_]+$ (ignore case)`)
targetLangFilesBySuffix[l.suffix] = l
}
for (let l of targetLangFiles) {
if (l.overrideSuffix) l.override = targetLangFilesBySuffix[l.overrideSuffix]
}
type KVPair = [string, string];
function parseOrdered(src: string): KVPair[] {
const o: KVPair[] = [];
JSON.parse(src, (k, v) => {
if (k === '') return v
if (typeof k !== "string" || typeof v !== "string") throw new Error(`unexpected k/v:${JSON.stringify([k, v])}`)
o.push([k, v])
return v;
});
return o;
}
function stringify(data: KVPair[]): string {
const s = JSON.stringify
const body = data.map(([k, v]) => ` ${s(k)}: ${s(v)}`).join(",\n")
return `{\n${body}\n}\n`
}
const keyOrder = parseOrdered(fs.readFileSync(mainLangFile, "utf-8")).map(([k, v]) => k)
function genMergedLang(): void {
for (const l of targetLangFiles) {
l.data = JSON.parse(fs.readFileSync(l.file, "utf-8")) as Record<string, string>
}
const output: KVPair[] = []
for (let key of keyOrder) {
for (let l of targetLangFiles) {
let value = l.data?.[key];
if (l.override?.data?.[key] === value) value = undefined
if (value) output.push([`${key}.${l.suffix}`, value])
}
}
fs.writeFileSync(workFile, stringify(output))
}
function writeBackLang() {
let mixed = parseOrdered(fs.readFileSync(workFile, "utf-8"));
const buffer: Record<string, KVPair[]> = {}
for (const key of Object.keys(targetLangFilesBySuffix)) buffer[key] = []
for (const [k, v] of mixed) {
const [key, suffix] = k.split(/\.(?=[^.]*$)/);
if (!buffer[suffix]) throw new Error(`can't parse the suffix: ${JSON.stringify(suffix)} from key: ${JSON.stringify(k)}`)
buffer[suffix].push([key, v])
}
for (let suffix in buffer) {
if (targetLangFilesBySuffix[suffix].ro) continue
fs.writeFileSync(targetLangFilesBySuffix[suffix].file, stringify(buffer[suffix]))
}
}
;(() => {
let files = [targetLangFiles.map(l => l.file), workFile].flat()
let latestFile = files.reduce<{ mtime: Date, fileName: string } | null>(
(latest, file) => {
if (!fs.existsSync(file)) return latest
let stat = fs.statSync(file);
if (!latest) return {mtime: stat.mtime, fileName: file};
return stat.mtime > latest.mtime ? {mtime: stat.mtime, fileName: file} : latest
}, null);
if (!latestFile) return
if (latestFile.fileName === workFile)
writeBackLang()
else
genMergedLang()
})();
+29
View File
@@ -0,0 +1,29 @@
{
"name": "NoN-Translations",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"@types/node": "^26.6.2"
}
},
"node_modules/@types/node": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz",
"integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.9.0"
}
},
"node_modules/undici-types": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
"dev": true,
"license": "MIT"
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"devDependencies": {
"@types/node": "^26.6.2"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"module": "nodenext",
"types": ["node"]
}
}
+3121
View File
File diff suppressed because it is too large Load Diff