Add chinese translation

Based on https://github.com/dyxang/Fantasy-Map-Generator-Chinese
This commit is contained in:
Blipz 2026-03-28 04:59:26 +01:00
parent 7eeb1f76c3
commit c5f1577c4b
12 changed files with 3359 additions and 980 deletions

View file

@ -8,7 +8,7 @@ const HTMLPlugin: Plugin = {
const content = await readFile("src/index.html", "utf-8");
const matches = content.matchAll(/data-(?:html|text|tip)="([^"]+)"/g);
for (const match of matches) {
const key = match[1];
const key = match[1].replace(/"/g, '"');
keys.set(key, {key, defaultValue: key});
}
}
@ -17,13 +17,15 @@ const HTMLPlugin: Plugin = {
export default defineConfig({
locales: [
"en",
"fr"
"fr",
"zh"
],
extract: {
input: "src/**/*.{js,ts}",
output: "public/locales/{{language}}/{{namespace}}.json",
defaultNS: "lang",
keySeparator: false
keySeparator: "::",
nsSeparator: false,
},
plugins: [
HTMLPlugin

View file

@ -234,10 +234,10 @@
.icon-temperature-low:before {content:'\f76b';} /* '' */
/* Amended FA icons */
.icon-sort-name-up:after {font-size:.9em;content:'\f15d';}
.icon-sort-name-down:after {font-size:.9em;content:'\f15e';}
.icon-sort-number-up:after {font-size:.9em;content:'\f162';}
.icon-sort-number-down:after {font-size:.9em;content:'\f163';}
.icon-sort-name-up:after {font-size:.9em;content:'\00a0\f15d';}
.icon-sort-name-down:after {font-size:.9em;content:'\00a0\f15e';}
.icon-sort-number-up:after {font-size:.9em;content:'\00a0\f162';}
.icon-sort-number-down:after {font-size:.9em;content:'\00a0\f163';}
/* Custom icons */
.icon-w:before {font-style:italic;content:'w:';}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1620
public/locales/zh/lang.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -523,8 +523,8 @@ function applyStoredOptions() {
if (stored("tooltipSize")) changeTooltipSize(stored("tooltipSize"));
if (stored("regions")) changeStatesNumber(stored("regions"));
if (stored("language")) changeLanguage(stored("language"));
else changeLanguage("en");
if (stored("language")) setLanguage(stored("language"));
else setLanguage("en");
uiSize.max = uiSize.max = getUImaxSize();
if (stored("uiSize")) changeUiSize(+stored("uiSize"));
@ -659,10 +659,15 @@ function changeEra() {
options.era = eraInput.value;
}
function changeLanguage(value) {
function setLanguage(value) {
options.language = value;
}
function changeLanguage(value) {
setLanguage(value);
changeLocale();
}
async function openTemplateSelectionDialog() {
const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.96.00");
HeightmapSelectionDialog.open();

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,49 @@
import i18next from "i18next";
import i18nextHTTPBackend from "i18next-http-backend";
import { isVowel } from "../utils";
import { TOptions } from "i18next";
function initTooltips() {
for (const tip of document.querySelectorAll("[data-tip]")) {
tip.setAttribute(
"data-original-tip",
tip.getAttribute("data-tip") as string,
);
}
}
function updateLabels() {
for (const label of document.querySelectorAll("[data-html]")) {
const translation = i18next.t(label.getAttribute("data-html") as string);
let vars: TOptions = {
interpolation: { escapeValue: false },
};
for (const attr of label.attributes) {
if (attr.name.startsWith("data-var-")) {
vars[attr.name.slice(9)] = attr.value;
}
}
const translation = i18next.t(
label.getAttribute("data-html") as string,
vars,
);
if (translation) label.innerHTML = translation;
}
for (const label of document.querySelectorAll("[data-text]")) {
const translation = i18next.t(label.getAttribute("data-text") as string);
if (translation) label.textContent = translation;
}
for (const tip of document.querySelectorAll("[data-tip]")) {
const translation = i18next.t(tip.getAttribute("data-tip") as string);
for (const tip of document.querySelectorAll("[data-original-tip]")) {
const translation = i18next.t(
tip.getAttribute("data-original-tip") as string,
);
if (translation) tip.setAttribute("data-tip", translation);
}
}
function addFormatters() {
i18next.services.formatter?.add("link", (value, _lng, options) => {
return `<a href="${value}" target="blank">${options.text}</a>`;
});
i18next.services.formatter?.add("gender", (value, lng, options) => {
if (lng !== "fr") return value;
else if (options.gender === "feminine") {
@ -43,13 +69,22 @@ window.initLocale = async () => {
await i18next.use(i18nextHTTPBackend).init(
{
lng: options.language,
fallbackLng: "en",
backend: {
loadPath: "locales/{{lng}}/lang.json",
},
keySeparator: "::",
nsSeparator: false,
returnEmptyString: false,
},
() => {
addFormatters();
initTooltips();
updateLabels();
},
);
};
window.changeLocale = () => {
i18next.changeLanguage(options.language, updateLabels);
};

View file

@ -144,7 +144,7 @@ class ProvinceModule {
form[formName] += 10;
const fullName = i18next.t("{{provinceName}} {{provinceForm}}", {
provinceName: name,
provinceForm: i18next.t(formName),
provinceForm: i18next.t(`provinceForm::${formName}`),
});
const color = getMixedColor(s.color!);
const kinship = nameByBurg ? 0.8 : 0.4;
@ -327,7 +327,7 @@ class ProvinceModule {
const fullName = i18next.t("{{provinceName}} {{provinceForm}}", {
provinceName: name,
provinceForm: i18next.t(formName),
provinceForm: i18next.t(`provinceForm::${formName}`),
});
const dominion = colony

View file

@ -64,6 +64,7 @@ gender.fr = {
Diarchy: "feminine",
Horde: "feminine",
League: "feminine",
Marches: "feminine",
Oligarchy: "feminine",
Tetrarchy: "feminine",
Theocracy: "feminine",
@ -834,7 +835,7 @@ class StatesModule {
const stateGender = gender[options.language as string]?.[state.formName];
if (!state.name && state.formName)
return i18next.t("The {{noun}}", {
noun: i18next.t(state.formName),
noun: i18next.t(`stateForm::${state.formName}`),
gender: stateGender,
});
const adjName =
@ -842,13 +843,14 @@ class StatesModule {
return adjName
? i18next.t("{{adjective}} {{noun}}", {
adjective: getAdjective(state.name),
noun: i18next.t(state.formName),
noun: i18next.t(`stateForm::${state.formName}`),
gender: stateGender,
})
: i18next.t("{{noun}} of {{complement}}", {
noun: i18next.t(state.formName),
noun: i18next.t(`stateForm::${state.formName}`),
complement: state.name,
gender: stateGender,
interpolation: { escapeValue: false },
});
}
}

View file

@ -90,4 +90,5 @@ declare global {
var changeFont: () => void;
var getFriendlyHeight: (coords: [number, number]) => string;
var initLocale: () => void;
var changeLocale: () => void;
}

View file

@ -329,7 +329,9 @@ export const getAdjective = (nounToBeAdjective: string) => {
},
],
};
for (const rule of adjectivizationRules[options.language]) {
const rules =
adjectivizationRules[options.language] ?? adjectivizationRules.en;
for (const rule of rules) {
if (P(rule.probability) && rule.condition.test(nounToBeAdjective)) {
return rule.action(nounToBeAdjective);
}