mirror of
https://github.com/gogs/gogs.git
synced 2026-10-02 19:56:43 +02:00
238 lines
6.3 KiB
JavaScript
238 lines
6.3 KiB
JavaScript
// Extracts the subset of keys the SPA needs from conf/locale/locale_*.ini and
|
|
// writes them as JSON under web/src/locales/. Run with `node scripts/extract-locales.mjs`
|
|
// after adding a new key or changing source translations.
|
|
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = resolve(here, "../..");
|
|
const inDir = join(repoRoot, "conf/locale");
|
|
const outDir = join(here, "..", "src/locales");
|
|
|
|
// Keys pulled from Gogs's INI files. Add new entries here when the SPA needs
|
|
// another translation. Locales missing a key fall back to en-US via react-i18next.
|
|
const KEYS = [
|
|
"app_desc",
|
|
"home",
|
|
"dashboard",
|
|
"issues",
|
|
"pull_requests",
|
|
"explore",
|
|
"help",
|
|
"register",
|
|
"sign_in",
|
|
"sign_out",
|
|
"create_new",
|
|
"new_repo",
|
|
"new_migrate",
|
|
"new_org",
|
|
"signed_in_as",
|
|
"user_profile_and_more",
|
|
"your_profile",
|
|
"your_settings",
|
|
"admin_panel",
|
|
"settings",
|
|
"language_current",
|
|
"repository",
|
|
"theme",
|
|
"theme_light",
|
|
"theme_dark",
|
|
"theme_system",
|
|
"username",
|
|
"username_placeholder",
|
|
"new_username_placeholder",
|
|
"email",
|
|
"email_placeholder",
|
|
"password",
|
|
"password_placeholder",
|
|
"captcha",
|
|
"captcha_placeholder",
|
|
"captcha_image_alt",
|
|
"refresh_captcha",
|
|
"click_to_refresh_captcha",
|
|
"close",
|
|
"show_more",
|
|
"show_less",
|
|
"resize_sidebar",
|
|
"copy_failed",
|
|
"more",
|
|
"more_tabs",
|
|
"more_actions",
|
|
"status.page_not_found",
|
|
"status.internal_server_error",
|
|
"auth.auth_source",
|
|
"auth.local",
|
|
"auth.forget_password",
|
|
"auth.send_reset_email",
|
|
"auth.reset_password_email_submitting",
|
|
"auth.reset_password_email_failed",
|
|
"auth.reset_password_email_sent",
|
|
"auth.disable_register_mail",
|
|
"auth.disable_register_prompt",
|
|
"auth.reset_password_resend_limited",
|
|
"auth.non_local_account",
|
|
"auth.create_new_account",
|
|
"auth.register_hepler_msg",
|
|
"auth.sign_up_now",
|
|
"auth.sign_up_submitting",
|
|
"auth.sign_up_failed",
|
|
"auth.sign_in_submitting",
|
|
"auth.sign_in_failed",
|
|
"auth.show_password",
|
|
"auth.hide_password",
|
|
"auth.back_to_sign_in",
|
|
"auth.reset_password",
|
|
"auth.invalid_code",
|
|
"auth.reset_password_submit",
|
|
"auth.reset_password_submitting",
|
|
"auth.reset_password_failed",
|
|
"auth.new_password",
|
|
"auth.new_password_placeholder",
|
|
"auth.confirm_password",
|
|
"auth.confirm_password_placeholder",
|
|
"auth.confirm_new_password",
|
|
"auth.confirm_new_password_placeholder",
|
|
"auth.password_mismatch",
|
|
"auth.mfa_title",
|
|
"auth.mfa_passcode",
|
|
"auth.mfa_passcode_placeholder",
|
|
"auth.mfa_recovery_code",
|
|
"auth.mfa_recovery_code_placeholder",
|
|
"auth.mfa_use_recovery_code",
|
|
"auth.mfa_use_passcode",
|
|
"auth.mfa_verify",
|
|
"auth.mfa_verifying",
|
|
"auth.mfa_session_expired",
|
|
"auth.mfa_verify_failed",
|
|
"auth.activate_your_account",
|
|
"auth.resend_rate_limited",
|
|
"auth.send_activation_email",
|
|
"auth.check_activation_email",
|
|
"auth.activation_email_pending",
|
|
"auth.activation_email_sent",
|
|
"auth.sending_activation_email",
|
|
"auth.send_activation_email_failed",
|
|
"auth.activating_account",
|
|
"tool.now",
|
|
"tool.ago",
|
|
"tool.from_now",
|
|
"tool.1s",
|
|
"tool.1m",
|
|
"tool.1h",
|
|
"tool.1d",
|
|
"tool.1w",
|
|
"tool.1mon",
|
|
"tool.1y",
|
|
"tool.seconds",
|
|
"tool.minutes",
|
|
"tool.hours",
|
|
"tool.days",
|
|
"tool.weeks",
|
|
"tool.months",
|
|
"tool.years",
|
|
"repo.diff.showing_changed_files",
|
|
"repo.diff.additions",
|
|
"repo.diff.deletions",
|
|
"repo.diff.unified",
|
|
"repo.diff.split",
|
|
"repo.diff.diff_settings",
|
|
"repo.diff.whitespace",
|
|
"repo.diff.show_whitespace",
|
|
"repo.diff.ignore_whitespace_changes",
|
|
"repo.diff.ignore_all_whitespace",
|
|
"repo.diff.display",
|
|
"repo.diff.wrap_long_lines",
|
|
"repo.diff.expand_all_files",
|
|
"repo.diff.collapse_all_files",
|
|
"repo.show_file_tree",
|
|
"repo.hide_file_tree",
|
|
"repo.expand_all_directories",
|
|
"repo.collapse_all_directories",
|
|
"repo.search_files",
|
|
"repo.search_hide",
|
|
"repo.search_diff",
|
|
"repo.search_previous_match",
|
|
"repo.search_next_match",
|
|
"repo.diff.expand_file",
|
|
"repo.diff.collapse_file",
|
|
"repo.diff.expand_all_lines",
|
|
"repo.diff.unmodified_line",
|
|
"repo.diff.unmodified_lines",
|
|
"repo.diff.no_newline_at_eof",
|
|
"repo.commit_parent",
|
|
"repo.commit_label",
|
|
"repo.view_file",
|
|
"repo.editor.edit_file",
|
|
"repo.editor.delete_this_file",
|
|
"repo.files",
|
|
"repo.settings",
|
|
"repo.wiki",
|
|
"repo.watch",
|
|
"repo.unwatch",
|
|
"repo.star",
|
|
"repo.starred",
|
|
"repo.fork",
|
|
"repo.mirror_of",
|
|
"repo.sign_in_to_watch",
|
|
"repo.sign_in_to_star",
|
|
"repo.sign_in_to_fork",
|
|
"repo.watch_this_repository",
|
|
"repo.unwatch_this_repository",
|
|
"repo.star_this_repository",
|
|
"repo.unstar_this_repository",
|
|
"repo.fork_this_repository",
|
|
"repo.visibility_private",
|
|
"repo.visibility_public",
|
|
"repo.view_watchers",
|
|
"repo.view_stargazers",
|
|
"repo.view_forks",
|
|
"repo.browse_files",
|
|
"repo.view_history",
|
|
"repo.view_raw",
|
|
"repo.copy_file_path",
|
|
"repo.copy_full_sha",
|
|
"repo.renamed_from",
|
|
"repo.authored",
|
|
"repo.parents",
|
|
"repo.diff_label",
|
|
"repo.patch_label",
|
|
];
|
|
|
|
// Parse the INI into a single `section.key` to value map. Top-level keys
|
|
// (above any section header) are stored bare. First occurrence wins.
|
|
function parseIni(text) {
|
|
const out = {};
|
|
let section = "";
|
|
for (const rawLine of text.split(/\r?\n/)) {
|
|
const line = rawLine.trim();
|
|
if (!line || line.startsWith(";") || line.startsWith("#")) continue;
|
|
if (line.startsWith("[") && line.endsWith("]")) {
|
|
section = line.slice(1, -1).trim();
|
|
continue;
|
|
}
|
|
const eq = line.indexOf("=");
|
|
if (eq < 0) continue;
|
|
const key = line.slice(0, eq).trim();
|
|
const value = line.slice(eq + 1).trim();
|
|
if (!key) continue;
|
|
const qualified = section ? `${section}.${key}` : key;
|
|
if (!(qualified in out)) out[qualified] = value;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
const files = readdirSync(inDir).filter((f) => f.startsWith("locale_") && f.endsWith(".ini"));
|
|
for (const file of files) {
|
|
const lang = file.slice("locale_".length, -".ini".length);
|
|
const parsed = parseIni(readFileSync(join(inDir, file), "utf8"));
|
|
const out = {};
|
|
for (const key of KEYS) {
|
|
if (parsed[key]) out[key] = parsed[key];
|
|
}
|
|
writeFileSync(join(outDir, `${lang}.json`), JSON.stringify(out, null, 2) + "\n", "utf8");
|
|
console.log(`wrote ${lang}.json (${Object.keys(out).length} keys)`);
|
|
}
|