Files
Tha.Les b1acc5b5b3 Add German, Portuguese, and Indonesian translations (#415)
* Add German, Portuguese, and Indonesian translations; fix i18n coverage gaps

Extends the existing English/Polish/Japanese/Simplified Chinese i18n system
to seven languages total. Also fixes several pre-existing i18n coverage gaps
found while auditing: the recent-tracks list, search placeholder, and trash
empty-state were hardcoding English text instead of using the translation
system; a presence-panel legend lacked data-i18n attributes; upload/job/
playlist error toasts were untranslated; and library list content did not
refresh on a live language switch. Widened the settings dropdown to fit the
longest new language name and the device-select to stop truncating longer
translated values.

Bumps the Unraid template pin to 0.13.0.

* Remove unused plural import in job.js

Flagged in PR review: job.js imports plural from i18n.js but never calls
it, only t().

* Native-speaker QA pass on all translations

Fixes real mistranslations (German "schleifen" for loop, "Skala" for musical
scale, Indonesian countdown/count-in mixup, Chinese Alpha badge), grammar bugs
(Polish aria-labels requiring an unavailable grammatical case, singular/plural
adjective agreement in playlist skip messages), inconsistent terminology
within each language, and a stray three-dot ellipsis instead of the single
character used everywhere else.

Converts playlist.skip.* from t() to plural() with proper singular/plural
forms across all seven languages, since Portuguese and Polish adjectives
don't inflect correctly as flat strings.

---------

Co-authored-by: Thales <>
2026-08-22 08:32:51 +01:00

75 lines
3.0 KiB
JavaScript

import { t, TRANSLATIONS } from "./i18n.js";
// Fallback list used before /api/config responds. Kept in sync with
// STEM_NAMES in app/core/config.py — the API is the canonical source.
export let STEM_NAMES = ["vocals", "drums", "bass", "guitar", "piano", "other"];
export let TRACK_NAMES = ["original", ...STEM_NAMES];
// Stems from the on-demand lead/backing vocal split (#275, EXTRA_STEM_NAMES
// in app/core/config.py). Not folded into STEM_NAMES/TRACK_NAMES -- most jobs
// never run this split -- but the player/mixer render them as real lanes,
// swapped in for "vocals", via effectiveStemOrder() below.
export let EXTRA_STEM_NAMES = ["lead_vocals", "backing_vocals"];
export async function syncStemNamesFromAPI() {
try {
const res = await fetch("/api/config");
if (!res.ok) return;
const data = await res.json();
if (Array.isArray(data.stem_names) && data.stem_names.length > 0) {
STEM_NAMES = data.stem_names;
TRACK_NAMES = ["original", ...STEM_NAMES];
}
if (Array.isArray(data.extra_stem_names) && data.extra_stem_names.length > 0) {
EXTRA_STEM_NAMES = data.extra_stem_names;
}
} catch (e) {
console.warn("[constants] failed to sync stem names from API:", e);
}
}
// The lane order for a specific job: STEM_NAMES with "vocals" replaced by
// lead_vocals + backing_vocals when a job's on-demand split (#275) has
// produced both. `presentNames` is the Set of stem names the job actually
// has (typically from job.stems). Order matters -- callers use this both to
// decide what to render and in what sequence (waveform stacking, mixer rows).
export function effectiveStemOrder(presentNames) {
const splitDone = presentNames.has("lead_vocals") && presentNames.has("backing_vocals");
return STEM_NAMES.flatMap((n) => (n === "vocals" && splitDone ? EXTRA_STEM_NAMES : [n]));
}
// A Proxy, not a plain object, so every lookup resolves through the CURRENT
// language live -- a plain object would freeze these labels in whatever
// language was active the moment this module first loaded. Callers keep their
// existing `STEM_DISPLAY[name] || name` fallback pattern unchanged: an
// unrecognized name (not one of ours) still yields undefined, same as a plain
// object would, rather than the i18n engine's own missing-key fallback
// (which returns the raw dictionary key string -- wrong here).
export const STEM_DISPLAY = new Proxy({}, {
get(_target, prop) {
if (typeof prop !== "string") return undefined;
const key = `stem.${prop}`;
return key in TRANSLATIONS.en ? t(key) : undefined;
},
});
// FL Studio-style channel palette: saturated but slightly dusty, designed
// to read well on a dark background.
export const STEM_COLORS = {
vocals: "#e85f6f",
drums: "#e89048",
bass: "#e8b848",
guitar: "#88d878",
piano: "#b88fe8",
other: "#88a8c8",
original: "#a8b0bd",
lead_vocals: "#e8748a",
backing_vocals: "#c98fe0",
};
export const PROGRESS_COLOR = "#3a3a3a";
export const LOOP_DEFAULT_START_FRAC = 0.25;
export const LOOP_DEFAULT_END_FRAC = 0.5;
export const LANE_VOLUME_MAX = 2;