Refactor: remove desktop/Tauri bundle, add i18n, CI/CD pipeline
Nightly Build / build (push) Failing after 6s
Nightly Build / build (push) Failing after 6s
- Remove desktop (Tauri) bundle: docs/desktop.md, icons/, tauri.conf.json, src/desktop/mod.rs, gen/schemas/ - Remove build.rs (no longer needed) - Add i18n system (crates/core-api, plugin-mobile-connector, web) - Refactor config system (src/config.rs, boot_format.rs) - Add mobile connector features (app, router, device pairing) - Plugin system improvements (skald-core) - Update dependencies (Cargo.lock, Cargo.toml) - CI/CD: Gitea Actions workflows (nightly + release), package.sh, verify-version.sh, builds.skaldagent.net config
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
//! Backend localization contract shared by the core and every plugin.
|
||||
//!
|
||||
//! Two halves:
|
||||
//! - [`LocaleBundle`] — what a plugin *declares* (its translation table for one
|
||||
//! locale), returned from `Plugin::i18n()` and collected into a single catalog
|
||||
//! at boot. Keys must be namespaced (`plugin.<id>.<key>`) so bundles from
|
||||
//! different plugins — and the core — merge without clobbering each other, and
|
||||
//! so the same key can back the frontend fragment's `t()` string.
|
||||
//! - [`I18nApi`] — what a plugin *calls* at request time to turn a key into text
|
||||
//! for the caller. Injected into `PluginContext.i18n`; the concrete impl lives
|
||||
//! in `skald-core` (it owns the locale-resolution chain and the system pool).
|
||||
//!
|
||||
//! The core never emits user-facing text through a hardcoded English literal
|
||||
//! once it can go through this seam — a plugin's own error/generated strings
|
||||
//! reach the user in the user's language, mirroring the frontend `i18n.js`.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// One namespace's translation table for a single locale, as declared by a
|
||||
/// plugin (or the core). Merged into the boot-time catalog keyed by locale;
|
||||
/// keys collide across bundles only if two authors reuse the same fully
|
||||
/// qualified key, which the `plugin.<id>.` convention prevents.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocaleBundle {
|
||||
/// Locale code — `"en"`, `"it"`, `"fr"`. Must match a supported locale;
|
||||
/// anything else is simply never selected by the resolver.
|
||||
pub locale: String,
|
||||
/// Fully qualified key → translated string. Placeholders are `{name}`.
|
||||
pub strings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl LocaleBundle {
|
||||
pub fn new(locale: impl Into<String>, strings: HashMap<String, String>) -> Self {
|
||||
Self { locale: locale.into(), strings }
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime translation, injected into [`crate::plugin::PluginContext`].
|
||||
///
|
||||
/// The catalog behind it is built once at boot from every plugin's
|
||||
/// `Plugin::i18n()`. Resolution mirrors the rest of the system: a user's
|
||||
/// `users.locale` override → the instance default → built-in English → the raw
|
||||
/// key as a last resort. Placeholders (`{name}`) are filled from `args`.
|
||||
#[async_trait]
|
||||
pub trait I18nApi: Send + Sync {
|
||||
/// Translate `key` for `user_id`, resolving *their* effective locale. Use
|
||||
/// this from any request/notification path where the target user is known.
|
||||
async fn for_user(&self, user_id: &str, key: &str, args: &[(&str, &str)]) -> String;
|
||||
|
||||
/// Translate for an already-resolved locale — for contexts with no single
|
||||
/// user (boot logs, broadcast copy) that have decided a locale by other means.
|
||||
fn get(&self, locale: &str, key: &str, args: &[(&str, &str)]) -> String;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub mod chatbot;
|
||||
pub mod chat_hub;
|
||||
pub mod command;
|
||||
pub mod events;
|
||||
pub mod i18n;
|
||||
pub mod image_generate;
|
||||
pub mod inbox;
|
||||
pub mod interface_tool;
|
||||
|
||||
@@ -7,6 +7,7 @@ use tokio::sync::RwLock;
|
||||
|
||||
use crate::command::CommandApi;
|
||||
use crate::config_api::ConfigApi;
|
||||
use crate::i18n::I18nApi;
|
||||
use crate::system_bus::SystemEventBus;
|
||||
use crate::image_generate::ImageGenerateRegistry;
|
||||
use crate::location::LocationUpdater;
|
||||
@@ -90,6 +91,10 @@ pub struct PluginContext {
|
||||
/// Per-user plugin configuration store (`plugin_user_configs` table).
|
||||
/// Admin-readable — never secrets.
|
||||
pub user_config: Arc<dyn PluginUserConfigApi>,
|
||||
/// Backend localization. Turns a plugin's namespaced string key into text in
|
||||
/// the caller's language (`i18n.for_user(user_id, key, args)`). The catalog
|
||||
/// is built at boot from every plugin's [`Plugin::i18n`]. See `core_api::i18n`.
|
||||
pub i18n: Arc<dyn I18nApi>,
|
||||
pub web_port: u16,
|
||||
pub remote_slot: Arc<RwLock<Option<Arc<dyn RemoteAccess>>>>,
|
||||
pub router_factory: RouterFactory,
|
||||
@@ -174,7 +179,9 @@ pub trait Plugin: Send + Sync {
|
||||
/// `/api/plugin/<id>/…` — no host APIs are injected;
|
||||
/// - it runs with the full privileges of the logged-in session (plugins are
|
||||
/// trusted — they ship in the binary);
|
||||
/// - it carries its own UI strings (reads the locale from `/api/auth/me`).
|
||||
/// - it localizes by shipping its own `{en,it,fr}` string table and
|
||||
/// registering it via `addStrings` into the host's shared `i18n.js`, then
|
||||
/// using the same `t()`/`I18nMixin` (keys namespaced `plugin.<id>.`).
|
||||
///
|
||||
/// Default: no pages.
|
||||
fn web_pages(&self) -> Vec<PluginPage> { Vec::new() }
|
||||
@@ -192,6 +199,13 @@ pub trait Plugin: Send + Sync {
|
||||
/// is stopped. Default: no tools.
|
||||
fn tools(self: Arc<Self>) -> Vec<Arc<dyn crate::tool::Tool>> { Vec::new() }
|
||||
|
||||
/// Backend translation tables this plugin contributes — one
|
||||
/// [`crate::i18n::LocaleBundle`] per locale it ships. Collected once at boot
|
||||
/// into the shared catalog behind [`PluginContext::i18n`]. Keys must be
|
||||
/// namespaced (`plugin.<id>.<key>`). Default: no strings (plugin emits no
|
||||
/// localized backend text). See `core_api::i18n`.
|
||||
fn i18n(&self) -> Vec<crate::i18n::LocaleBundle> { Vec::new() }
|
||||
|
||||
/// Returns a [`Memory`] backend if this plugin provides one.
|
||||
fn memory(&self) -> Option<Arc<dyn Memory>> { None }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user