feat: add i18n support for libcosmic widgets

This commit is contained in:
Vukašin Vojinović 2025-09-05 18:50:25 +02:00 • committed by GitHub
parent ea349aca82
commit 066999586b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 110 additions and 15 deletions

View file

@ -107,6 +107,13 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c
chrono = "0.4.41" chrono = "0.4.41"
cosmic-config = { path = "cosmic-config" } cosmic-config = { path = "cosmic-config" }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
# Internationalization
i18n-embed = { version = "0.16.0", features = [
"fluent-system",
"desktop-requester",
] }
i18n-embed-fl = "0.10"
rust-embed = "8.7.2"
css-color = "0.2.8" css-color = "0.2.8"
derive_setters = "0.1.8" derive_setters = "0.1.8"
futures = "0.3" futures = "0.3"

4
i18n.toml Normal file
View file

@ -0,0 +1,4 @@
fallback_language = "en"
[fluent]
assets_dir = "i18n"

11
i18n/en/libcosmic.ftl Normal file
View file

@ -0,0 +1,11 @@
# Context Drawer
close = Close
# About
license = License
links = Links
developers = Developers
designers = Designers
artists = Artists
translators = Translators
documenters = Documenters

View file

@ -0,0 +1,11 @@
# Context Drawer
close = Затвори
# About
license = Лиценца
links = Линкови
Developers = Програмери
Designers = Дизајнери
Artists = Уметници
Translators = Преводиоци
Documenters = Документатори

View file

@ -0,0 +1,11 @@
# Context Drawer
close = Zatvori
# About
license = Licenca
links = Linkovi
developers = Programeri
designers = Dizajneri
artists = Umetnici
translators = Prevodioci
documenters = Dokumentatori

View file

@ -90,6 +90,8 @@ pub use iced_wgpu;
pub mod icon_theme; pub mod icon_theme;
pub mod keyboard_nav; pub mod keyboard_nav;
mod localize;
#[cfg(all(target_env = "gnu", not(target_os = "windows")))] #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
pub(crate) mod malloc; pub(crate) mod malloc;

51
src/localize.rs Normal file
View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-3.0-only
use i18n_embed::{
DefaultLocalizer, LanguageLoader, Localizer,
fluent::{FluentLanguageLoader, fluent_language_loader},
};
use rust_embed::RustEmbed;
use std::sync::{LazyLock, OnceLock};
#[derive(RustEmbed)]
#[folder = "i18n/"]
struct Localizations;
pub static LANGUAGE_LOADER: LazyLock<FluentLanguageLoader> = LazyLock::new(|| {
let loader: FluentLanguageLoader = fluent_language_loader!();
loader
.load_fallback_language(&Localizations)
.expect("Error while loading fallback language");
loader
});
static LOCALIZATION_INITIALIZED: OnceLock<()> = OnceLock::new();
#[macro_export]
macro_rules! fl {
($message_id:literal) => {{
$crate::localize::localize();
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id)
}};
($message_id:literal, $($args:expr),*) => {{
$crate::localize::localize();
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *)
}};
}
// Get the `Localizer` to be used for localizing this library.
pub fn localizer() -> Box<dyn Localizer> {
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
}
pub fn localize() {
LOCALIZATION_INITIALIZED.get_or_init(|| {
let localizer = localizer();
let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
if let Err(error) = localizer.select(&requested_languages) {
eprintln!("Error while loading language for libcosmic {}", error);
}
});
}

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
Element, Element, fl,
iced::{Alignment, Length}, iced::{Alignment, Length},
widget::{self, horizontal_space}, widget::{self, horizontal_space},
}, },
@ -116,7 +116,7 @@ pub fn about<'a, Message: Clone + 'static>(
space_xxs, space_m, .. space_xxs, space_m, ..
} = crate::theme::spacing(); } = crate::theme::spacing();
let section = |list: &'a Vec<(String, String)>, title: &'a str| { let section = |list: &'a Vec<(String, String)>, title: String| {
(!list.is_empty()).then_some({ (!list.is_empty()).then_some({
let items: Vec<Element<Message>> = let items: Vec<Element<Message>> =
list.iter() list.iter()
@ -150,15 +150,15 @@ pub fn about<'a, Message: Clone + 'static>(
}); });
let author = about.author.as_ref().map(widget::text::body); let author = about.author.as_ref().map(widget::text::body);
let version = about.version.as_ref().map(widget::button::standard); let version = about.version.as_ref().map(widget::button::standard);
let links_section = section(&about.links, "Links"); let links_section = section(&about.links, fl!("links"));
let developers_section = section(&about.developers, "Developers"); let developers_section = section(&about.developers, fl!("developers"));
let designers_section = section(&about.designers, "Designers"); let designers_section = section(&about.designers, fl!("designers"));
let artists_section = section(&about.artists, "Artists"); let artists_section = section(&about.artists, fl!("artists"));
let translators_section = section(&about.translators, "Translators"); let translators_section = section(&about.translators, fl!("translators"));
let documenters_section = section(&about.documenters, "Documenters"); let documenters_section = section(&about.documenters, fl!("documenters"));
let license = about.license.as_ref().map(|license| { let license = about.license.as_ref().map(|license| {
let url = about.get_license_url(); let url = about.get_license_url();
widget::settings::section().title("License").add( widget::settings::section().title(fl!("license")).add(
widget::button::custom( widget::button::custom(
widget::row() widget::row()
.push(widget::text(license)) .push(widget::text(license))

View file

@ -1,12 +1,10 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::borrow::Cow;
use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text};
use crate::{Apply, Element, Renderer, Theme};
use super::overlay::Overlay; use super::overlay::Overlay;
use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text};
use crate::{Apply, Element, Renderer, Theme, fl};
use std::borrow::Cow;
use iced_core::Alignment; use iced_core::Alignment;
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
@ -86,7 +84,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
) )
.push_maybe(title) .push_maybe(title)
.push( .push(
button::text("Close") button::text(fl!("close"))
.trailing_icon(icon::from_name("go-next-symbolic")) .trailing_icon(icon::from_name("go-next-symbolic"))
.on_press(on_close) .on_press(on_close)
.apply(container) .apply(container)