diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9703579
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+.cargo/
+*.pdb
+**/*.rs.bk
+debug/
+target/
+vendor/
+vendor.tar
+debian/*
+!debian/changelog
+!debian/control
+!debian/copyright
+!debian/install
+!debian/rules
+!debian/source
\ No newline at end of file
diff --git a/.mise.toml b/.mise.toml
new file mode 100644
index 0000000..2f12f59
--- /dev/null
+++ b/.mise.toml
@@ -0,0 +1,3 @@
+[tools]
+rust = "1.92.0"
+rust-analyzer = "latest"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4060d82
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "cosmic-weather-applet"
+version = "0.1.0"
+edition = "2024"
+license = "MIT"
+description = "Weather applet for COSMIC"
+repository = "https://git.lerch.org/lobo/cosmic-weather-applet"
+
+[dependencies]
+futures-util = "0.3.31"
+i18n-embed = { version = "0.16", features = [
+ "fluent-system",
+ "desktop-requester",
+] }
+i18n-embed-fl = "0.10"
+open = "5.3.2"
+rust-embed = "8.8.0"
+tokio = { version = "1.48.0", features = ["full"] }
+
+[dependencies.libcosmic]
+git = "https://github.com/pop-os/libcosmic.git"
+# See https://github.com/pop-os/libcosmic/blob/master/Cargo.toml for available features.
+features = [
+ # Accessibility support
+ "a11y",
+ # About widget for the app
+ "about",
+ # Uses cosmic-settings-daemon to watch for config file changes
+ "dbus-config",
+ # Support creating additional application windows.
+ "multi-window",
+ # On app startup, focuses an existing instance if the app is already open
+ "single-instance",
+ # Uses tokio as the executor for the runtime
+ "tokio",
+ # Windowing support for X11, Windows, Mac, & Redox
+ "winit",
+ # Add Wayland support to winit
+ "wayland",
+ # GPU-accelerated rendering
+ "wgpu",
+]
+
+# Uncomment to test a locally-cloned libcosmic
+# [patch.'https://github.com/pop-os/libcosmic']
+# libcosmic = { path = "../libcosmic" }
+# cosmic-config = { path = "../libcosmic/cosmic-config" }
+# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2252f67
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Emil Lerch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8fcbff2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+# Cosmic Weather Applet
+
+Weather applet for COSMIC
+
+## Installation
+
+A [justfile](./justfile) is included by default for the [casey/just][just] command runner.
+
+- `just` builds the application with the default `just build-release` recipe
+- `just run` builds and runs the application
+- `just install` installs the project into the system
+- `just vendor` creates a vendored tarball
+- `just build-vendored` compiles with vendored dependencies from that tarball
+- `just check` runs clippy on the project to check for linter warnings
+- `just check-json` can be used by IDEs that support LSP
+
+## Translators
+
+[Fluent][fluent] is used for localization of the software. Fluent's translation files are found in the [i18n directory](./i18n). New translations may copy the [English (en) localization](./i18n/en) of the project, rename `en` to the desired [ISO 639-1 language code][iso-codes], and then translations can be provided for each [message identifier][fluent-guide]. If no translation is necessary, the message may be omitted.
+
+## Packaging
+
+If packaging for a Linux distribution, vendor dependencies locally with the `vendor` rule, and build with the vendored sources using the `build-vendored` rule. When installing files, use the `rootdir` and `prefix` variables to change installation paths.
+
+```sh
+just vendor
+just build-vendored
+just rootdir=debian/cosmic-weather-applet prefix=/usr install
+```
+
+It is recommended to build a source tarball with the vendored dependencies, which can typically be done by running `just vendor` on the host system before it enters the build environment.
+
+## Developers
+
+Developers should install [rustup][rustup] and configure their editor to use [rust-analyzer][rust-analyzer]. To improve compilation times, disable LTO in the release profile, install the [mold][mold] linker, and configure [sccache][sccache] for use with Rust. The [mold][mold] linker will only improve link times if LTO is disabled.
+
+[fluent]: https://projectfluent.org/
+[fluent-guide]: https://projectfluent.org/fluent/guide/hello.html
+[iso-codes]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+[just]: https://github.com/casey/just
+[rustup]: https://rustup.rs/
+[rust-analyzer]: https://rust-analyzer.github.io/
+[mold]: https://github.com/rui314/mold
+[sccache]: https://github.com/mozilla/sccache
diff --git a/i18n.toml b/i18n.toml
new file mode 100644
index 0000000..05c50ba
--- /dev/null
+++ b/i18n.toml
@@ -0,0 +1,4 @@
+fallback_language = "en"
+
+[fluent]
+assets_dir = "i18n"
\ No newline at end of file
diff --git a/i18n/en/cosmic_weather_applet.ftl b/i18n/en/cosmic_weather_applet.ftl
new file mode 100644
index 0000000..43972e3
--- /dev/null
+++ b/i18n/en/cosmic_weather_applet.ftl
@@ -0,0 +1,7 @@
+app-title = Cosmic Weather Applet
+about = About
+repository = Repository
+view = View
+welcome = Welcome to COSMIC! ✨
+page-id = Page { $num }
+git-description = Git commit {$hash} on {$date}
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..7b988e4
--- /dev/null
+++ b/justfile
@@ -0,0 +1,96 @@
+# Name of the application's binary.
+name := 'cosmic-weather-applet'
+# The unique ID of the application.
+appid := 'com.github.elerch.cosmic-weather-applet'
+
+# Path to root file system, which defaults to `/`.
+rootdir := ''
+# The prefix for the `/usr` directory.
+prefix := '/usr'
+# The location of the cargo target directory.
+cargo-target-dir := env('CARGO_TARGET_DIR', 'target')
+
+# Application's appstream metadata
+appdata := appid + '.metainfo.xml'
+# Application's desktop entry
+desktop := appid + '.desktop'
+# Application's icon.
+icon-svg := appid + '.svg'
+
+# Install destinations
+base-dir := absolute_path(clean(rootdir / prefix))
+appdata-dst := base-dir / 'share' / 'appdata' / appdata
+bin-dst := base-dir / 'bin' / name
+desktop-dst := base-dir / 'share' / 'applications' / desktop
+icons-dst := base-dir / 'share' / 'icons' / 'hicolor'
+icon-svg-dst := icons-dst / 'scalable' / 'apps'
+
+# Default recipe which runs `just build-release`
+default: build-release
+
+# Runs `cargo clean`
+clean:
+ cargo clean
+
+# Removes vendored dependencies
+clean-vendor:
+ rm -rf .cargo vendor vendor.tar
+
+# `cargo clean` and removes vendored dependencies
+clean-dist: clean clean-vendor
+
+# Compiles with debug profile
+build-debug *args:
+ cargo build {{args}}
+
+# Compiles with release profile
+build-release *args: (build-debug '--release' args)
+
+# Compiles release profile with vendored dependencies
+build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
+
+# Runs a clippy check
+check *args:
+ cargo clippy --all-features {{args}} -- -W clippy::pedantic
+
+# Runs a clippy check with JSON message format
+check-json: (check '--message-format=json')
+
+# Run the application for testing purposes
+run *args:
+ env RUST_BACKTRACE=full cargo run --release {{args}}
+
+# Installs files
+install:
+ install -Dm0755 {{ cargo-target-dir / 'release' / name }} {{bin-dst}}
+ install -Dm0644 {{ 'resources' / desktop }} {{desktop-dst}}
+ install -Dm0644 {{ 'resources' / appdata }} {{appdata-dst}}
+ install -Dm0644 {{ 'resources' / 'icons' / 'hicolor' / 'scalable' / 'apps' / 'icon.svg' }} {{icon-svg-dst}}
+
+# Uninstalls installed files
+uninstall:
+ rm {{bin-dst}} {{desktop-dst}} {{icon-svg-dst}}
+
+# Vendor dependencies locally
+vendor:
+ mkdir -p .cargo
+ cargo vendor | head -n -1 > .cargo/config.toml
+ echo 'directory = "vendor"' >> .cargo/config.toml
+ tar pcf vendor.tar vendor
+ rm -rf vendor
+
+# Extracts vendored dependencies
+vendor-extract:
+ rm -rf vendor
+ tar pxf vendor.tar
+
+# Bump cargo version, create git commit, and create tag
+tag version:
+ find -type f -name Cargo.toml -exec sed -i '0,/^version/s/^version.*/version = "{{version}}"/' '{}' \; -exec git add '{}' \;
+ cargo check
+ cargo clean
+ git add Cargo.lock
+ git commit -m 'release: {{version}}'
+ git commit --amend
+ git tag -a {{version}} -m ''
+
diff --git a/resources/app.desktop b/resources/app.desktop
new file mode 100644
index 0000000..b36894b
--- /dev/null
+++ b/resources/app.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=Cosmic Weather Applet
+Comment=Weather applet for COSMIC
+Type=Application
+Icon=com.github.elerch.cosmic-weather-applet
+Exec=cosmic-weather-applet %F
+Terminal=false
+StartupNotify=true
+Categories=COSMIC
+Keywords=COSMIC
+MimeType=
diff --git a/resources/app.metainfo.xml b/resources/app.metainfo.xml
new file mode 100644
index 0000000..4755522
--- /dev/null
+++ b/resources/app.metainfo.xml
@@ -0,0 +1,35 @@
+
+
+ com.github.elerch.cosmic-weather-applet
+ CC0-1.0
+ MIT
+ Cosmic Weather Applet
+ Weather applet for COSMIC
+
+ https://git.lerch.org/lobo/cosmic-weather-applet/raw/main/resources/icons/hicolor/scalable/apps/icon.svg
+
+ https://git.lerch.org/lobo/cosmic-weather-applet
+ com.github.elerch.cosmic-weather-applet.desktop
+
+ com.github.elerch.cosmic-weather-applet
+
+ cosmic-weather-applet
+
+
+
+ 360
+
+
+ keyboard
+ pointing
+ touch
+
+
+ COSMIC
+
+
+ COSMIC
+
+
+
+
diff --git a/resources/icons/hicolor/scalable/apps/icon.svg b/resources/icons/hicolor/scalable/apps/icon.svg
new file mode 100644
index 0000000..36c2f5a
--- /dev/null
+++ b/resources/icons/hicolor/scalable/apps/icon.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/app.rs b/src/app.rs
new file mode 100644
index 0000000..d0c1744
--- /dev/null
+++ b/src/app.rs
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: MIT
+
+use crate::config::Config;
+use crate::fl;
+use cosmic::app::context_drawer;
+use cosmic::cosmic_config::{self, CosmicConfigEntry};
+use cosmic::iced::alignment::{Horizontal, Vertical};
+use cosmic::iced::{Alignment, Length, Subscription};
+use cosmic::widget::{self, about::About, icon, menu, nav_bar};
+use cosmic::{iced_futures, prelude::*};
+use futures_util::SinkExt;
+use std::collections::HashMap;
+use std::time::Duration;
+
+const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
+const APP_ICON: &[u8] = include_bytes!("../resources/icons/hicolor/scalable/apps/icon.svg");
+
+/// The application model stores app-specific state used to describe its interface and
+/// drive its logic.
+pub struct AppModel {
+ /// Application state which is managed by the COSMIC runtime.
+ core: cosmic::Core,
+ /// Display a context drawer with the designated page if defined.
+ context_page: ContextPage,
+ /// The about page for this app.
+ about: About,
+ /// Contains items assigned to the nav bar panel.
+ nav: nav_bar::Model,
+ /// Key bindings for the application's menu bar.
+ key_binds: HashMap,
+ /// Configuration data that persists between application runs.
+ config: Config,
+ /// Time active
+ time: u32,
+ /// Toggle the watch subscription
+ watch_is_active: bool,
+}
+
+/// Messages emitted by the application and its widgets.
+#[derive(Debug, Clone)]
+pub enum Message {
+ LaunchUrl(String),
+ ToggleContextPage(ContextPage),
+ ToggleWatch,
+ UpdateConfig(Config),
+ WatchTick(u32),
+}
+
+/// Create a COSMIC application from the app model
+impl cosmic::Application for AppModel {
+ /// The async executor that will be used to run your application's commands.
+ type Executor = cosmic::executor::Default;
+
+ /// Data that your application receives to its init method.
+ type Flags = ();
+
+ /// Messages which the application and its widgets will emit.
+ type Message = Message;
+
+ /// Unique identifier in RDNN (reverse domain name notation) format.
+ const APP_ID: &'static str = "dev.mmurphy.Test";
+
+ fn core(&self) -> &cosmic::Core {
+ &self.core
+ }
+
+ fn core_mut(&mut self) -> &mut cosmic::Core {
+ &mut self.core
+ }
+
+ /// Initializes the application with any given flags and startup commands.
+ fn init(
+ core: cosmic::Core,
+ _flags: Self::Flags,
+ ) -> (Self, Task>) {
+ // Create a nav bar with three page items.
+ let mut nav = nav_bar::Model::default();
+
+ nav.insert()
+ .text(fl!("page-id", num = 1))
+ .data::(Page::Page1)
+ .icon(icon::from_name("applications-science-symbolic"))
+ .activate();
+
+ nav.insert()
+ .text(fl!("page-id", num = 2))
+ .data::(Page::Page2)
+ .icon(icon::from_name("applications-system-symbolic"));
+
+ nav.insert()
+ .text(fl!("page-id", num = 3))
+ .data::(Page::Page3)
+ .icon(icon::from_name("applications-games-symbolic"));
+
+ // Create the about widget
+ let about = About::default()
+ .name(fl!("app-title"))
+ .icon(widget::icon::from_svg_bytes(APP_ICON))
+ .version(env!("CARGO_PKG_VERSION"))
+ .links([(fl!("repository"), REPOSITORY)])
+ .license(env!("CARGO_PKG_LICENSE"));
+
+ // Construct the app model with the runtime's core.
+ let mut app = AppModel {
+ core,
+ context_page: ContextPage::default(),
+ about,
+ nav,
+ key_binds: HashMap::new(),
+ // Optional configuration file for an application.
+ config: cosmic_config::Config::new(Self::APP_ID, Config::VERSION)
+ .map(|context| match Config::get_entry(&context) {
+ Ok(config) => config,
+ Err((_errors, config)) => {
+ // for why in errors {
+ // tracing::error!(%why, "error loading app config");
+ // }
+
+ config
+ }
+ })
+ .unwrap_or_default(),
+ time: 0,
+ watch_is_active: false,
+ };
+
+ // Create a startup command that sets the window title.
+ let command = app.update_title();
+
+ (app, command)
+ }
+
+ /// Elements to pack at the start of the header bar.
+ fn header_start(&self) -> Vec> {
+ let menu_bar = menu::bar(vec![menu::Tree::with_children(
+ menu::root(fl!("view")).apply(Element::from),
+ menu::items(
+ &self.key_binds,
+ vec![menu::Item::Button(fl!("about"), None, MenuAction::About)],
+ ),
+ )]);
+
+ vec![menu_bar.into()]
+ }
+
+ /// Enables the COSMIC application to create a nav bar with this model.
+ fn nav_model(&self) -> Option<&nav_bar::Model> {
+ Some(&self.nav)
+ }
+
+ /// Display a context drawer if the context page is requested.
+ fn context_drawer(&self) -> Option> {
+ if !self.core.window.show_context {
+ return None;
+ }
+
+ Some(match self.context_page {
+ ContextPage::About => context_drawer::about(
+ &self.about,
+ |url| Message::LaunchUrl(url.to_string()),
+ Message::ToggleContextPage(ContextPage::About),
+ ),
+ })
+ }
+
+ /// Describes the interface based on the current state of the application model.
+ ///
+ /// Application events will be processed through the view. Any messages emitted by
+ /// events received by widgets will be passed to the update method.
+ fn view(&self) -> Element<'_, Self::Message> {
+ let space_s = cosmic::theme::spacing().space_s;
+ let content: Element<_> = match self.nav.active_data::().unwrap() {
+ Page::Page1 => {
+ let header = widget::row::with_capacity(2)
+ .push(widget::text::title1(fl!("welcome")))
+ .push(widget::text::title3(fl!("page-id", num = 1)))
+ .align_y(Alignment::End)
+ .spacing(space_s);
+
+ let counter_label = ["Watch: ", self.time.to_string().as_str()].concat();
+ let section = cosmic::widget::settings::section().add(
+ cosmic::widget::settings::item::builder(counter_label).control(
+ widget::button::text(if self.watch_is_active {
+ "Stop"
+ } else {
+ "Start"
+ })
+ .on_press(Message::ToggleWatch),
+ ),
+ );
+
+ widget::column::with_capacity(2)
+ .push(header)
+ .push(section)
+ .spacing(space_s)
+ .height(Length::Fill)
+ .into()
+ }
+
+ Page::Page2 => {
+ let header = widget::row::with_capacity(2)
+ .push(widget::text::title1(fl!("welcome")))
+ .push(widget::text::title3(fl!("page-id", num = 2)))
+ .align_y(Alignment::End)
+ .spacing(space_s);
+
+ widget::column::with_capacity(1)
+ .push(header)
+ .spacing(space_s)
+ .height(Length::Fill)
+ .into()
+ }
+
+ Page::Page3 => {
+ let header = widget::row::with_capacity(2)
+ .push(widget::text::title1(fl!("welcome")))
+ .push(widget::text::title3(fl!("page-id", num = 3)))
+ .align_y(Alignment::End)
+ .spacing(space_s);
+
+ widget::column::with_capacity(1)
+ .push(header)
+ .spacing(space_s)
+ .height(Length::Fill)
+ .into()
+ }
+ };
+
+ widget::container(content)
+ .width(600)
+ .height(Length::Fill)
+ .apply(widget::container)
+ .width(Length::Fill)
+ .align_x(Horizontal::Center)
+ .align_y(Vertical::Center)
+ .into()
+ }
+
+ /// Register subscriptions for this application.
+ ///
+ /// Subscriptions are long-running async tasks running in the background which
+ /// emit messages to the application through a channel. They can be dynamically
+ /// stopped and started conditionally based on application state, or persist
+ /// indefinitely.
+ fn subscription(&self) -> Subscription {
+ // Add subscriptions which are always active.
+ let mut subscriptions = vec![
+ // Watch for application configuration changes.
+ self.core()
+ .watch_config::(Self::APP_ID)
+ .map(|update| {
+ // for why in update.errors {
+ // tracing::error!(?why, "app config error");
+ // }
+
+ Message::UpdateConfig(update.config)
+ }),
+ ];
+
+ // Conditionally enables a timer that emits a message every second.
+ if self.watch_is_active {
+ subscriptions.push(Subscription::run(|| {
+ iced_futures::stream::channel(1, |mut emitter| async move {
+ let mut time = 1;
+ let mut interval = tokio::time::interval(Duration::from_secs(1));
+
+ loop {
+ interval.tick().await;
+ _ = emitter.send(Message::WatchTick(time)).await;
+ time += 1;
+ }
+ })
+ }));
+ }
+
+ Subscription::batch(subscriptions)
+ }
+
+ /// Handles messages emitted by the application and its widgets.
+ ///
+ /// Tasks may be returned for asynchronous execution of code in the background
+ /// on the application's async runtime.
+ fn update(&mut self, message: Self::Message) -> Task> {
+ match message {
+ Message::WatchTick(time) => {
+ self.time = time;
+ }
+
+ Message::ToggleWatch => {
+ self.watch_is_active = !self.watch_is_active;
+ }
+
+ Message::ToggleContextPage(context_page) => {
+ if self.context_page == context_page {
+ // Close the context drawer if the toggled context page is the same.
+ self.core.window.show_context = !self.core.window.show_context;
+ } else {
+ // Open the context drawer to display the requested context page.
+ self.context_page = context_page;
+ self.core.window.show_context = true;
+ }
+ }
+
+ Message::UpdateConfig(config) => {
+ self.config = config;
+ }
+
+ Message::LaunchUrl(url) => match open::that_detached(&url) {
+ Ok(()) => {}
+ Err(err) => {
+ eprintln!("failed to open {url:?}: {err}");
+ }
+ },
+ }
+ Task::none()
+ }
+
+ /// Called when a nav item is selected.
+ fn on_nav_select(&mut self, id: nav_bar::Id) -> Task> {
+ // Activate the page in the model.
+ self.nav.activate(id);
+
+ self.update_title()
+ }
+}
+
+impl AppModel {
+ /// Updates the header and window titles.
+ pub fn update_title(&mut self) -> Task> {
+ let mut window_title = fl!("app-title");
+
+ if let Some(page) = self.nav.text(self.nav.active()) {
+ window_title.push_str(" — ");
+ window_title.push_str(page);
+ }
+
+ if let Some(id) = self.core.main_window_id() {
+ self.set_window_title(window_title, id)
+ } else {
+ Task::none()
+ }
+ }
+}
+
+/// The page to display in the application.
+pub enum Page {
+ Page1,
+ Page2,
+ Page3,
+}
+
+/// The context page to display in the context drawer.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+pub enum ContextPage {
+ #[default]
+ About,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum MenuAction {
+ About,
+}
+
+impl menu::action::MenuAction for MenuAction {
+ type Message = Message;
+
+ fn message(&self) -> Self::Message {
+ match self {
+ MenuAction::About => Message::ToggleContextPage(ContextPage::About),
+ }
+ }
+}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..f0bac1c
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: MIT
+
+use cosmic::cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
+
+#[derive(Debug, Default, Clone, CosmicConfigEntry, Eq, PartialEq)]
+#[version = 1]
+pub struct Config {
+ demo: String,
+}
diff --git a/src/i18n.rs b/src/i18n.rs
new file mode 100644
index 0000000..40ba9b0
--- /dev/null
+++ b/src/i18n.rs
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: MIT
+
+//! Provides localization support for this crate.
+
+use i18n_embed::{
+ DefaultLocalizer, LanguageLoader, Localizer,
+ fluent::{FluentLanguageLoader, fluent_language_loader},
+ unic_langid::LanguageIdentifier,
+};
+use rust_embed::RustEmbed;
+use std::sync::LazyLock;
+
+/// Applies the requested language(s) to requested translations from the `fl!()` macro.
+pub fn init(requested_languages: &[LanguageIdentifier]) {
+ if let Err(why) = localizer().select(requested_languages) {
+ eprintln!("error while loading fluent localizations: {why}");
+ }
+}
+
+// Get the `Localizer` to be used for localizing this library.
+#[must_use]
+pub fn localizer() -> Box {
+ Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
+}
+
+#[derive(RustEmbed)]
+#[folder = "i18n/"]
+struct Localizations;
+
+pub static LANGUAGE_LOADER: LazyLock = LazyLock::new(|| {
+ let loader: FluentLanguageLoader = fluent_language_loader!();
+
+ loader
+ .load_fallback_language(&Localizations)
+ .expect("Error while loading fallback language");
+
+ loader
+});
+
+
+/// Request a localized string by ID from the i18n/ directory.
+#[macro_export]
+macro_rules! fl {
+ ($message_id:literal) => {{
+ i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id)
+ }};
+
+ ($message_id:literal, $($args:expr),*) => {{
+ i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id, $($args), *)
+ }};
+}
+
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..a39c9d6
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: MIT
+
+mod app;
+mod config;
+mod i18n;
+
+fn main() -> cosmic::iced::Result {
+ // Get the system's preferred languages.
+ let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
+
+ // Enable localizations to be applied.
+ i18n::init(&requested_languages);
+
+ // Settings for configuring the application window and iced runtime.
+ let settings = cosmic::app::Settings::default().size_limits(
+ cosmic::iced::Limits::NONE
+ .min_width(360.0)
+ .min_height(180.0),
+ );
+
+ // Starts the application's event loop with `()` as the application's flags.
+ cosmic::app::run::(settings, ())
+}