diff --git a/launcher/lang/fr.json b/launcher/lang/fr.json index d2181dfcc..68ef9f744 100644 --- a/launcher/lang/fr.json +++ b/launcher/lang/fr.json @@ -91,5 +91,67 @@ "Landscape": "Paysage", "Your picture, behind Home, cropped to fill it.": "Votre image, derrière l'accueil, recadrée pour le remplir.", "Horizon's landscape. Pick a PNG, JPEG or WebP picture of your own.": "Le paysage d'Horizon. Choisissez votre propre image PNG, JPEG ou WebP.", - "Home background": "Fond de l'accueil" + "Home background": "Fond de l'accueil", + "Play": "Jouer", + "Running": "En cours", + "Stopped": "Arrêté", + "Start the game, or run a server for others to join.": "Lancez le jeu, ou ouvrez un serveur que d'autres peuvent rejoindre.", + "No version installed yet.": "Aucune version installée pour l'instant.", + "Choose a version": "Choisir une version", + "Host a server": "Héberger un serveur", + "Preparing…": "Préparation…", + "Prepare": "Préparer", + "Prepare again": "Préparer à nouveau", + "Not found. Choose the folder where your launcher keeps Minecraft.": "Introuvable. Choisissez le dossier où votre launcher garde Minecraft.", + "Ready to play": "Prêt à jouer", + "Resources": "Ressources", + "Minecraft folder": "Dossier Minecraft", + "Choose…": "Choisir…", + "Playing offline": "Jeu hors ligne", + "Online servers need a Microsoft account.": "Les serveurs en ligne demandent un compte Microsoft.", + "Accounts…": "Comptes…", + "The first preparation takes a few minutes.": "La première préparation prend quelques minutes.", + "Horizon crashed": "Horizon a planté", + "When you report it on Discord, attach the crash report file (or paste it with Copy report) and say what you were doing.": "Pour le signaler sur Discord, joignez le rapport de plantage (ou collez-le avec Copier le rapport) et dites ce que vous faisiez.", + "The game ended without a crash report. When you report it on Discord, attach latest.log from the logs folder and say what you were doing.": "Le jeu s'est arrêté sans rapport de plantage. Pour le signaler sur Discord, joignez latest.log du dossier des journaux et dites ce que vous faisiez.", + "Open report": "Ouvrir le rapport", + "Copied": "Copié", + "Copy report": "Copier le rapport", + "Open logs folder": "Ouvrir le dossier des journaux", + "Report on Discord": "Signaler sur Discord", + "Dismiss": "Fermer", + "INSTANCES": "INSTANCES", + "SERVERS": "SERVEURS", + "HIDE": "MASQUER", + "Website": "Site web", + "Ready": "Prêt", + "Needs preparing": "À préparer", + "{version} is not installed": "{version} n'est pas installée", + "No version installed": "Aucune version installée", + "Modpack": "Modpack", + "From Horizon's registry": "Du registre d'Horizon", + "Instance": "Instance", + "Edit…": "Modifier…", + "Copy…": "Copier…", + "Export…": "Exporter…", + "Close export": "Fermer l'export", + "Launch…": "Lancement…", + "Close launch": "Fermer le lancement", + "Open folder": "Ouvrir le dossier", + "Join a friend": "Rejoindre un ami", + "Paste an invite code": "Collez un code d'invitation", + "Paste the code a friend shows when they invite you.": "Collez le code qu'un ami affiche quand il vous invite.", + "Join": "Rejoindre", + "Shortcut": "Raccourci", + "Remove shortcut": "Retirer le raccourci", + "today": "aujourd'hui", + "yesterday": "hier", + "{days} days ago": "il y a {days} jours", + "{time} in total · last played {when}": "{time} au total · joué pour la dernière fois {when}", + "Not played yet": "Pas encore joué", + "under a minute": "moins d'une minute", + "PARTNER": "PARTENAIRE", + "Not prepared. Horizon needs the files of your own Minecraft {version}.": "Pas préparé. Horizon a besoin des fichiers de votre propre Minecraft {version}.", + "Prepared for Minecraft {version}": "Préparé pour Minecraft {version}", + "Prepared for Minecraft {version}, without its sounds: the game will be silent. Prepare again to copy them from your Minecraft install.": "Préparé pour Minecraft {version}, sans ses sons : le jeu sera muet. Préparez à nouveau pour les copier depuis votre installation de Minecraft." } diff --git a/launcher/lang/ru.json b/launcher/lang/ru.json index d47b03cf0..48051bfac 100644 --- a/launcher/lang/ru.json +++ b/launcher/lang/ru.json @@ -91,5 +91,67 @@ "Landscape": "Пейзаж", "Your picture, behind Home, cropped to fill it.": "Ваша картинка за главной страницей, обрезанная по размеру.", "Horizon's landscape. Pick a PNG, JPEG or WebP picture of your own.": "Пейзаж Horizon. Выберите свою картинку PNG, JPEG или WebP.", - "Home background": "Фон главной" + "Home background": "Фон главной", + "Play": "Играть", + "Running": "Запущено", + "Stopped": "Остановлен", + "Start the game, or run a server for others to join.": "Запустите игру или сервер, к которому смогут подключиться другие.", + "No version installed yet.": "Пока не установлено ни одной версии.", + "Choose a version": "Выбрать версию", + "Host a server": "Открыть сервер", + "Preparing…": "Подготовка…", + "Prepare": "Подготовить", + "Prepare again": "Подготовить заново", + "Not found. Choose the folder where your launcher keeps Minecraft.": "Не найдено. Выберите папку, где ваш лаунчер хранит Minecraft.", + "Ready to play": "Готово к игре", + "Resources": "Ресурсы", + "Minecraft folder": "Папка Minecraft", + "Choose…": "Выбрать…", + "Playing offline": "Игра офлайн", + "Online servers need a Microsoft account.": "Для онлайн-серверов нужен аккаунт Microsoft.", + "Accounts…": "Аккаунты…", + "The first preparation takes a few minutes.": "Первая подготовка занимает несколько минут.", + "Horizon crashed": "Horizon вылетел", + "When you report it on Discord, attach the crash report file (or paste it with Copy report) and say what you were doing.": "Сообщая об этом в Discord, приложите файл отчёта о сбое (или вставьте его через «Скопировать отчёт») и расскажите, что вы делали.", + "The game ended without a crash report. When you report it on Discord, attach latest.log from the logs folder and say what you were doing.": "Игра закрылась без отчёта о сбое. Сообщая об этом в Discord, приложите latest.log из папки журналов и расскажите, что вы делали.", + "Open report": "Открыть отчёт", + "Copied": "Скопировано", + "Copy report": "Скопировать отчёт", + "Open logs folder": "Открыть папку журналов", + "Report on Discord": "Сообщить в Discord", + "Dismiss": "Закрыть", + "INSTANCES": "СБОРКИ", + "SERVERS": "СЕРВЕРЫ", + "HIDE": "СКРЫТЬ", + "Website": "Сайт", + "Ready": "Готово", + "Needs preparing": "Нужна подготовка", + "{version} is not installed": "{version} не установлена", + "No version installed": "Версия не установлена", + "Modpack": "Сборка модов", + "From Horizon's registry": "Из каталога Horizon", + "Instance": "Сборка", + "Edit…": "Изменить…", + "Copy…": "Копировать…", + "Export…": "Экспорт…", + "Close export": "Закрыть экспорт", + "Launch…": "Запуск…", + "Close launch": "Закрыть запуск", + "Open folder": "Открыть папку", + "Join a friend": "Присоединиться к другу", + "Paste an invite code": "Вставьте код приглашения", + "Paste the code a friend shows when they invite you.": "Вставьте код, который друг показывает, когда приглашает вас.", + "Join": "Войти", + "Shortcut": "Ярлык", + "Remove shortcut": "Убрать ярлык", + "today": "сегодня", + "yesterday": "вчера", + "{days} days ago": "{days} дн. назад", + "{time} in total · last played {when}": "всего {time} · последняя игра: {when}", + "Not played yet": "Ещё не запускалась", + "under a minute": "меньше минуты", + "PARTNER": "ПАРТНЁР", + "Not prepared. Horizon needs the files of your own Minecraft {version}.": "Не подготовлено. Horizon нужны файлы вашего собственного Minecraft {version}.", + "Prepared for Minecraft {version}": "Подготовлено для Minecraft {version}", + "Prepared for Minecraft {version}, without its sounds: the game will be silent. Prepare again to copy them from your Minecraft install.": "Подготовлено для Minecraft {version}, но без звуков: игра будет беззвучной. Подготовьте заново, чтобы скопировать их из вашей установки Minecraft." } diff --git a/launcher/src/backend.rs b/launcher/src/backend.rs index 3c2c3d3e4..17840fe90 100644 --- a/launcher/src/backend.rs +++ b/launcher/src/backend.rs @@ -651,14 +651,17 @@ fn sounds_in(resources: &Path) -> bool { /// What the Play page says of a version's resources. pub fn resources_summary(prepared: bool, sounds: bool, version: &str) -> String { + use crate::i18n::trf; match (prepared, sounds) { - (false, _) => { - format!("Not prepared. Horizon needs the files of your own Minecraft {version}.") - } - (true, true) => format!("Prepared for Minecraft {version}"), - (true, false) => format!( + (false, _) => trf( + "Not prepared. Horizon needs the files of your own Minecraft {version}.", + &[("version", version)], + ), + (true, true) => trf("Prepared for Minecraft {version}", &[("version", version)]), + (true, false) => trf( "Prepared for Minecraft {version}, without its sounds: the game will be silent. \ - Prepare again to copy them from your Minecraft install." + Prepare again to copy them from your Minecraft install.", + &[("version", version)], ), } } diff --git a/launcher/src/instances.rs b/launcher/src/instances.rs index 38951bf02..0d47386cb 100644 --- a/launcher/src/instances.rs +++ b/launcher/src/instances.rs @@ -448,7 +448,7 @@ fn record_play_in( pub fn play_time(played: Duration) -> String { let minutes = played.as_secs() / 60; match (minutes / 60, minutes % 60) { - (0, 0) => "under a minute".into(), + (0, 0) => crate::i18n::tr("under a minute").into(), (0, m) => format!("{m} min"), (h, m) if h < 100 => format!("{h} h {m:02} min"), (h, _) => format!("{h} h"), diff --git a/launcher/src/instances_page.rs b/launcher/src/instances_page.rs index b0777e9ba..e94fffaaa 100644 --- a/launcher/src/instances_page.rs +++ b/launcher/src/instances_page.rs @@ -190,11 +190,11 @@ impl Launcher { InstanceKind::Default | InstanceKind::Own => ( self.page == Page::Play && selected.as_ref() == Some(&i.id), match (&tag, &i.version) { - (None, Some(own)) => format!("{own} is not installed"), - (None, None) => "No version installed".into(), - (Some(_), _) if !prepared => "Needs preparing".into(), - (Some(tag), Some(_)) => format!("Ready · {tag}"), - (Some(_), None) => "Ready".into(), + (None, Some(own)) => trf("{version} is not installed", &[("version", own)]), + (None, None) => tr("No version installed").into(), + (Some(_), _) if !prepared => tr("Needs preparing").into(), + (Some(tag), Some(_)) => format!("{} · {tag}", tr("Ready")), + (Some(_), None) => tr("Ready").into(), }, ), InstanceKind::Port => { @@ -202,10 +202,10 @@ impl Launcher { let summary = self.packs.iter().find(|p| p.slug == slug); ( on_packs && self.pack_open.as_deref() == Some(slug), - summary.map_or_else(|| "Modpack".into(), |p| p.summary()), + summary.map_or_else(|| tr("Modpack").into(), |p| p.summary()), ) } - InstanceKind::Pack => (false, "From Horizon's registry".into()), + InstanceKind::Pack => (false, tr("From Horizon's registry").into()), }; let dot = matches!(i.kind, InstanceKind::Default | InstanceKind::Own).then(|| { div() @@ -621,7 +621,7 @@ impl Launcher { ); Some(index_row( n, - "Instance", + tr("Instance"), Some( div() .flex() @@ -651,12 +651,14 @@ impl Launcher { /// "3 h 05 min in total · last played today", or "Not played yet". fn played_line(instance: &Instance) -> String { match instance.last_played { - Some(when) => format!( - "{} in total · last played {}", - instances::play_time(instance.played), - worlds_page::ago(when) + Some(when) => trf( + "{time} in total · last played {when}", + &[ + ("time", &instances::play_time(instance.played)), + ("when", &worlds_page::ago(when)), + ], ), - None => "Not played yet".into(), + None => tr("Not played yet").into(), } } diff --git a/launcher/src/main.rs b/launcher/src/main.rs index e1c0e8ca9..bd7822bd6 100644 --- a/launcher/src/main.rs +++ b/launcher/src/main.rs @@ -1755,7 +1755,7 @@ impl Launcher { .flex() .flex_col() .gap(px(4.)) - .child(heading("INSTANCES".into()).child(add)) + .child(heading(tr("INSTANCES").into()).child(add)) .children(self.instance_items(heading, cx)); let servers: Vec<_> = self .servers @@ -1764,7 +1764,7 @@ impl Launcher { .unwrap_or_else(|| self.side_servers.clone()); let mut list = list; if !servers.is_empty() { - list = list.child(heading("SERVERS".into())); + list = list.child(heading(tr("SERVERS").into())); } for e in servers { let up = self.servers.is_up(&e.dir); @@ -1790,7 +1790,7 @@ impl Launcher { .flex_col() .min_w_0() .child(title(e.name.clone())) - .child(sub(if up { "Running" } else { "Stopped" }.into())), + .child(sub(tr(if up { "Running" } else { "Stopped" }).into())), ) .on_click(cx.listener(move |this, _: &ClickEvent, _, cx| { this.servers.open = Some(dir.clone()); @@ -1874,7 +1874,7 @@ impl Launcher { text(theme::MICRO) .text_color(t.muted) .hover(move |s| s.text_color(t.ink)) - .child("HIDE"), + .child(tr("HIDE")), ) .on_click(cx.listener(|this, _: &ClickEvent, _, cx| { this.settings.partner_hidden = true; @@ -1896,7 +1896,7 @@ impl Launcher { if !self.links.website.is_empty() { foot = foot.child(link( "website", - "Website", + tr("Website"), "globe", self.links.website.clone(), )); @@ -2061,7 +2061,7 @@ impl Launcher { "Start the game, or run a server for others to join.", &t, )) - .child(centered("No version installed yet.", &t).child( + .child(centered(tr("No version installed yet."), &t).child( button("go-versions", "Choose a version", Kind::Secondary, &t).on_click( cx.listener(|this, _: &ClickEvent, _, cx| { this.go(Page::Versions); @@ -2077,10 +2077,12 @@ impl Launcher { let hero = self.hero(&tag, &version, prepared, cx); let running = self.client.is_some() || self.starting_client.is_some(); let play_label = if running { "Running" } else { "Play" }; + // The label clears the icon in every language ("Играть" is wider than "Play"). let mut play = button("play", play_label, Kind::Primary, &t) .min_w(px(200.)) .h(px(60.)) - .px(px(28.)) + .pl(px(60.)) + .pr(px(28.)) .text_size(px(28.)) .font_weight(FontWeight::SEMIBOLD) .child( @@ -2121,7 +2123,7 @@ impl Launcher { ); let resources_value = if self.preparing { - "Preparing…".to_string() + tr("Preparing…").to_string() } else { backend::resources_summary(prepared, !silent, &version) }; @@ -2146,7 +2148,9 @@ impl Launcher { let minecraft = self.settings.minecraft_dir.as_deref().map_or_else( || { div() - .child("Not found. Choose the folder where your launcher keeps Minecraft.") + .child(tr( + "Not found. Choose the folder where your launcher keeps Minecraft.", + )) .into_any_element() }, |p| { @@ -2165,14 +2169,14 @@ impl Launcher { .children(self.launch_panel(cx)) .child(index_row( 2, - "Resources", + tr("Resources"), Some(div().child(resources_value).into_any_element()), Some(prepare.into_any_element()), &t, )) .child(index_row( 3, - "Minecraft folder", + tr("Minecraft folder"), Some(minecraft), Some( button("choose-mc", "Choose…", Kind::Ghost, &t) @@ -2188,10 +2192,10 @@ impl Launcher { if self.accounts.selected().is_none() && account::client_id().is_some() { rows = rows.child(index_row( 5, - "Playing offline", + tr("Playing offline"), Some( div() - .child("Online servers need a Microsoft account.") + .child(tr("Online servers need a Microsoft account.")) .into_any_element(), ), Some( @@ -2276,7 +2280,7 @@ impl Launcher { .child( text(theme::CAPTION) .text_color(t.muted) - .child("The first preparation takes a few minutes."), + .child(tr("The first preparation takes a few minutes.")), ), ); } @@ -2370,11 +2374,11 @@ impl Launcher { }, )), ); - let help = if crash.report.is_some() { + let help = tr(if crash.report.is_some() { "When you report it on Discord, attach the crash report file (or paste it with Copy report) and say what you were doing." } else { "The game ended without a crash report. When you report it on Discord, attach latest.log from the logs folder and say what you were doing." - }; + }); let mut body = div() .flex_1() .min_w_0() @@ -2384,7 +2388,7 @@ impl Launcher { .child( text(theme::HEADING) .text_color(t.ink) - .child("Horizon crashed"), + .child(tr("Horizon crashed")), ); if !crash.summary.is_empty() { body = body.child(div().text_color(t.ink_soft).child(crash.summary)); @@ -2460,7 +2464,7 @@ impl Launcher { .child(caps(format!("Minecraft {version}")).text_color(t.ink_soft)) .child(div().size(px(4.)).bg(t.muted)) .child( - caps(if prepared { "Ready" } else { "Needs preparing" }) + caps(tr(if prepared { "Ready" } else { "Needs preparing" })) .text_color(if prepared { t.success } else { t.warn }), ), ) @@ -3153,7 +3157,7 @@ impl Launcher { .text_size(px(12.)) .cursor_text() .child(if self.friend_code.is_empty() && !focused { - div().text_color(t.muted).child("Paste an invite code") + div().text_color(t.muted).child(tr("Paste an invite code")) } else { // The end of a long code stays in view while typing. let shown: String = { @@ -3203,10 +3207,10 @@ impl Launcher { } index_row( 4, - "Join a friend", + tr("Join a friend"), Some( div() - .child("Paste the code a friend shows when they invite you.") + .child(tr("Paste the code a friend shows when they invite you.")) .into_any_element(), ), Some( diff --git a/launcher/src/partner.rs b/launcher/src/partner.rs index c4feac7b8..6242f9228 100644 --- a/launcher/src/partner.rs +++ b/launcher/src/partner.rs @@ -38,7 +38,7 @@ pub fn banner(id: &'static str, width: f32, t: &Theme) -> Stateful
{ crate::text(crate::theme::MICRO) .text_color(t.muted) .group_hover(id, move |s| s.text_color(t.ink)) - .child("PARTNER"), + .child(horizon_launcher::i18n::tr("PARTNER")), ) .child( div().relative().group_hover(id, |s| s.top(px(-2.))).child( diff --git a/launcher/src/worlds_page.rs b/launcher/src/worlds_page.rs index 16484a10a..b850c306a 100644 --- a/launcher/src/worlds_page.rs +++ b/launcher/src/worlds_page.rs @@ -32,9 +32,9 @@ pub(super) fn ago(when: std::time::SystemTime) -> String { let when: chrono::DateTime = when.into(); let days = (chrono::Local::now().date_naive() - when.date_naive()).num_days(); match days { - ..=0 => "today".into(), - 1 => "yesterday".into(), - 2..=13 => format!("{days} days ago"), + ..=0 => tr("today").into(), + 1 => tr("yesterday").into(), + 2..=13 => trf("{days} days ago", &[("days", &days.to_string())]), _ => when.format("%Y-%m-%d").to_string(), } }