feat(i18n): add i18n support

This commit is contained in:
leookun
2026-08-24 03:08:29 +08:00
parent 2abffcc835
commit 03075952a6
20 changed files with 932 additions and 354 deletions
+25 -2
View File
@@ -15,11 +15,12 @@ use super::ControlService;
// 此广告拉取不涉及用户隐私,用户id随机产生
// 开源项目广告为作者唯一收入来源,PR时请勿去除此广告
pub(super) const ADS_ENDPOINT: &str = "http://127.0.0.1:8080/api/v1/ads?placement=menu";
pub(super) const ADS_ENDPOINT: &str = "https://tab.leokun.cn/api/v1/ads?placement=menu";
pub(super) const DEVICE_ID_HEADER: &str = "X-Cursor-Assistant-Device-ID";
pub(super) const OS_HEADER: &str = "X-Cursor-Assistant-OS";
pub(super) const APP_VERSION_HEADER: &str = "X-Cursor-Assistant-Version";
pub(super) const DISABLED_AD_IDS_HEADER: &str = "disable-ad-ids";
pub(super) const LANGUAGE_HEADER: &str = "accept-language";
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AdRuntime {
@@ -121,7 +122,19 @@ pub async fn get(
let disabled_ad_ids = headers
.get(DISABLED_AD_IDS_HEADER)
.and_then(|value| value.to_str().ok());
Ok(Json(service.ads(disabled_ad_ids).await?))
Ok(Json(
service.ads(disabled_ad_ids, ad_language(&headers)).await?,
))
}
fn ad_language(headers: &HeaderMap) -> &'static str {
match headers
.get(LANGUAGE_HEADER)
.and_then(|value| value.to_str().ok())
{
Some(value) if value.eq_ignore_ascii_case("zh-CN") => "zh-CN",
_ => "en-US",
}
}
pub async fn dismiss(
@@ -137,6 +150,16 @@ pub async fn dismiss(
mod tests {
use super::*;
#[test]
fn accepts_supported_ad_language_and_falls_back_to_english() {
let mut headers = HeaderMap::new();
headers.insert(LANGUAGE_HEADER, "zh-CN".parse().unwrap());
assert_eq!(ad_language(&headers), "zh-CN");
headers.insert(LANGUAGE_HEADER, "fr-FR".parse().unwrap());
assert_eq!(ad_language(&headers), "en-US");
}
#[test]
fn filters_disabled_slots_without_limiting_menu_ads() {
let slot = |id: &str, enabled| AdSlot {
+1
View File
@@ -185,6 +185,7 @@ fn desktop_cors() -> CorsLayer {
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers([
CONTENT_TYPE,
header::ACCEPT_LANGUAGE,
header::HeaderName::from_static("disable-ad-ids"),
])
}
+7 -2
View File
@@ -7,7 +7,7 @@ use url::Url;
use super::ads::{
AdDismissalInput, AdRuntime, ADS_ENDPOINT, APP_VERSION_HEADER, DEVICE_ID_HEADER,
DISABLED_AD_IDS_HEADER, OS_HEADER,
DISABLED_AD_IDS_HEADER, LANGUAGE_HEADER, OS_HEADER,
};
use crate::{
@@ -83,7 +83,11 @@ impl ControlService {
&self.cursor_harness
}
pub(super) async fn ads(&self, disabled_ad_ids: Option<&str>) -> Result<AdRuntime> {
pub(super) async fn ads(
&self,
disabled_ad_ids: Option<&str>,
language: &str,
) -> Result<AdRuntime> {
let client = crate::network::client(&self.store).await?;
let installation_id = self.store.installation_id().await?;
let mut request = client
@@ -91,6 +95,7 @@ impl ControlService {
.header(DEVICE_ID_HEADER, installation_id)
.header(OS_HEADER, std::env::consts::OS)
.header(APP_VERSION_HEADER, env!("CARGO_PKG_VERSION"))
.header(LANGUAGE_HEADER, language)
.timeout(std::time::Duration::from_secs(5));
if let Some(disabled_ad_ids) = disabled_ad_ids.filter(|value| !value.is_empty()) {
request = request.header(DISABLED_AD_IDS_HEADER, disabled_ad_ids);