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"
cosmic-config = { path = "cosmic-config" }
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"
derive_setters = "0.1.8"
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 keyboard_nav;
mod localize;
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
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 {
crate::{
Element,
Element, fl,
iced::{Alignment, Length},
widget::{self, horizontal_space},
},
@ -116,7 +116,7 @@ pub fn about<'a, Message: Clone + 'static>(
space_xxs, space_m, ..
} = 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({
let items: Vec<Element<Message>> =
list.iter()
@ -150,15 +150,15 @@ pub fn about<'a, Message: Clone + 'static>(
});
let author = about.author.as_ref().map(widget::text::body);
let version = about.version.as_ref().map(widget::button::standard);
let links_section = section(&about.links, "Links");
let developers_section = section(&about.developers, "Developers");
let designers_section = section(&about.designers, "Designers");
let artists_section = section(&about.artists, "Artists");
let translators_section = section(&about.translators, "Translators");
let documenters_section = section(&about.documenters, "Documenters");
let links_section = section(&about.links, fl!("links"));
let developers_section = section(&about.developers, fl!("developers"));
let designers_section = section(&about.designers, fl!("designers"));
let artists_section = section(&about.artists, fl!("artists"));
let translators_section = section(&about.translators, fl!("translators"));
let documenters_section = section(&about.documenters, fl!("documenters"));
let license = about.license.as_ref().map(|license| {
let url = about.get_license_url();
widget::settings::section().title("License").add(
widget::settings::section().title(fl!("license")).add(
widget::button::custom(
widget::row()
.push(widget::text(license))

View file

@ -1,12 +1,10 @@
// Copyright 2023 System76 <info@system76.com>
// 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 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::event::{self, Event};
@ -86,7 +84,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
)
.push_maybe(title)
.push(
button::text("Close")
button::text(fl!("close"))
.trailing_icon(icon::from_name("go-next-symbolic"))
.on_press(on_close)
.apply(container)