Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .agents/skills/develop-maple-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,13 @@ platform ([Agent updates](../../../apps/maple-agent/docs/updates.md)). Releasing
is a production action, so run it only when the owner asks. Installed clients
parse the feed strictly; keep the wire contract in that doc unchanged. See
[desktop builds](../../../apps/maple-agent/docs/desktop-builds.md) for packaging.

## Localization

New Agent UI labels use `ui::i18n` and the embedded Fluent catalogs; follow
[the localization contract](../../../apps/maple-agent/docs/localization.md).
Plain labels are cached; parameterized messages are formatted on state changes.
Language changes apply on restart. Preserve machine identifiers, model-facing
text, user content and persisted timeline text. Do not translate host/ACP errors
in place. Expand the converted-module guard with each extraction, and require
platform/font/packaging evidence before enabling another locale.
9 changes: 9 additions & 0 deletions apps/maple-agent/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,12 @@ This app must feel instant. Treat frame time and UI-thread stalls as bugs.
cached view. Avoid `Window::refresh`; it discards every cached view.
- Release builds use fat LTO and one codegen unit. Use release builds
for any performance check; the dev profile is `opt-level = 1`.

## Localization

Use the embedded Fluent catalog and `ui::i18n` for new UI labels, including
accessibility text; see [localization](docs/localization.md) for extraction and
locale enablement. Cache parameterized messages when their state changes. Keep
element IDs, protocol values, model prompts, user content and persisted timeline
text unchanged. Language changes apply on restart. Extend the converted-module
guard as each module is extracted; host errors and native menus need owned work.
151 changes: 150 additions & 1 deletion apps/maple-agent/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions apps/maple-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,11 @@ ignored. See [build profiles](docs/release-profiles.md).
| `MAPLE_DISABLE_UPDATE_CHECK` | `1` turns Agent update checks off. | unset |
| `RUST_LOG` | Log filter. | `info` |

`MAPLE_ENABLE_LOCALIZATION=1` enables the localization preview in **all** build
profiles, including Dev and Prod. It defaults off: Language settings are hidden
and UI text uses English. Restart to change the gate; saved locale preferences
are preserved. See [localization](docs/localization.md).

### File locations

The application database lives in `<local data>/store-v1/`. An absolute
Expand Down
8 changes: 8 additions & 0 deletions apps/maple-agent/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ default = ["desktop", "acp", "proxy"]
# The gpui window. Without it the binary is headless and needs no display
# libraries; only `acp` and `proxy` modes remain.
desktop = [
"dep:fluent-bundle",
"dep:fluent-templates",
"dep:sys-locale",
"dep:gpui",
"dep:gpui-platform",
"dep:image",
Expand All @@ -38,6 +41,9 @@ acp = ["maple-agent/acp"]
proxy = ["dep:maple-proxy", "dep:axum", "dep:tower-http", "dep:hyper", "dep:hyper-util", "dep:futures-util"]

[dependencies]
fluent-bundle = { version = "0.16", optional = true }
fluent-templates = { version = "0.15.1", default-features = false, features = ["macros", "walkdir"], optional = true }
sys-locale = { version = "0.3", optional = true }
dirs = "6"
maple-agent = { path = "../crates/maple-agent", default-features = false }
maple-billing = { path = "../crates/maple-billing" }
Expand Down Expand Up @@ -113,6 +119,8 @@ raw-window-handle = { version = "0.6", optional = true }
windows-sys = { version = "0.61", features = ["Win32_Networking_WinSock", "Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_System_Memory", "Win32_System_Threading", "Win32_System_ErrorReporting", "Win32_System_SystemInformation", "Win32_Storage_FileSystem"] }

[dev-dependencies]
fluent-syntax = "0.12"
syn = { version = "2", features = ["full", "visit"] }
tempfile = "3"
parking_lot = "0.12"
# The gpui test harness. Keep this out of [dependencies]: with test-support on,
Expand Down
2 changes: 2 additions & 0 deletions apps/maple-agent/app/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod git_metadata;
mod release_profile;

fn main() {
// Track added and removed catalogs as well as edits to embedded resources.
println!("cargo:rerun-if-changed=locales");
release_profile::emit("../release-profiles.json", "../release_profile.rs", true);
let git_metadata::GitMetadata {
revision,
Expand Down
12 changes: 12 additions & 0 deletions apps/maple-agent/app/locales/en/app.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Source catalog. Keep message IDs stable; never translate protocol or element IDs.
settings-language = Language
settings-language-description = Applies after restarting Maple. Text not yet translated remains in English.
# $language is the native name of the resolved system language.
settings-language-system = System ({ $language })
language-name = English
update-confirmation-title = Restart Maple to update?
# $version is the target application version, not translated.
update-confirmation-title-version = Restart Maple to install v{ $version }?
update-confirmation-detail = This stops running tasks and remote access on this computer. Editors using Maple must reconnect after the update.
update-confirmation-later = Later
update-confirmation-restart = Restart to Update
8 changes: 7 additions & 1 deletion apps/maple-agent/app/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,7 @@ impl MapleApp {
}
}
this.set_store_resident(status.keeps_resident(), cx);
ui::i18n::initialize_saved_preference(this.settings.locale.as_deref());
ui::theme::apply_preference(
ui::theme::Preference::parse(&this.settings.theme),
cx,
Expand Down Expand Up @@ -1120,10 +1121,15 @@ impl MapleApp {
return;
}
self.restart_prompt_pending = true;
#[cfg(not(target_os = "linux"))]
let title = crate::update::restart_title(self.updater.snapshot().version.as_deref());
#[cfg(target_os = "linux")]
let answer = {
let (prompt, answer) = ui::update_confirmation::open(title, window, cx);
let (prompt, answer) = ui::update_confirmation::open(
self.updater.snapshot().version.as_deref(),
window,
cx,
);
self.restart_confirmation = Some(prompt);
cx.notify();
answer
Expand Down
3 changes: 1 addition & 2 deletions apps/maple-agent/app/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
//! trimmed, and an empty value counts as unset so a stray `NAME=` in a
//! launcher does not override a default with nothing.

// A headless build (no `desktop` feature) has no update check, the only
// caller of `env_flag`.
// Only desktop update checks and the localization preview gate use `env_flag`.
#![cfg_attr(not(feature = "desktop"), allow(dead_code))]

/// The trimmed value of `name`, or `None` when unset or blank.
Expand Down
28 changes: 28 additions & 0 deletions apps/maple-agent/app/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ pub struct AppSettings {
/// Window size and state from the last run.
#[serde(default)]
pub window: Option<WindowState>,
/// UI language override; None follows the operating system.
#[serde(default, deserialize_with = "view_preference_or_default")]
pub locale: Option<String>,
/// Color theme: "system", "dark", or "light".
#[serde(default = "default_theme")]
pub theme: String,
Expand Down Expand Up @@ -318,6 +321,7 @@ impl Default for AppSettings {
reduce_motion: false,
harness_instructions: String::new(),
window: None,
locale: None,
theme: default_theme(),
chat_font_family: default_chat_font_family(),
chat_font_size: default_chat_font_size(),
Expand Down Expand Up @@ -987,6 +991,30 @@ pub(crate) fn install_test_store(store: std::sync::Arc<maple_agent::store::Store
mod tests {
use super::*;

#[test]
fn locale_preferences_are_optional_and_survive_host_patches() {
for value in [serde_json::json!({}), serde_json::json!({"locale": 7})] {
let settings: AppSettings = serde_json::from_value(value).unwrap();
assert_eq!(settings.locale, None);
}
let root = tempfile::tempdir().unwrap();
let store =
maple_agent::store::Store::open(maple_agent::store::StorePaths::new(root.path()))
.unwrap();
for locale in [Some("en"), Some("future-locale"), None] {
apply_host_patch(&store, None, &serde_json::json!({"locale": locale})).unwrap();
apply_host_patch(&store, None, &serde_json::json!({"reduce_motion": true})).unwrap();
assert_eq!(
load_host_settings(&store, None).unwrap().locale.as_deref(),
locale
);
assert_eq!(
store.app_setting("ui").unwrap().unwrap()["locale"],
serde_json::json!(locale)
);
}
}

#[test]
fn the_sidebar_layout_round_trips_and_defaults_to_projects() {
let settings: AppSettings = serde_json::from_str("{}").unwrap();
Expand Down
Loading
Loading