1use dioxus::prelude::*;
2pub use dioxus_translate_macro::*;
3pub use dioxus_translate_types::Translator;
4
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8static LANGUAGE: GlobalSignal<Language> = Signal::global(|| {
9 #[cfg(target_arch = "wasm32")]
10 {
11 if let Some(lang) = read_local_storage(STORAGE_KEY) {
12 if let Ok(l) = lang.parse::<Language>() {
13 return l;
14 }
15 }
16
17 if let Some(lang) = browser_language() {
18 if let Ok(l) = lang.parse::<Language>() {
19 return l;
20 }
21 }
22 }
23
24 Language::default()
25});
26
27pub const STORAGE_KEY: &str = "language";
28
29pub fn use_translate<T: Translator>() -> T {
30 let lang = use_language();
31 let l = lang();
32
33 translate::<T>(&l)
34}
35
36#[cfg(target_arch = "wasm32")]
37pub fn use_language() -> Signal<Language> {
38 LANGUAGE.signal()
39}
40
41#[cfg(not(target_arch = "wasm32"))]
49pub fn use_language() -> Signal<Language> {
50 use dioxus::fullstack::FullstackContext;
51 if FullstackContext::current().is_some() {
52 use_signal(|| language_from_cookie())
53 } else {
54 LANGUAGE.signal()
55 }
56}
57
58pub fn set_initial_language(lang: Language) {
62 LANGUAGE.signal().set(lang);
63}
64
65#[cfg(not(target_arch = "wasm32"))]
69pub fn language_from_cookie() -> Language {
70 use dioxus::fullstack::FullstackContext;
71
72 let Some(ctx) = FullstackContext::current() else {
73 return Language::default();
74 };
75 let parts = ctx.parts_mut();
76 parts
77 .headers
78 .get("cookie")
79 .and_then(|v| v.to_str().ok())
80 .and_then(|cookies| {
81 cookies
82 .split(';')
83 .find_map(|c| c.trim().strip_prefix("language="))
84 })
85 .and_then(|v| v.parse::<Language>().ok())
86 .unwrap_or_default()
87}
88
89pub fn set_language(lang: Language) {
91 LANGUAGE.signal().set(lang);
92
93 #[cfg(target_arch = "wasm32")]
94 {
95 use web_sys::wasm_bindgen::JsCast;
96
97 if let Some(window) = web_sys::window() {
98 if let Ok(Some(storage)) = window.local_storage() {
99 let _ = storage.set_item(STORAGE_KEY, &next.to_string());
100 }
101
102 if let Some(doc) = window.document() {
103 let html_document = doc.dyn_into::<web_sys::HtmlDocument>().unwrap();
104 let _ = html_document.set_cookie(&format!("language={}; path=/;", next));
105 }
106 }
107 }
108}
109
110#[cfg(target_arch = "wasm32")]
111fn read_local_storage(key: &str) -> Option<String> {
112 web_sys::window()?
113 .local_storage()
114 .ok()??
115 .get_item(key)
116 .ok()?
117}
118
119#[cfg(target_arch = "wasm32")]
120fn browser_language() -> Option<String> {
121 let lang = web_sys::window()?.navigator().language()?;
122 Some(lang.split('-').next().unwrap_or(&lang).to_string())
123}
124
125pub fn translate<T: Translator>(lang: &Language) -> T {
126 match lang {
127 #[cfg(feature = "ko")]
128 Language::Ko => T::ko(),
129 Language::En => T::en(),
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy, JsonSchema)]
134pub enum Language {
135 #[cfg(feature = "ko")]
136 #[serde(rename = "ko")]
137 Ko,
138 #[serde(rename = "en")]
139 En,
140}
141
142impl Default for Language {
143 fn default() -> Self {
144 Language::En
145 }
146}
147
148impl Language {
149 pub fn switch(&self) -> Self {
150 #[cfg(feature = "ko")]
151 let next = match self {
152 Language::Ko => Language::En,
153 Language::En => Language::Ko,
154 };
155
156 #[cfg(not(feature = "ko"))]
157 let next = Language::En;
158
159 set_language(next);
160
161 next
162 }
163
164 pub fn open_graph_locale(&self) -> String {
165 match self {
166 #[cfg(feature = "ko")]
167 Language::Ko => "ko_KR".to_string(),
168 Language::En => "en_US".to_string(),
169 }
170 }
171}
172
173impl std::fmt::Display for Language {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 match self {
176 #[cfg(feature = "ko")]
177 Language::Ko => write!(f, "ko"),
178 Language::En => write!(f, "en"),
179 }
180 }
181}
182
183impl std::str::FromStr for Language {
184 type Err = String;
185
186 fn from_str(s: &str) -> Result<Self, Self::Err> {
187 match s {
188 #[cfg(feature = "ko")]
189 "ko" => Ok(Language::Ko),
190 "en" => Ok(Language::En),
191 _ => Ok(Language::En),
192 }
193 }
194}
195
196impl Language {
197 pub fn to_string(&self) -> String {
198 match self {
199 #[cfg(feature = "ko")]
200 Language::Ko => "ko".to_string(),
201 Language::En => "en".to_string(),
202 }
203 }
204
205 pub fn all() -> Vec<Language> {
206 #[cfg(feature = "ko")]
207 {
208 vec![Language::Ko, Language::En]
209 }
210 #[cfg(not(feature = "ko"))]
211 {
212 vec![Language::En]
213 }
214 }
215}
216
217pub trait Translate {
218 fn translate(&self, lang: &Language) -> &'static str;
219}