u_naro 830f6f4c9b feat: add Korean (한국어) localization
Add Korean language support with full UI translations including
menu items, update messages, and time format suffixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:46:56 +09:00

212 lines
6.0 KiB
Rust

mod english;
mod french;
mod german;
mod japanese;
mod korean;
mod spanish;
use windows::core::PWSTR;
use windows::Win32::Globalization::{
GetUserDefaultLocaleName, GetUserDefaultUILanguage, GetUserPreferredUILanguages,
LCIDToLocaleName, LOCALE_ALLOW_NEUTRAL_NAMES, MAX_LOCALE_NAME, MUI_LANGUAGE_NAME,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LanguageId {
English,
Spanish,
French,
German,
Japanese,
Korean,
}
impl LanguageId {
pub const ALL: [LanguageId; 6] = [
LanguageId::English,
LanguageId::Spanish,
LanguageId::French,
LanguageId::German,
LanguageId::Japanese,
LanguageId::Korean,
];
pub fn code(self) -> &'static str {
match self {
Self::English => "en",
Self::Spanish => "es",
Self::French => "fr",
Self::German => "de",
Self::Japanese => "ja",
Self::Korean => "ko",
}
}
pub fn native_name(self) -> &'static str {
match self {
Self::English => "English",
Self::Spanish => "Español",
Self::French => "Français",
Self::German => "Deutsch",
Self::Japanese => "日本語",
Self::Korean => "한국어",
}
}
pub fn strings(self) -> Strings {
match self {
Self::English => english::STRINGS,
Self::Spanish => spanish::STRINGS,
Self::French => french::STRINGS,
Self::German => german::STRINGS,
Self::Japanese => japanese::STRINGS,
Self::Korean => korean::STRINGS,
}
}
pub fn update_via_winget_label(self) -> &'static str {
match self {
Self::English => english::UPDATE_VIA_WINGET_LABEL,
Self::Spanish => spanish::UPDATE_VIA_WINGET_LABEL,
Self::French => french::UPDATE_VIA_WINGET_LABEL,
Self::German => german::UPDATE_VIA_WINGET_LABEL,
Self::Japanese => japanese::UPDATE_VIA_WINGET_LABEL,
Self::Korean => korean::UPDATE_VIA_WINGET_LABEL,
}
}
pub fn from_code(code: &str) -> Option<Self> {
let normalized = code.trim().replace('_', "-").to_ascii_lowercase();
if normalized.is_empty() || normalized == "system" {
return None;
}
match normalized.split('-').next().unwrap_or_default() {
"en" => Some(Self::English),
"es" => Some(Self::Spanish),
"fr" => Some(Self::French),
"de" => Some(Self::German),
"ja" => Some(Self::Japanese),
"ko" => Some(Self::Korean),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Strings {
pub window_title: &'static str,
pub refresh: &'static str,
pub update_frequency: &'static str,
pub one_minute: &'static str,
pub five_minutes: &'static str,
pub fifteen_minutes: &'static str,
pub one_hour: &'static str,
pub settings: &'static str,
pub start_with_windows: &'static str,
pub reset_position: &'static str,
pub language: &'static str,
pub system_default: &'static str,
pub check_for_updates: &'static str,
pub checking_for_updates: &'static str,
pub updates: &'static str,
pub update_in_progress: &'static str,
pub up_to_date: &'static str,
pub up_to_date_short: &'static str,
pub update_failed: &'static str,
pub applying_update: &'static str,
pub update_to: &'static str,
pub update_available: &'static str,
pub update_prompt_now: &'static str,
pub exit: &'static str,
pub session_window: &'static str,
pub weekly_window: &'static str,
pub now: &'static str,
pub day_suffix: &'static str,
pub hour_suffix: &'static str,
pub minute_suffix: &'static str,
pub second_suffix: &'static str,
}
pub fn resolve_language(language_override: Option<LanguageId>) -> LanguageId {
language_override.unwrap_or_else(detect_system_language)
}
pub fn detect_system_language() -> LanguageId {
preferred_ui_languages()
.into_iter()
.find_map(|locale| LanguageId::from_code(&locale))
.or_else(default_ui_locale)
.or_else(default_locale_name)
.unwrap_or(LanguageId::English)
}
pub fn update_via_winget(language: LanguageId) -> &'static str {
language.update_via_winget_label()
}
fn preferred_ui_languages() -> Vec<String> {
unsafe {
let mut num_languages = 0u32;
let mut buffer_len = 0u32;
if GetUserPreferredUILanguages(
MUI_LANGUAGE_NAME,
&mut num_languages,
PWSTR::null(),
&mut buffer_len,
)
.is_err()
|| buffer_len == 0
{
return Vec::new();
}
let mut buffer = vec![0u16; buffer_len as usize];
if GetUserPreferredUILanguages(
MUI_LANGUAGE_NAME,
&mut num_languages,
PWSTR(buffer.as_mut_ptr()),
&mut buffer_len,
)
.is_err()
{
return Vec::new();
}
buffer
.split(|unit| *unit == 0)
.filter(|part| !part.is_empty())
.map(String::from_utf16_lossy)
.collect()
}
}
fn default_ui_locale() -> Option<LanguageId> {
unsafe {
let lang_id = GetUserDefaultUILanguage();
let mut buffer = [0u16; MAX_LOCALE_NAME as usize];
let len = LCIDToLocaleName(
lang_id as u32,
Some(&mut buffer),
LOCALE_ALLOW_NEUTRAL_NAMES,
);
if len <= 1 {
return None;
}
let locale = String::from_utf16_lossy(&buffer[..(len as usize - 1)]);
LanguageId::from_code(&locale)
}
}
fn default_locale_name() -> Option<LanguageId> {
unsafe {
let mut buffer = [0u16; MAX_LOCALE_NAME as usize];
let len = GetUserDefaultLocaleName(&mut buffer);
if len <= 1 {
return None;
}
let locale = String::from_utf16_lossy(&buffer[..(len as usize - 1)]);
LanguageId::from_code(&locale)
}
}