use std::collections::BTreeMap;
use crate::diagnostic::{Diagnostic, codes};
use crate::l10n::{L10nDoc, tag};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChromeString {
pub key: &'static str,
pub en: &'static str,
pub args: usize,
pub slot: usize,
}
impl ChromeString {
fn tagged_as(&self, text: &str) -> String {
tag(self.key, text)
}
pub fn tagged(&self) -> String {
self.tagged_as(self.en)
}
}
macro_rules! chrome {
($( $(#[$m:meta])* $name:ident = $slot:literal, $key:literal, $args:literal, $en:expr; )*) => {
$(
$(#[$m])*
pub const $name: ChromeString = ChromeString {
key: $key, en: $en, args: $args, slot: $slot,
};
)*
pub const ALL: &[ChromeString] = &[$($name),*];
};
}
chrome! {
OBJECTIVE_NEW = 0, "delvewright.ui.objective.new", 1, "New objective: %s";
OBJECTIVE_COMPLETE = 1, "delvewright.ui.objective.complete", 1, "Objective complete: %s";
CAMPAIGN_COMPLETE = 2, "delvewright.ui.campaign.complete", 1, "%s — complete.";
CAMPAIGN_SIGNATURE = 3, "delvewright.ui.campaign.signature", 0, "A Delvewright delve.";
CAMPAIGN_BANNER = 4, "delvewright.ui.campaign.banner", 0, "Delve Complete";
LOBBY_WAITING = 5, "delvewright.ui.lobby.waiting", 2, "Waiting for the party — %s / %s";
CLASS_TITLE = 6, "delvewright.ui.class.title", 0, "Choose your class";
CLASS_BODY = 7, "delvewright.ui.class.body", 0, "Pick the kit you will carry.";
BOUNDARY_MESSAGE = 8, "delvewright.ui.boundary.message", 0,
"The tide turns you back — the delve lies behind you.";
GATE_SEALED = 9, "delvewright.ui.gate.sealed", 0, "The way is sealed.";
BONFIRE_TITLE = 10, "delvewright.ui.bonfire.title", 0, crate::stages::BONFIRE_PROMPT_EN;
BONFIRE_REST = 11, "delvewright.ui.bonfire.rest", 0, crate::stages::BONFIRE_REST_LABEL_EN;
BONFIRE_SAVE = 12, "delvewright.ui.bonfire.save", 0, crate::stages::BONFIRE_SAVE_LABEL_EN;
RESPAWN_WAIT = 13, "delvewright.ui.respawn.wait", 1, "Back in %s";
}
type Table = [&'static str; 14];
const ZH_HANS: Table = [
"新目标:%s",
"目标完成:%s",
"%s——完成。",
"一场 Delvewright 秘境。",
"秘境通关",
"等待队伍集结 —— %s / %s",
"选择你的职业",
"挑选你要携带的装备。",
"潮水将你送回——秘境在你身后。",
"此路已封。",
"篝火",
"休息并存档",
"仅存档",
"%s 秒后归队",
];
const ZH_HANT: Table = [
"新目標:%s",
"目標完成:%s",
"%s——完成。",
"一場 Delvewright 秘境。",
"秘境通關",
"等待隊伍集結 —— %s / %s",
"選擇你的職業",
"挑選你要攜帶的裝備。",
"潮水將你送回——秘境在你身後。",
"此路已封。",
"篝火",
"休息並存檔",
"僅存檔",
"%s 秒後歸隊",
];
const JA: Table = [
"新しい目標: %s",
"目標達成: %s",
"%s — クリア。",
"Delvewright の探索です。",
"探索完了",
"パーティを待っています — %s / %s",
"クラスを選択",
"携行する装備を選ぼう。",
"潮に押し戻された — 探索地は後ろだ。",
"道は閉ざされている。",
"焚き火",
"休息してセーブ",
"セーブのみ",
"復帰まで %s",
];
const KO: Table = [
"새 목표: %s",
"목표 완료: %s",
"%s — 완료.",
"Delvewright 탐험.",
"탐험 완료",
"일행을 기다리는 중 — %s / %s",
"직업을 선택하세요",
"가져갈 장비를 고르세요.",
"조류가 당신을 되돌려 보낸다 — 탐험지는 뒤에 있다.",
"길이 막혀 있다.",
"모닥불",
"휴식하고 저장",
"저장만",
"%s초 후 복귀",
];
const DE: Table = [
"Neues Ziel: %s",
"Ziel erfüllt: %s",
"%s – abgeschlossen.",
"Ein Delvewright-Abenteuer.",
"Abenteuer abgeschlossen",
"Warten auf die Gruppe – %s / %s",
"Wähle deine Klasse",
"Wähle die Ausrüstung, die du mitnimmst.",
"Die Flut trägt dich zurück – das Abenteuer liegt hinter dir.",
"Der Weg ist versperrt.",
"Lagerfeuer",
"Rasten und speichern",
"Nur speichern",
"Zurück in %s",
];
const FR: Table = [
"Nouvel objectif : %s",
"Objectif accompli : %s",
"%s — terminé.",
"Une aventure Delvewright.",
"Aventure terminée",
"En attente du groupe — %s / %s",
"Choisissez votre classe",
"Choisissez l'équipement que vous emporterez.",
"La marée vous repousse — l'aventure est derrière vous.",
"Le passage est scellé.",
"Feu de camp",
"Se reposer et sauvegarder",
"Sauvegarder seulement",
"Retour dans %s",
];
const ES: Table = [
"Nuevo objetivo: %s",
"Objetivo completado: %s",
"%s: completada.",
"Una aventura de Delvewright.",
"Aventura completada",
"Esperando al grupo — %s / %s",
"Elige tu clase",
"Elige el equipo que llevarás.",
"La marea te hace retroceder: la aventura queda a tus espaldas.",
"El paso está sellado.",
"Hoguera",
"Descansar y guardar",
"Solo guardar",
"Vuelves en %s",
];
const PT_BR: Table = [
"Novo objetivo: %s",
"Objetivo concluído: %s",
"%s — concluída.",
"Uma aventura Delvewright.",
"Aventura concluída",
"Aguardando o grupo — %s / %s",
"Escolha sua classe",
"Escolha o equipamento que vai levar.",
"A maré te empurra de volta — a aventura ficou para trás.",
"A passagem está selada.",
"Fogueira",
"Descansar e salvar",
"Apenas salvar",
"De volta em %s",
];
const PT_PT: Table = [
"Novo objetivo: %s",
"Objetivo concluído: %s",
"%s — concluída.",
"Uma aventura Delvewright.",
"Aventura concluída",
"À espera do grupo — %s / %s",
"Escolhe a tua classe",
"Escolhe o equipamento que vais levar.",
"A maré empurra-te de volta — a aventura ficou para trás.",
"A passagem está selada.",
"Fogueira",
"Descansar e guardar",
"Apenas guardar",
"De volta em %s",
];
const RU: Table = [
"Новая цель: %s",
"Цель выполнена: %s",
"%s — пройдено.",
"Приключение Delvewright.",
"Приключение пройдено",
"Ожидание отряда — %s / %s",
"Выберите класс",
"Выберите снаряжение, которое возьмёте с собой.",
"Прилив относит вас назад — приключение позади.",
"Путь запечатан.",
"Костёр",
"Отдохнуть и сохранить",
"Только сохранить",
"Возвращение через %s",
];
const IT: Table = [
"Nuovo obiettivo: %s",
"Obiettivo completato: %s",
"%s — completata.",
"Un'avventura Delvewright.",
"Avventura completata",
"In attesa del gruppo — %s / %s",
"Scegli la tua classe",
"Scegli l'equipaggiamento che porterai.",
"La marea ti respinge — l'avventura è alle tue spalle.",
"Il passaggio è sigillato.",
"Falò",
"Riposa e salva",
"Salva soltanto",
"Di ritorno tra %s",
];
const NL: Table = [
"Nieuw doel: %s",
"Doel voltooid: %s",
"%s — voltooid.",
"Een Delvewright-avontuur.",
"Avontuur voltooid",
"Wachten op het gezelschap — %s / %s",
"Kies je klasse",
"Kies de uitrusting die je meeneemt.",
"Het tij drijft je terug — het avontuur ligt achter je.",
"De doorgang is verzegeld.",
"Kampvuur",
"Rusten en opslaan",
"Alleen opslaan",
"Terug over %s",
];
const PL: Table = [
"Nowy cel: %s",
"Cel ukończony: %s",
"%s — ukończona.",
"Przygoda Delvewright.",
"Przygoda ukończona",
"Oczekiwanie na drużynę — %s / %s",
"Wybierz swoją klasę",
"Wybierz ekwipunek, który zabierzesz.",
"Przypływ zawraca cię — przygoda została za tobą.",
"Przejście jest zapieczętowane.",
"Ognisko",
"Odpocznij i zapisz",
"Tylko zapisz",
"Powrót za %s",
];
const TR: Table = [
"Yeni hedef: %s",
"Hedef tamamlandı: %s",
"%s — tamamlandı.",
"Bir Delvewright macerası.",
"Macera tamamlandı",
"Takım bekleniyor — %s / %s",
"Sınıfını seç",
"Yanına alacağın teçhizatı seç.",
"Gelgit seni geri getiriyor — macera arkanda kaldı.",
"Yol mühürlendi.",
"Kamp ateşi",
"Dinlen ve kaydet",
"Sadece kaydet",
"Dönüşe kalan: %s",
];
const UK: Table = [
"Нова ціль: %s",
"Ціль виконано: %s",
"%s — пройдено.",
"Пригода Delvewright.",
"Пригоду пройдено",
"Очікування загону — %s / %s",
"Оберіть клас",
"Оберіть спорядження, яке візьмете з собою.",
"Приплив відносить вас назад — пригода позаду.",
"Шлях запечатано.",
"Багаття",
"Відпочити та зберегти",
"Лише зберегти",
"Повернення через %s",
];
const CS: Table = [
"Nový cíl: %s",
"Cíl splněn: %s",
"%s — dokončeno.",
"Dobrodružství Delvewright.",
"Dobrodružství dokončeno",
"Čekání na družinu — %s / %s",
"Vyber si třídu",
"Vyber si výbavu, kterou si vezmeš.",
"Příliv tě vrací zpět — dobrodružství leží za tebou.",
"Cesta je zapečetěná.",
"Táborák",
"Odpočinout a uložit",
"Jen uložit",
"Návrat za %s",
];
const SK: Table = [
"Nový cieľ: %s",
"Cieľ splnený: %s",
"%s — dokončené.",
"Dobrodružstvo Delvewright.",
"Dobrodružstvo dokončené",
"Čaká sa na družinu — %s / %s",
"Vyber si triedu",
"Vyber si výstroj, ktorú si vezmeš.",
"Príliv ťa vracia späť — dobrodružstvo je za tebou.",
"Cesta je zapečatená.",
"Táborák",
"Oddýchnuť si a uložiť",
"Len uložiť",
"Návrat o %s",
];
const SV: Table = [
"Nytt mål: %s",
"Mål avklarat: %s",
"%s — avklarat.",
"Ett Delvewright-äventyr.",
"Äventyret avklarat",
"Väntar på sällskapet — %s / %s",
"Välj din klass",
"Välj utrustningen du tar med dig.",
"Tidvattnet för dig tillbaka — äventyret ligger bakom dig.",
"Vägen är förseglad.",
"Lägereld",
"Vila och spara",
"Spara endast",
"Tillbaka om %s",
];
const DA: Table = [
"Nyt mål: %s",
"Mål fuldført: %s",
"%s — fuldført.",
"Et Delvewright-eventyr.",
"Eventyret fuldført",
"Venter på selskabet — %s / %s",
"Vælg din klasse",
"Vælg det udstyr, du tager med.",
"Tidevandet fører dig tilbage — eventyret ligger bag dig.",
"Vejen er forseglet.",
"Bål",
"Hvil og gem",
"Gem kun",
"Tilbage om %s",
];
const NB: Table = [
"Nytt mål: %s",
"Mål fullført: %s",
"%s — fullført.",
"Et Delvewright-eventyr.",
"Eventyret fullført",
"Venter på følget — %s / %s",
"Velg klassen din",
"Velg utstyret du tar med deg.",
"Tidevannet fører deg tilbake — eventyret ligger bak deg.",
"Veien er forseglet.",
"Bål",
"Hvil og lagre",
"Bare lagre",
"Tilbake om %s",
];
const FI: Table = [
"Uusi tavoite: %s",
"Tavoite suoritettu: %s",
"%s — suoritettu.",
"Delvewright-seikkailu.",
"Seikkailu suoritettu",
"Odotetaan ryhmää — %s / %s",
"Valitse luokkasi",
"Valitse mukaan otettavat varusteet.",
"Vuorovesi kääntää sinut takaisin — seikkailu jää taaksesi.",
"Tie on sinetöity.",
"Nuotio",
"Lepää ja tallenna",
"Vain tallennus",
"Paluu %s kuluttua",
];
const HU: Table = [
"Új cél: %s",
"Cél teljesítve: %s",
"%s — teljesítve.",
"Egy Delvewright-kaland.",
"A kaland teljesítve",
"Várakozás a csapatra — %s / %s",
"Válaszd ki a kasztodat",
"Válaszd ki a magaddal vitt felszerelést.",
"Az ár visszasodor — a kaland mögötted maradt.",
"Az út le van pecsételve.",
"Tábortűz",
"Pihenés és mentés",
"Csak mentés",
"Visszatérés: %s",
];
const RO: Table = [
"Obiectiv nou: %s",
"Obiectiv îndeplinit: %s",
"%s — încheiată.",
"O aventură Delvewright.",
"Aventură încheiată",
"Se așteaptă grupul — %s / %s",
"Alege-ți clasa",
"Alege echipamentul pe care îl vei lua.",
"Mareea te împinge înapoi — aventura a rămas în urmă.",
"Trecerea este pecetluită.",
"Foc de tabără",
"Odihnește-te și salvează",
"Doar salvează",
"Revii în %s",
];
const EL: Table = [
"Νέος στόχος: %s",
"Ο στόχος ολοκληρώθηκε: %s",
"%s — ολοκληρώθηκε.",
"Μια περιπέτεια Delvewright.",
"Η περιπέτεια ολοκληρώθηκε",
"Αναμονή για την ομάδα — %s / %s",
"Διάλεξε την κλάση σου",
"Διάλεξε τον εξοπλισμό που θα πάρεις μαζί σου.",
"Η παλίρροια σε γυρίζει πίσω — η περιπέτεια είναι πίσω σου.",
"Το πέρασμα είναι σφραγισμένο.",
"Φωτιά",
"Ξεκουράσου και αποθήκευσε",
"Μόνο αποθήκευση",
"Επιστροφή σε %s",
];
const BG: Table = [
"Нова цел: %s",
"Целта е изпълнена: %s",
"%s — завършено.",
"Приключение на Delvewright.",
"Приключението е завършено",
"Изчакване на групата — %s / %s",
"Избери своя клас",
"Избери снаряжението, което ще носиш.",
"Приливът те връща назад — приключението остана зад теб.",
"Пътят е запечатан.",
"Огън",
"Почини си и запази",
"Само запази",
"Връщане след %s",
];
const TH: Table = [
"เป้าหมายใหม่: %s",
"ทำเป้าหมายสำเร็จ: %s",
"%s — จบแล้ว",
"การผจญภัยของ Delvewright",
"ผจญภัยสำเร็จ",
"กำลังรอกลุ่ม — %s / %s",
"เลือกคลาสของคุณ",
"เลือกอุปกรณ์ที่คุณจะนำติดตัวไป",
"กระแสน้ำพัดคุณกลับมา — การผจญภัยอยู่ข้างหลังคุณแล้ว",
"ทางนี้ถูกปิดผนึกไว้",
"กองไฟ",
"พักและบันทึก",
"บันทึกเท่านั้น",
"กลับมาใน %s",
];
const VI: Table = [
"Mục tiêu mới: %s",
"Hoàn thành mục tiêu: %s",
"%s — hoàn thành.",
"Một cuộc phiêu lưu Delvewright.",
"Hoàn thành cuộc phiêu lưu",
"Đang chờ cả nhóm — %s / %s",
"Chọn lớp nhân vật của bạn",
"Chọn trang bị bạn sẽ mang theo.",
"Thủy triều đẩy bạn trở lại — cuộc phiêu lưu ở phía sau bạn.",
"Lối đi đã bị niêm phong.",
"Lửa trại",
"Nghỉ ngơi và lưu",
"Chỉ lưu",
"Quay lại sau %s",
];
const ID: Table = [
"Tujuan baru: %s",
"Tujuan selesai: %s",
"%s — selesai.",
"Sebuah petualangan Delvewright.",
"Petualangan selesai",
"Menunggu rombongan — %s / %s",
"Pilih kelasmu",
"Pilih perlengkapan yang akan kamu bawa.",
"Air pasang membawamu kembali — petualangan ada di belakangmu.",
"Jalan ini tersegel.",
"Api unggun",
"Istirahat dan simpan",
"Simpan saja",
"Kembali dalam %s",
];
const MS: Table = [
"Objektif baharu: %s",
"Objektif selesai: %s",
"%s — selesai.",
"Sebuah pengembaraan Delvewright.",
"Pengembaraan selesai",
"Menunggu kumpulan — %s / %s",
"Pilih kelas anda",
"Pilih kelengkapan yang akan anda bawa.",
"Air pasang membawa anda kembali — pengembaraan berada di belakang anda.",
"Laluan ini telah dimeterai.",
"Unggun api",
"Berehat dan simpan",
"Simpan sahaja",
"Kembali dalam %s",
];
const AR: Table = [
"هدف جديد: %s",
"اكتمل الهدف: %s",
"%s — اكتملت.",
"مغامرة من Delvewright.",
"اكتملت المغامرة",
"في انتظار الفريق — %s / %s",
"اختر فئتك",
"اختر العتاد الذي ستحمله.",
"المد يعيدك — المغامرة خلفك.",
"الطريق مغلق.",
"نار المخيم",
"استرح واحفظ",
"احفظ فقط",
"العودة خلال %s",
];
const HE: Table = [
"יעד חדש: %s",
"היעד הושלם: %s",
"%s — הושלמה.",
"הרפתקה של Delvewright.",
"ההרפתקה הושלמה",
"ממתינים לחבורה — %s / %s",
"בחר את המחלקה שלך",
"בחר את הציוד שתישא איתך.",
"הגאות מחזירה אותך — ההרפתקה מאחוריך.",
"הדרך חתומה.",
"מדורה",
"לנוח ולשמור",
"לשמור בלבד",
"חזרה בעוד %s",
];
const HI: Table = [
"नया लक्ष्य: %s",
"लक्ष्य पूरा हुआ: %s",
"%s — पूर्ण।",
"एक Delvewright साहसिक यात्रा।",
"साहसिक यात्रा पूर्ण",
"दल की प्रतीक्षा — %s / %s",
"अपनी श्रेणी चुनें",
"वह सामान चुनें जो आप साथ ले जाएँगे।",
"ज्वार आपको वापस मोड़ देता है — यात्रा आपके पीछे रह गई।",
"यह रास्ता बंद है।",
"अलाव",
"विश्राम करें और सहेजें",
"केवल सहेजें",
"%s में वापसी",
];
const CA: Table = [
"Nou objectiu: %s",
"Objectiu completat: %s",
"%s — completada.",
"Una aventura de Delvewright.",
"Aventura completada",
"Esperant el grup — %s / %s",
"Tria la teva classe",
"Tria l'equipament que t'enduràs.",
"La marea et fa retrocedir: l'aventura queda enrere.",
"El pas està segellat.",
"Foguera",
"Descansa i desa",
"Només desa",
"Tornes en %s",
];
const FIL: Table = [
"Bagong layunin: %s",
"Natapos ang layunin: %s",
"%s — tapos na.",
"Isang pakikipagsapalaran ng Delvewright.",
"Tapos na ang pakikipagsapalaran",
"Naghihintay sa grupo — %s / %s",
"Piliin ang iyong klase",
"Piliin ang kagamitang dadalhin mo.",
"Ibinalik ka ng agos — nasa likuran mo na ang pakikipagsapalaran.",
"Nakasara ang daan.",
"Siga",
"Magpahinga at mag-save",
"Mag-save lamang",
"Babalik sa loob ng %s",
];
const TABLES: &[(&str, &Table)] = &[
("ar_sa", &AR),
("bg_bg", &BG),
("ca_es", &CA),
("cs_cz", &CS),
("da_dk", &DA),
("de_at", &DE),
("de_ch", &DE),
("de_de", &DE),
("el_gr", &EL),
("es_ar", &ES),
("es_cl", &ES),
("es_ec", &ES),
("es_es", &ES),
("es_mx", &ES),
("es_uy", &ES),
("es_ve", &ES),
("fi_fi", &FI),
("fil_ph", &FIL),
("fr_ca", &FR),
("fr_ch", &FR),
("fr_fr", &FR),
("he_il", &HE),
("hi_in", &HI),
("hu_hu", &HU),
("id_id", &ID),
("it_it", &IT),
("ja_jp", &JA),
("ko_kr", &KO),
("ms_my", &MS),
("nl_be", &NL),
("nl_nl", &NL),
("no_no", &NB),
("pl_pl", &PL),
("pt_br", &PT_BR),
("pt_pt", &PT_PT),
("ro_ro", &RO),
("ru_ru", &RU),
("sk_sk", &SK),
("sv_se", &SV),
("th_th", &TH),
("tl_ph", &FIL),
("tr_tr", &TR),
("uk_ua", &UK),
("vi_vn", &VI),
("zh_cn", &ZH_HANS),
("zh_hk", &ZH_HANT),
("zh_tw", &ZH_HANT),
];
fn table(mc_code: &str) -> Option<&'static Table> {
TABLES.iter().find(|(c, _)| *c == mc_code).map(|(_, t)| *t)
}
pub fn lang_entries(mc_code: &str) -> BTreeMap<String, String> {
let Some(t) = table(mc_code) else {
return BTreeMap::new();
};
ALL.iter()
.map(|c| (c.key.to_string(), t[c.slot].to_string()))
.collect()
}
pub fn english_entries() -> BTreeMap<String, String> {
ALL.iter()
.map(|c| (c.key.to_string(), c.en.to_string()))
.collect()
}
pub fn coverage(mc_code: &str) -> (usize, usize) {
(lang_entries(mc_code).len(), ALL.len())
}
pub fn validate_chrome_namespace(sidecars: &BTreeMap<String, L10nDoc>) -> Vec<Diagnostic> {
let mut d = Vec::new();
for (lang, doc) in sidecars {
for key in doc.content.keys() {
if !key.starts_with(RESERVED_PREFIX) {
continue;
}
d.push(Diagnostic::error(
codes::CHROME_RESERVED,
"l10n",
format!("l10n/{lang}.json#/content/{key}"),
format!(
"`{key}` is in the reserved `{RESERVED_PREFIX}` namespace, which the compiler \
owns: those are the engine's own on-screen strings (`New objective: `, \
`Choose your class`, …), they ship translated with the compiler, and no \
campaign authors or overrides them. Remove `{key}` from \
`l10n/{lang}.json` — to change what a campaign's own text says, translate \
that campaign's key instead"
),
));
}
}
d
}
pub const RESERVED_PREFIX: &str = "delvewright.";
#[derive(Clone, Debug, Default)]
pub struct Chrome {
baked: Option<&'static Table>,
ns: String,
}
impl Chrome {
pub fn for_build(campaign_id: &str, language: Option<&str>) -> Self {
Self {
baked: language
.and_then(crate::mclang::mc_lang_code)
.and_then(table),
ns: crate::l10n::pack_namespace(campaign_id),
}
}
fn text(&self, c: ChromeString) -> &str {
match self.baked {
Some(t) => t[c.slot],
None => c.en,
}
}
pub fn get(&self, c: ChromeString) -> String {
tag(&format!("{}{}", self.ns, c.key), self.text(c))
}
pub fn rebind(&self, s: &str) -> String {
let Some((key, _)) = crate::l10n::untag(s) else {
return s.to_string();
};
match ALL.iter().find(|c| c.key == key) {
Some(c) => self.get(*c),
None => s.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn placeholders(s: &str) -> usize {
s.matches("%s").count()
}
#[test]
fn chrome_strings_are_well_formed() {
let mut seen = std::collections::BTreeSet::new();
for (i, c) in ALL.iter().enumerate() {
assert_eq!(c.slot, i, "`{}` declares the wrong slot", c.key);
assert!(
c.key.starts_with("delvewright.ui."),
"chrome key `{}` is outside the reserved namespace",
c.key
);
assert!(
c.key.starts_with(RESERVED_PREFIX),
"`{}` must live under the reserved prefix",
c.key
);
assert!(!c.en.is_empty(), "chrome key `{}` has no English", c.key);
assert_eq!(
placeholders(c.en),
c.args,
"`{}` declares {} args but its English has {}",
c.key,
c.args,
placeholders(c.en)
);
assert!(seen.insert(c.key), "duplicate chrome key `{}`", c.key);
}
assert_eq!(ALL.len(), 14, "the chrome inventory changed size");
}
#[test]
fn every_table_is_complete_and_placeholder_faithful() {
assert!(!TABLES.is_empty(), "no chrome table binds");
for (code, t) in TABLES {
assert!(
crate::mclang::CLIENT_LANGS.contains(code),
"`{code}` has a chrome table but is not a language the pinned client loads"
);
for c in ALL {
let s = t[c.slot];
assert!(!s.is_empty(), "`{code}` leaves `{}` empty", c.key);
assert_eq!(
placeholders(s),
c.args,
"`{code}` renders `{}` with {} placeholder(s), English has {}",
c.key,
placeholders(s),
c.args
);
}
}
}
#[test]
fn table_rows_are_sorted_and_unique() {
let codes: Vec<&str> = TABLES.iter().map(|(c, _)| *c).collect();
assert!(
codes.windows(2).all(|w| w[0] < w[1]),
"TABLES must stay sorted by language stem"
);
}
#[test]
fn zh_cn_is_complete() {
assert_eq!(coverage("zh_cn"), (14, 14));
assert_eq!(lang_entries("zh_cn")[CLASS_TITLE.key], "选择你的职业");
}
#[test]
fn untabled_language_ships_no_chrome() {
assert_eq!(coverage("tlh_aa"), (0, 14));
assert!(lang_entries("en_us").is_empty());
assert_eq!(english_entries().len(), 14);
}
#[test]
fn build_carries_english_and_bake_carries_its_language() {
let key = crate::l10n::pack_key("doune-castle", CLASS_TITLE.key);
let multi = Chrome::for_build("doune-castle", None);
assert_eq!(multi.get(CLASS_TITLE), tag(&key, CLASS_TITLE.en));
assert_eq!(
multi.rebind(&CLASS_TITLE.tagged()),
tag(&key, CLASS_TITLE.en)
);
let zh = Chrome::for_build("doune-castle", Some("zh-cn"));
assert_eq!(zh.get(CLASS_TITLE), tag(&key, "选择你的职业"));
assert_eq!(zh.rebind(&CLASS_TITLE.tagged()), tag(&key, "选择你的职业"));
let klingon = Chrome::for_build("doune-castle", Some("tlh-aa"));
assert_eq!(klingon.get(CLASS_TITLE), tag(&key, CLASS_TITLE.en));
}
#[test]
fn two_delves_share_no_chrome_key() {
let a = Chrome::for_build("doune-castle", None);
let b = Chrome::for_build("nobodys-cave-island", Some("zh-cn"));
let mut n = 0;
for c in ALL {
let (ta, tb) = (a.get(*c), b.get(*c));
let (ka, _) = crate::l10n::untag(&ta).expect("chrome is tagged");
let (kb, _) = crate::l10n::untag(&tb).expect("chrome is tagged");
assert_ne!(ka, kb, "`{}` is one key for two delves", c.key);
n += 1;
}
assert_eq!(n, ALL.len(), "every chrome string compared");
}
#[test]
fn rebind_leaves_authored_strings_alone() {
let zh = Chrome::for_build("demo", Some("zh-cn"));
assert_eq!(zh.rebind("Shrine fire"), "Shrine fire");
let authored = tag(
&crate::l10n::pack_key("demo", "fx.q.done.0.rest_prompt"),
"Shrine fire",
);
assert_eq!(zh.rebind(&authored), authored);
let bound = zh.get(CLASS_TITLE);
assert_eq!(zh.rebind(&bound), bound);
}
#[test]
fn sidecar_may_not_shadow_chrome() {
let mut content = BTreeMap::new();
content.insert(CLASS_TITLE.key.to_string(), "俺の職業".to_string());
content.insert("npc.keeper.name".to_string(), "門番".to_string());
let doc = L10nDoc {
dsl_version: crate::DSL_VERSION.to_string(),
campaign_id: crate::ids::CampaignId("demo".to_string()),
kind: crate::l10n::L10nKind::L10n,
lang: "ja-jp".to_string(),
content,
source: BTreeMap::new(),
};
let d = validate_chrome_namespace(&BTreeMap::from([("ja-jp".to_string(), doc)]));
assert_eq!(d.len(), 1, "exactly the chrome row is flagged");
assert_eq!(d[0].code, codes::CHROME_RESERVED);
assert!(d[0].message.contains(CLASS_TITLE.key));
}
}