mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #2637 from ludovic111/feat/launcher-language-home
🤖 Agent: launcher-features-1: base main, merge-tree clean against main, CI verify + launcher green; queued.
This commit is contained in:
8 files changed
+180
-47
No files matched your search
+63
-1
@@ -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."
|
||||
}
|
||||
+63
-1
@@ -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."
|
||||
}
|
||||
@@ -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)],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-21
@@ -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(
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn banner(id: &'static str, width: f32, t: &Theme) -> Stateful<Div> {
|
||||
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(
|
||||
|
||||
@@ -32,9 +32,9 @@ pub(super) fn ago(when: std::time::SystemTime) -> String {
|
||||
let when: chrono::DateTime<chrono::Local> = 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(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user