mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 11:17:01 -04:00
commit
753c9a99e7
12 files changed
+3728
-13
No files matched your search
@@ -9,6 +9,7 @@ iconography, images or other Adobe assets; open licences only; every asset attri
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `crates/ui-egui/src/i18n/it.tsv` | FilmCraft contributors | Original work: clean-room Italian interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/id.tsv` | FilmCraft contributors | Original work: clean-room Indonesian interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/ko.tsv` | FilmCraft contributors | Original work: clean-room Korean interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/fr.tsv` | FilmCraft contributors | Original work: clean-room French interface translations | MIT OR Apache-2.0 |
|
||||
|
||||
@@ -84,6 +84,7 @@ Premiere ships 10 interface languages. Measured from our catalogs (3,498 strings
|
||||
| French | partial (whole interface; native review welcome) | ~100% |
|
||||
| Portuguese (Brazil) | partial (whole interface; native review welcome) | ~100% |
|
||||
| Indonesian | partial (whole interface; native review welcome) | ~100% |
|
||||
| Italian | partial (whole interface; native review welcome) | ~100% |
|
||||
| Korean | partial (whole interface; needs an installed Hangul font; native review welcome) | ~100% |
|
||||
| Japanese | partial (whole interface) | 99.7% |
|
||||
| German | menus only (native-reviewed) | 10.6% |
|
||||
|
||||
@@ -131,6 +131,8 @@ pub fn install(app: &FilmcraftApp, ctx: egui::Context) -> (Receiver<String>, Men
|
||||
filmcraft_ui_egui::i18n::Language::Fr
|
||||
} else if checked("app.language.indonesian") {
|
||||
filmcraft_ui_egui::i18n::Language::Id
|
||||
} else if checked("app.language.italian") {
|
||||
filmcraft_ui_egui::i18n::Language::It
|
||||
} else if checked("app.language.korean") {
|
||||
filmcraft_ui_egui::i18n::Language::Ko
|
||||
} else {
|
||||
|
||||
@@ -34,7 +34,7 @@ pub const PREFS_VERSION: u32 = 2;
|
||||
#[serde(rename_all = "camelCase", default)]
|
||||
pub struct GeneralPrefs {
|
||||
/// Interface language (persisted independently of projects): `en`, `ja`, `es`, `pt-br`, `uk`,
|
||||
/// `zh-cn`, `de`, `ru`, `fr`, `id`, or `system` (the default: the operating system's preferred language when the
|
||||
/// `zh-cn`, `de`, `ru`, `fr`, `id`, `it`, or `system` (the default: the operating system's preferred language when the
|
||||
/// interface has it, otherwise English).
|
||||
pub interface_language: String,
|
||||
/// "At Startup": `showHome` (FilmCraft: the demo project), `openMostRecent`, `emptyProject`.
|
||||
@@ -1007,6 +1007,7 @@ static CATEGORIES: &[Category] = &[
|
||||
("ru", "Русский"),
|
||||
("fr", "Français"),
|
||||
("id", "Bahasa Indonesia"),
|
||||
("it", "Italiano"),
|
||||
("ko", "한국어"),
|
||||
]),
|
||||
true,
|
||||
|
||||
@@ -44,6 +44,12 @@ fn interface_language_persists_and_rejects_hostile_values() {
|
||||
}
|
||||
set(&mut s, "general.interfaceLanguage", json!("id"));
|
||||
assert_eq!(Preferences::load(&path).general.interface_language, "id");
|
||||
// Italian is stored as `it`, not a locale tag
|
||||
for value in [json!("it-IT"), json!("IT")] {
|
||||
assert!(s.execute("prefs.set", json!({"key": "general.interfaceLanguage", "value": value})).is_err());
|
||||
}
|
||||
set(&mut s, "general.interfaceLanguage", json!("it"));
|
||||
assert_eq!(Preferences::load(&path).general.interface_language, "it");
|
||||
// back to following the operating system (#218)
|
||||
set(&mut s, "general.interfaceLanguage", json!("system"));
|
||||
assert_eq!(Preferences::load(&path).general.interface_language, "system");
|
||||
|
||||
@@ -18,7 +18,7 @@ the committed span and undo. Set `FILMCRAFT_UI_SNAPSHOT_DIR` to render screensho
|
||||
## Localisation
|
||||
|
||||
Edit > Language offers English, Japanese, Spanish, Brazilian Portuguese, Ukrainian, Simplified
|
||||
Chinese, German, Russian, French, Indonesian and Korean (which cover the whole interface). The language is stored in the engine's `general.interfaceLanguage` preference,
|
||||
Chinese, German, Russian, French, Indonesian, Korean and Italian (which cover the whole interface). The language is stored in the engine's `general.interfaceLanguage` preference,
|
||||
restored on startup, and also available in Settings > General.
|
||||
Its default, System Language (`system`), follows the operating system: the first of the user's
|
||||
preferred languages that the interface has (the host supplies them through
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
||||
asset: it.tsv
|
||||
title: Italian interface translation catalog
|
||||
author: FilmCraft contributors
|
||||
source: original work
|
||||
license: MIT OR Apache-2.0
|
||||
license-file: LICENSE-MIT and LICENSE-APACHE
|
||||
added: 2026-10-12 by Nai-ITA
|
||||
notes: Original English-to-Italian interface translations using clean-room NLE vocabulary.
|
||||
@@ -46,6 +46,7 @@ pub enum Language {
|
||||
Ru,
|
||||
Fr,
|
||||
Id,
|
||||
It,
|
||||
/// Korean; Hangul needs an installed system font ([`korean_font_available`]).
|
||||
Ko,
|
||||
}
|
||||
@@ -60,9 +61,10 @@ static RUSSIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static FRENCH: OnceLock<Catalog> = OnceLock::new();
|
||||
static KOREAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static INDONESIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static ITALIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
|
||||
impl Language {
|
||||
pub const ALL: [Self; 11] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id, Self::Ko];
|
||||
pub const ALL: [Self; 12] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id, Self::Ko, Self::It];
|
||||
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
@@ -76,6 +78,7 @@ impl Language {
|
||||
Self::Ru => "Русский",
|
||||
Self::Fr => "Français",
|
||||
Self::Id => "Bahasa Indonesia",
|
||||
Self::It => "Italiano",
|
||||
Self::Ko => "한국어",
|
||||
}
|
||||
}
|
||||
@@ -92,6 +95,7 @@ impl Language {
|
||||
"ru" => Some(Self::Ru),
|
||||
"fr" => Some(Self::Fr),
|
||||
"id" => Some(Self::Id),
|
||||
"it" => Some(Self::It),
|
||||
"ko" => Some(Self::Ko),
|
||||
_ => None,
|
||||
}
|
||||
@@ -102,11 +106,12 @@ impl Language {
|
||||
/// interface has, else English. Any Portuguese gets the Brazilian catalog, the only one there is;
|
||||
/// any German (`de-AT`, `de_CH.UTF-8`…) gets the German one and any French (`fr-CA`, `fr_BE.UTF-8`…)
|
||||
/// the French one; any Indonesian (`id-ID`, `id_ID.UTF-8`, and the legacy `in-ID` that older Java
|
||||
/// and Android runtimes report) the Indonesian one. Chinese gets the Simplified catalog
|
||||
/// and Android runtimes report) the Indonesian one; any Italian (`it-IT`, `it-CH`, `it_IT.UTF-8`…)
|
||||
/// the Italian one. Chinese gets the Simplified catalog
|
||||
/// only for Simplified locales (`zh`, `zh-CN`, `zh-SG`, `zh-Hans…`); Traditional ones (`zh-TW`,
|
||||
/// `zh-HK`, `zh-MO`, `zh-Hant…`) are skipped.
|
||||
pub fn from_locales(tags: &[String]) -> Self {
|
||||
const PRIMARY: [(&str, Language); 11] = [
|
||||
const PRIMARY: [(&str, Language); 12] = [
|
||||
("en", Language::En),
|
||||
("ja", Language::Ja),
|
||||
("es", Language::Es),
|
||||
@@ -117,6 +122,7 @@ impl Language {
|
||||
("fr", Language::Fr),
|
||||
("id", Language::Id),
|
||||
("in", Language::Id),
|
||||
("it", Language::It),
|
||||
("ko", Language::Ko),
|
||||
];
|
||||
tags.iter()
|
||||
@@ -148,6 +154,7 @@ impl Language {
|
||||
Self::Ru => "ru",
|
||||
Self::Fr => "fr",
|
||||
Self::Id => "id",
|
||||
Self::It => "it",
|
||||
Self::Ko => "ko",
|
||||
}
|
||||
}
|
||||
@@ -165,6 +172,7 @@ impl Language {
|
||||
Self::Ru => Some(RUSSIAN.get_or_init(|| Catalog::parse(include_str!("ru.tsv")))),
|
||||
Self::Fr => Some(FRENCH.get_or_init(|| Catalog::parse(include_str!("fr.tsv")))),
|
||||
Self::Id => Some(INDONESIAN.get_or_init(|| Catalog::parse(include_str!("id.tsv")))),
|
||||
Self::It => Some(ITALIAN.get_or_init(|| Catalog::parse(include_str!("it.tsv")))),
|
||||
Self::Ko => Some(KOREAN.get_or_init(|| Catalog::parse(include_str!("ko.tsv")))),
|
||||
}
|
||||
}
|
||||
@@ -344,6 +352,7 @@ mod tests {
|
||||
("ru", include_str!("ru.tsv")),
|
||||
("fr", include_str!("fr.tsv")),
|
||||
("id", include_str!("id.tsv")),
|
||||
("it", include_str!("it.tsv")),
|
||||
("ko", include_str!("ko.tsv")),
|
||||
] {
|
||||
let (entries, errors) = catalog::parse_entries(text);
|
||||
@@ -399,6 +408,13 @@ mod tests {
|
||||
assert_eq!(Language::Id.tr("An untranslated label"), "An untranslated label");
|
||||
assert_eq!(Language::parse("id"), Some(Language::Id));
|
||||
assert_eq!(Language::parse("id-ID"), None, "the preference stores the bare code");
|
||||
assert_eq!(Language::It.name(), "Italiano");
|
||||
assert_eq!(Language::It.tr("File"), "File");
|
||||
assert_eq!(Language::It.tr("Save As…"), "Salva con nome…");
|
||||
assert_eq!(Language::It.tr("Vacanze d'estate.mp4"), "Vacanze d'estate.mp4");
|
||||
assert_eq!(Language::It.tr("An untranslated label"), "An untranslated label");
|
||||
assert_eq!(Language::parse("it"), Some(Language::It));
|
||||
assert_eq!(Language::parse("it-IT"), None, "the preference stores the bare code");
|
||||
for l in Language::ALL {
|
||||
assert_eq!(Language::parse(l.code()), Some(l));
|
||||
let json = serde_json::to_string(&l).unwrap();
|
||||
@@ -963,6 +979,24 @@ mod tests {
|
||||
let mut restarted = filmcraft_engine::Session::default();
|
||||
restarted.prefs = serde_json::from_str(&prefs).unwrap();
|
||||
assert_eq!(crate::FilmcraftApp::new(restarted).ui.language, Language::Id);
|
||||
let result = crate::menus::invoke(&mut app, &ctx, "app.language.italian", serde_json::json!({})).unwrap();
|
||||
assert_eq!(result, serde_json::json!("it"));
|
||||
assert_eq!((app.ui.language, current()), (Language::It, Language::It));
|
||||
assert_eq!(app.session.prefs.general.interface_language, "it");
|
||||
for it in crate::menus::menu_items(&app).iter().filter(|it| it.id.starts_with("app.language.")) {
|
||||
assert_eq!(it.checked, Some(it.id == "app.language.italian"), "{}", it.id);
|
||||
}
|
||||
let items = crate::menus::menu_items(&app);
|
||||
let label = |id: &str| items.iter().find(|it| it.id == id).map(|it| it.label.as_str());
|
||||
assert_eq!(label("file.saveAs"), Some("Salva con nome…"));
|
||||
assert_eq!(label("app.language.indonesian"), Some("Bahasa Indonesia"));
|
||||
assert_eq!(label("app.language.italian"), Some("Italiano"));
|
||||
let saved = serde_json::to_string(&app.ui).unwrap();
|
||||
assert!(saved.contains("\"language\":\"it\""), "{saved}");
|
||||
let prefs = serde_json::to_string(&app.session.prefs).unwrap();
|
||||
let mut restarted = filmcraft_engine::Session::default();
|
||||
restarted.prefs = serde_json::from_str(&prefs).unwrap();
|
||||
assert_eq!(crate::FilmcraftApp::new(restarted).ui.language, Language::It);
|
||||
crate::menus::invoke(&mut app, &ctx, "app.language.english", serde_json::json!({})).unwrap();
|
||||
assert_eq!(app.ui.language, Language::En);
|
||||
set_current(Language::En);
|
||||
@@ -1001,12 +1035,19 @@ mod tests {
|
||||
for none in [&["ind"][..], &["i"], &["-id"], &["ms"], &["ms-ID"], &["jv"]] {
|
||||
assert_eq!(l(none), Language::En, "{none:?}");
|
||||
}
|
||||
assert_eq!(l(&["it-IT", "nl", "es-MX", "ja"]), Language::Es, "the first one the interface has");
|
||||
assert_eq!(l(&["it-CH", "de-CH", "fr-CH", "en"]), Language::De);
|
||||
assert_eq!(l(&["it-CH", "fr-CH", "de-CH", "en"]), Language::Fr);
|
||||
for tag in ["it", "IT", "it-IT", "it-CH", "it-SM", "it_IT.UTF-8", "it_IT.UTF-8@euro", "it_CH"] {
|
||||
assert_eq!(l(&[tag]), Language::It, "{tag}");
|
||||
}
|
||||
assert_eq!(l(&["it-CH", "de-CH", "fr-CH", "en"]), Language::It);
|
||||
for none in [&["ita"][..], &["i"], &["-it"]] {
|
||||
assert_eq!(l(none), Language::En, "{none:?}");
|
||||
}
|
||||
assert_eq!(l(&["sv-SE", "nl", "es-MX", "ja"]), Language::Es, "the first one the interface has");
|
||||
assert_eq!(l(&["rm-CH", "de-CH", "fr-CH", "en"]), Language::De);
|
||||
assert_eq!(l(&["rm-CH", "fr-CH", "de-CH", "en"]), Language::Fr);
|
||||
assert_eq!(l(&["en-GB", "es"]), Language::En);
|
||||
assert_eq!(l(&["en-US", "de"]), Language::En);
|
||||
for none in [&[][..], &["it"], &["C"], &["POSIX"], &[""], &["e"], &["esp"], &["-es"], &["deu"], &["d"], &["-de"], &["fra"], &["f"], &["-fr"]] {
|
||||
for none in [&[][..], &["nl"], &["C"], &["POSIX"], &[""], &["e"], &["esp"], &["-es"], &["deu"], &["d"], &["-de"], &["fra"], &["f"], &["-fr"]] {
|
||||
assert_eq!(l(none), Language::En, "{none:?}");
|
||||
}
|
||||
assert_eq!(l(&[&"x".repeat(1 << 20), "es"]), Language::Es);
|
||||
@@ -1029,7 +1070,7 @@ mod tests {
|
||||
let count = asked.clone();
|
||||
app.hooks.system_languages = Some(Box::new(move || {
|
||||
count.set(count.get() + 1);
|
||||
vec!["it-IT".into(), "es-ES".into()]
|
||||
vec!["nl-NL".into(), "es-ES".into()]
|
||||
}));
|
||||
app.apply_prefs(&ctx);
|
||||
assert_eq!(app.ui.language, Language::Es);
|
||||
@@ -1191,6 +1232,40 @@ mod tests {
|
||||
set_current(Language::En);
|
||||
}
|
||||
|
||||
/// Italian covers the whole interface (the Spanish catalog's entries and the native macOS
|
||||
/// Settings menu); like Indonesian, the tests require only the core menus, so strings added
|
||||
/// later show in English until they are translated, and every entry must have a Spanish
|
||||
/// counterpart.
|
||||
#[test]
|
||||
fn italian_covers_the_menus_and_has_no_dead_entries() {
|
||||
let (entries, errors) = catalog::parse_entries(include_str!("it.tsv"));
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
for (ctx, en, _) in &entries {
|
||||
assert!(ctx.is_empty() && (Language::Es.has(en) || en == "Settings"), "Italian entry without a Spanish one: {ctx:?} {en:?}");
|
||||
}
|
||||
let (core, _) = catalog::parse_entries(include_str!("uk.tsv"));
|
||||
for (_, en, _) in core {
|
||||
assert!(Language::It.has(&en), "missing Italian menu label: {en}");
|
||||
}
|
||||
for en in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) {
|
||||
assert!(Language::It.has(en), "untranslated Italian menu: {en}");
|
||||
}
|
||||
// beyond the menus: panels, effects and their parameters, settings, status messages
|
||||
for (en, it) in [
|
||||
("Settings", "Impostazioni"),
|
||||
("Gaussian Blur", "Sfocatura gaussiana"),
|
||||
("Audio Track Mixer", "Mixer tracce audio"),
|
||||
("Sequence", "Sequenza"),
|
||||
("Playhead", "Indicatore di riproduzione"),
|
||||
] {
|
||||
assert_eq!(Language::It.tr(en), it);
|
||||
}
|
||||
set_current(Language::It);
|
||||
assert!(matches_query("Gaussian Blur", "sfocatura"));
|
||||
assert!(matches_query("Gaussian Blur", "gaussian"));
|
||||
set_current(Language::En);
|
||||
}
|
||||
|
||||
/// Every non-ASCII character of `text` renders with the bundled interface fonts, in every family.
|
||||
fn assert_bundled_fonts_cover(text: impl Iterator<Item = char>) {
|
||||
let ctx = egui::Context::default();
|
||||
@@ -1253,6 +1328,16 @@ mod tests {
|
||||
assert_bundled_fonts_cover(Language::Id.name().chars().chain(letters));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn italian_renders_with_bundled_fonts() {
|
||||
let (entries, _) = catalog::parse_entries(include_str!("it.tsv"));
|
||||
// every Italian accented letter, even ones no entry uses yet, and every letter, curly quote,
|
||||
// dash and ellipsis the entries use (symbols such as ✔ come from the English sources)
|
||||
let alphabet = "ÀÈÉÌÍÎÒÓÙÚàèéìíîòóùú";
|
||||
let letters = entries.iter().flat_map(|(_, _, it)| it.chars()).filter(|ch| ch.is_alphabetic() || "“”‘’–—…".contains(*ch));
|
||||
assert_bundled_fonts_cover(alphabet.chars().chain(Language::It.name().chars()).chain(letters));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn japanese_needs_an_installed_font() {
|
||||
let mut app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
|
||||
|
||||
@@ -36,6 +36,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[
|
||||
uic!("app.language.russian", "Русский", ["Edit", "Language"], None),
|
||||
uic!("app.language.french", "Français", ["Edit", "Language"], None),
|
||||
uic!("app.language.indonesian", "Bahasa Indonesia", ["Edit", "Language"], None),
|
||||
uic!("app.language.italian", "Italiano", ["Edit", "Language"], None),
|
||||
uic!("app.language.korean", "한국어", ["Edit", "Language"], None),
|
||||
uic!("source.playback.toggle", "Source Play/Stop", [], None),
|
||||
uic!("source.playback.play", "Play Source", [], None),
|
||||
@@ -196,6 +197,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
|
||||
| "app.language.russian"
|
||||
| "app.language.french"
|
||||
| "app.language.indonesian"
|
||||
| "app.language.italian"
|
||||
| "app.language.korean"
|
||||
) {
|
||||
// Japanese needs the craft-fonts (built with CRAFT_FONTS_DIR) or a font installed on the system
|
||||
@@ -220,6 +222,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
|
||||
"app.language.russian" => crate::i18n::Language::Ru,
|
||||
"app.language.french" => crate::i18n::Language::Fr,
|
||||
"app.language.indonesian" => crate::i18n::Language::Id,
|
||||
"app.language.italian" => crate::i18n::Language::It,
|
||||
"app.language.korean" => crate::i18n::Language::Ko,
|
||||
_ => crate::i18n::Language::En,
|
||||
};
|
||||
@@ -692,6 +695,7 @@ pub fn menu_items(app: &FilmcraftApp) -> Vec<MenuItem> {
|
||||
"app.language.russian" => it.checked = Some(app.ui.language == crate::i18n::Language::Ru),
|
||||
"app.language.french" => it.checked = Some(app.ui.language == crate::i18n::Language::Fr),
|
||||
"app.language.indonesian" => it.checked = Some(app.ui.language == crate::i18n::Language::Id),
|
||||
"app.language.italian" => it.checked = Some(app.ui.language == crate::i18n::Language::It),
|
||||
"app.language.korean" => it.checked = Some(app.ui.language == crate::i18n::Language::Ko),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
+4
-1
@@ -239,11 +239,14 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
|
||||
fallback fonts and shows captions in reading order, but other names and fields keep egui's
|
||||
left-to-right run order; Arabic titles reported broken, #395),
|
||||
German beyond menus (#490), Korean (#508; needs Hangul fonts in craft-fonts),
|
||||
Vietnamese; Premiere also ships Italian and Russian. Engine errors,
|
||||
Vietnamese; Premiere also ships Russian. Engine errors,
|
||||
CLI and MCP stay English. No language has a recorded native-speaker review.
|
||||
- **Done:** Indonesian (#867): `id.tsv` has every entry of the Spanish catalog (~100%), in Edit ▸
|
||||
Language, Settings ▸ General and the native macOS menu; System Language picks it for `id-*` (and
|
||||
legacy `in-*`) locales. Native-speaker review welcome.
|
||||
- **Done:** Italian: `it.tsv` has every entry of the Spanish catalog (~100%), in Edit ▸ Language,
|
||||
Settings ▸ General and the native macOS menu; System Language picks it for `it-*` locales.
|
||||
Native-speaker review welcome.
|
||||
- **Estimate:** 150–230 h. Doc: [localization-parity.md](localization-parity.md).
|
||||
|
||||
## G15. Interchange never validated in Avid / Pro Tools; slow XML import
|
||||
|
||||
@@ -10,7 +10,7 @@ Portuguese (Brazil), Russian, Simplified Chinese.
|
||||
|
||||
Edit ▸ Language (also Settings ▸ General, `general.interfaceLanguage`) switches the interface and
|
||||
persists across restarts; System Language (the default) follows the OS's or browser's preferred
|
||||
languages and falls back to English. Commands `app.language.<english|japanese|spanish|portuguese|ukrainian|chinese|german|russian|french|indonesian>`
|
||||
languages and falls back to English. Commands `app.language.<english|japanese|spanish|portuguese|ukrainian|chinese|german|russian|french|indonesian|italian>`
|
||||
are reachable over the control channel. One catalog per language,
|
||||
`crates/ui-egui/src/i18n/<code>.tsv` (format in the header of `es.tsv`); UI code wraps strings in
|
||||
`tl!("…")` / `tlf!("…{name}", name)`, and registry names (effects and their parameters, settings,
|
||||
@@ -53,9 +53,10 @@ whole interface, one PR) at ~4–8 h per language for the catalog plus tests.
|
||||
| German | de | 371 (10.6%) | menus only | Latin | yes (#630) | menus only | 5–9 h + review |
|
||||
| Korean | ko | 3,573 (same entries as Spanish, plus the macOS Settings item) | as Spanish | installed system Hangul font (Malgun Gothic, Apple SD Gothic Neo, Noto Sans CJK KR…); refused without one | not recorded; welcome | partial (#508) | review; Hangul in craft-fonts for the web |
|
||||
| Vietnamese | vi | 0 (0%) | — | Latin with stacked diacritics (font coverage to check) | — | none | 6–12 h + review |
|
||||
| *Other shipped:* Italian | it | 3,573 (same entries as Spanish, plus the macOS Settings item); must cover the core menus, no entry without a Spanish one | as Spanish | Latin (bundled fonts) | not recorded; welcome | partial | review |
|
||||
| *Other shipped:* Ukrainian | uk | 351 (10.0%) | menus only | Cyrillic (bundled) | not recorded | menus only | 5–9 h |
|
||||
|
||||
Premiere also ships Italian and Russian, which we don't (6–10 h each).
|
||||
Premiere also ships Italian, which we now cover like Spanish, and Russian (6–10 h).
|
||||
|
||||
**Localization dimension:** 6 of the 12 key languages near-complete (English, Spanish, Japanese,
|
||||
Simplified Chinese, French, Portuguese (Brazil)), 1 menus-only, 5 none; against Premiere's 10 languages
|
||||
|
||||
Reference in new issue
Block a user