blob: fcafee810bc938e89216a6f0a7e075f6c020772c [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Supported languages for Maka-owned copy and the user's saved selection.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum Language {
#[default]
#[serde(rename = "en")]
En,
#[serde(rename = "zh-CN")]
ZhCn,
#[serde(rename = "zh-TW")]
ZhTw,
}
impl Language {
pub fn is_english(&self) -> bool {
*self == Self::En
}
/// Normalize native POSIX/BCP 47 locales to supported copy, falling back to English.
pub fn from_locale(locale: &str) -> Self {
let locale = locale.trim().split(['.', '@']).next().unwrap_or("");
let normalized = locale.replace('_', "-").to_ascii_lowercase();
let mut parts = normalized.split('-');
if parts.next() != Some("zh") {
return Self::En;
}
let parts: Vec<_> = parts.collect();
if parts.contains(&"hant") {
Self::ZhTw
} else if parts.contains(&"hans") {
Self::ZhCn
} else if parts.iter().any(|part| matches!(*part, "tw" | "hk" | "mo")) {
Self::ZhTw
} else {
Self::ZhCn
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum LanguagePreference {
#[default]
#[serde(rename = "auto")]
Auto,
#[serde(rename = "en")]
En,
#[serde(rename = "zh-CN")]
ZhCn,
#[serde(rename = "zh-TW")]
ZhTw,
}
impl LanguagePreference {
/// Normalize deployment locale input; saved preferences retain strict wire tags.
pub fn from_locale(locale: &str) -> Self {
let locale = locale.trim();
if locale.is_empty() || locale.eq_ignore_ascii_case("auto") {
return Self::Auto;
}
match Language::from_locale(locale) {
Language::En => Self::En,
Language::ZhCn => Self::ZhCn,
Language::ZhTw => Self::ZhTw,
}
}
pub fn resolve(self, native: Language) -> Language {
match self {
Self::Auto => native,
Self::En => Language::En,
Self::ZhCn => Language::ZhCn,
Self::ZhTw => Language::ZhTw,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn native_locales_resolve_supported_copy() {
for (locale, expected) in [
("zh_CN.UTF-8", Language::ZhCn),
("zh-Hans-SG", Language::ZhCn),
("zh-Hant", Language::ZhTw),
("zh_TW", Language::ZhTw),
("zh-HK.UTF-8", Language::ZhTw),
("ZH_mo", Language::ZhTw),
("zh-Hans-TW", Language::ZhCn),
("zh", Language::ZhCn),
("en_GB.UTF-8", Language::En),
("fr-FR", Language::En),
("C", Language::En),
("", Language::En),
] {
assert_eq!(Language::from_locale(locale), expected, "{locale}");
}
}
#[test]
fn deployment_locale_aliases_normalize_without_widening_saved_preferences() {
for (locale, expected) in [
("", LanguagePreference::Auto),
(" auto ", LanguagePreference::Auto),
("zh-Hans-CN", LanguagePreference::ZhCn),
("zh-Hant-HK", LanguagePreference::ZhTw),
("zh_TW.UTF-8", LanguagePreference::ZhTw),
("en-US", LanguagePreference::En),
("fr-FR", LanguagePreference::En),
] {
assert_eq!(
LanguagePreference::from_locale(locale),
expected,
"{locale}"
);
}
assert!(serde_json::from_str::<LanguagePreference>(r#""zh-Hant-HK""#).is_err());
assert_eq!(
serde_json::from_str::<LanguagePreference>(r#""zh-TW""#).unwrap(),
LanguagePreference::ZhTw
);
}
}