diff --git a/README.md b/README.md index 4ee6c3a..d694b6a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ sr 是一個架空世界觀的 2D 六角格回合制策略遊戲:**手牌 + 再由使用者選擇 iframe 嵌入或另開新分頁。 3. 服務目錄、探活與分流屬於獨立的 `hoshi-svc`,不在這個靜態網站的部署物內。 +公開頁面使用 `hoshi_cookie_consent` 區分「只用必要 Cookie」與「記住偏好」。訪客明確 +選擇語言或接受偏好後,語言會寫入 `.hoshivel.com` 範圍的 `hoshi_lang`,供 Hoshivel、 +Hoshi ID 與遊戲入口共用;本站維持固定深色美術方向,不會覆寫其他頁面的外觀偏好。 +偏好 Cookie 不承載分析或追蹤資料,登入與安全 Cookie 也不受這個選項停用。 + 具體流程:官網 Play → `GET https://svc.hoshivel.com/v1/services/sr-game/route?endpoint=web` → 取得建議與候選節點 → 使用者進入後才載入該節點原始 `web` endpoint。 diff --git a/package.json b/package.json index 2a50136..fcd41cc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "scripts": { "dev": "astro dev", "start": "astro dev", - "test": "node --experimental-strip-types --test test/play.test.mjs", + "test": "node --experimental-strip-types --test test/*.test.mjs", "build": "npm test && astro check && astro build", "preview": "astro preview", "check": "astro check", diff --git a/src/components/CookiePreferences.astro b/src/components/CookiePreferences.astro new file mode 100644 index 0000000..66c6a71 --- /dev/null +++ b/src/components/CookiePreferences.astro @@ -0,0 +1,133 @@ +--- +import { useTranslations, type Locale } from "@/i18n/utils"; +import { globalLanguageForLocale } from "@/lib/preferences"; + +interface Props { + locale: Locale; +} + +const { locale } = Astro.props; +const t = useTranslations(locale); +const language = globalLanguageForLocale(locale); +--- + + + + + + diff --git a/src/components/LanguageSwitcher.astro b/src/components/LanguageSwitcher.astro index 425680c..20610c3 100644 --- a/src/components/LanguageSwitcher.astro +++ b/src/components/LanguageSwitcher.astro @@ -14,6 +14,7 @@ import { HTML_LANG, type Locale, } from "@/i18n/utils"; +import { globalLanguageForLocale } from "@/lib/preferences"; interface Props { locale: Locale; @@ -36,6 +37,7 @@ const t = useTranslations(locale); hreflang={HTML_LANG[l]} title={LOCALE_LABEL[l]} aria-current={active ? "true" : undefined} + data-hoshi-language={globalLanguageForLocale(l)} > {LOCALE_SHORT[l]} diff --git a/src/components/PreferenceBootstrap.astro b/src/components/PreferenceBootstrap.astro new file mode 100644 index 0000000..247ef88 --- /dev/null +++ b/src/components/PreferenceBootstrap.astro @@ -0,0 +1,52 @@ +--- +import { + LOCALES, + DEFAULT_LOCALE, + localizedPath, + type Locale, +} from "@/i18n/utils"; +import { globalLanguageForLocale } from "@/lib/preferences"; + +interface Props { + locale: Locale; + path: string; +} + +const { locale, path } = Astro.props; +const routes = Object.fromEntries( + LOCALES.map((candidate) => [ + globalLanguageForLocale(candidate), + localizedPath(candidate, path), + ]), +); +const redirectFromDefault = locale === DEFAULT_LOCALE && path === "/"; +--- + + diff --git a/src/i18n/ui.ts b/src/i18n/ui.ts index 1955216..a65a3c6 100644 --- a/src/i18n/ui.ts +++ b/src/i18n/ui.ts @@ -82,6 +82,13 @@ const zhHant = { "footer.summary": "破碎星空之下,啟程未竟之旅", "footer.rights": "碎界 Shattered Realms", + "cookie.settings": "Cookie 設定", + "cookie.title": "記住你的顯示偏好", + "cookie.body": + "我們不使用分析或追蹤 Cookie。你可以讓 Hoshivel 旗下網站共用語言偏好;外觀只在支援的頁面套用。登入與安全所需的 Cookie 永遠啟用。", + "cookie.essential": "只使用必要 Cookie", + "cookie.remember": "記住偏好", + "hero.badge": "正式上線", // 預覽 Teaser(#teaser)—— 只有眉標與播放器,不加說明文字 @@ -236,6 +243,13 @@ const zhCN: Record = { "footer.summary": "破碎星空之下,启程未竟之旅", "footer.rights": "碎界 Shattered Realms", + "cookie.settings": "Cookie 设置", + "cookie.title": "记住你的显示偏好", + "cookie.body": + "我们不使用分析或跟踪 Cookie。你可以让 Hoshivel 旗下网站共享语言偏好;外观只在支持的页面应用。登录与安全所需的 Cookie 始终启用。", + "cookie.essential": "仅使用必要 Cookie", + "cookie.remember": "记住偏好", + "hero.badge": "正式上线", "teaser.eyebrow": "预览", @@ -380,6 +394,13 @@ const en: Record = { "footer.summary": "Beneath a shattered sky, the journey begins", "footer.rights": "Shattered Realms", + "cookie.settings": "Cookie settings", + "cookie.title": "Remember your display preferences", + "cookie.body": + "We do not use analytics or tracking cookies. You can share your language across Hoshivel sites; appearance applies only on pages that support it. Cookies required for sign-in and security are always enabled.", + "cookie.essential": "Essential only", + "cookie.remember": "Remember preferences", + "hero.badge": "Now live", "teaser.eyebrow": "Preview", @@ -533,6 +554,13 @@ const ja: Record = { "footer.summary": "砕けた星空の下、未完の旅へ", "footer.rights": "砕界 Shattered Realms", + "cookie.settings": "Cookie 設定", + "cookie.title": "表示設定を保存", + "cookie.body": + "解析や追跡の Cookie は使用しません。Hoshivel の各サイトで言語を共有できます。外観は対応ページにのみ適用されます。ログインとセキュリティに必要な Cookie は常に有効です。", + "cookie.essential": "必須 Cookie のみ", + "cookie.remember": "設定を保存", + "hero.badge": "正式サービス中", "teaser.eyebrow": "プレビュー", diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index e894a36..a2d8b46 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -2,6 +2,8 @@ import "@/styles/global.css"; import Header from "@/components/Header.astro"; import Footer from "@/components/Footer.astro"; +import PreferenceBootstrap from "@/components/PreferenceBootstrap.astro"; +import CookiePreferences from "@/components/CookiePreferences.astro"; import { useTranslations, localizedPath, @@ -56,6 +58,7 @@ const xDefault = Astro.site + @@ -88,6 +91,7 @@ const xDefault = Astro.site