diablo2-web/src/i18n/lang.ts

152 lines
4.8 KiB
TypeScript

/**
* Centralized language detection & runtime state (`CHI` / `'zh'` vs `ENG` / `'en'`)
* for Diablo II v1.13c web port (Issue #503).
*/
import { resolveTblKey } from '../data/canonical-tbl-strings.ts'
export type GameLang = 'zh' | 'en'
export type TblLang = 'CHI' | 'ENG'
export function lookupTbl(key: string, lang: TblLang = getTblLang()): string | undefined {
return resolveTblKey(key, lang)
}
let overrideLang: GameLang | null = null
const listeners = new Set<(lang: TblLang) => void>()
export function toGameLang(lang: GameLang | TblLang | string): GameLang {
const norm = String(lang).trim().toLowerCase()
return norm === 'en' || norm === 'eng' || norm === 'english' ? 'en' : 'zh'
}
export function toTblLang(lang: GameLang | TblLang | string): TblLang {
return toGameLang(lang) === 'en' ? 'ENG' : 'CHI'
}
export function detectLangFromUrl(urlStr?: string): TblLang {
if (typeof urlStr === 'string' && urlStr.trim() !== '') {
try {
const u = new URL(urlStr, 'http://localhost')
const q = u.searchParams.get('lang')?.trim().toLowerCase()
if (q === 'en' || q === 'eng' || q === 'english') return 'ENG'
if (q === 'zh' || q === 'chi' || q === 'zh-cn' || q === 'cn' || q === 'chinese') return 'CHI'
const path = u.pathname.toLowerCase()
if (path.endsWith('/acts-en.html') || path.endsWith('acts-en.html') || path.endsWith('/arena-en.html') || path.endsWith('arena-en.html') || path.endsWith('/bnet.html') || path.endsWith('bnet.html')) {
return 'ENG'
}
if (path.endsWith('/acts-zh.html') || path.endsWith('acts-zh.html') || path.endsWith('/arena-zh.html') || path.endsWith('arena-zh.html')) {
return 'CHI'
}
return 'CHI'
} catch {
// fall through
}
}
return detectInitialLang() === 'en' ? 'ENG' : 'CHI'
}
export function detectInitialLang(): GameLang {
if (typeof window !== 'undefined' && window.location) {
try {
const params = new URLSearchParams(window.location.search || '')
const q = params.get('lang')?.trim().toLowerCase()
if (q === 'en' || q === 'eng' || q === 'english') return 'en'
if (q === 'zh' || q === 'chi' || q === 'zh-cn' || q === 'cn' || q === 'chinese') return 'zh'
} catch {
// Ignore malformed query string
}
const path = (window.location.pathname || '').toLowerCase()
if (path.endsWith('/acts-en.html') || path.endsWith('acts-en.html') || path.endsWith('/arena-en.html') || path.endsWith('arena-en.html') || path.endsWith('/bnet.html') || path.endsWith('bnet.html')) {
return 'en'
}
if (path.endsWith('/acts-zh.html') || path.endsWith('acts-zh.html') || path.endsWith('/arena-zh.html') || path.endsWith('arena-zh.html')) {
return 'zh'
}
}
if (typeof document !== 'undefined' && document.documentElement?.lang) {
const docLang = document.documentElement.lang.trim().toLowerCase()
if (docLang.startsWith('en')) return 'en'
if (docLang.startsWith('zh')) return 'zh'
}
return 'zh'
}
export function getGameLang(): GameLang {
return overrideLang ?? detectInitialLang()
}
export function getCurrentLang(): GameLang {
return getGameLang()
}
export function getTblLang(): TblLang {
return getGameLang() === 'en' ? 'ENG' : 'CHI'
}
export function isEn(): boolean {
return getGameLang() === 'en'
}
export function isEnglish(): boolean {
return getGameLang() === 'en'
}
function notifyListeners(): void {
const tbl = getTblLang()
for (const fn of listeners) {
try {
fn(tbl)
} catch {
// ignore listener errors
}
}
}
export function setGameLang(lang: GameLang | TblLang | null): void {
if (lang === null) {
overrideLang = null
notifyListeners()
return
}
overrideLang = toGameLang(lang)
notifyListeners()
}
export function setCurrentLang(lang: GameLang | TblLang | null): void {
setGameLang(lang)
}
export function setTblLang(lang: TblLang | GameLang | null): void {
setGameLang(lang)
}
export function initLangFromBrowser(): TblLang {
overrideLang = detectInitialLang()
notifyListeners()
return getTblLang()
}
export function onTblLangChange(listener: (lang: TblLang) => void): () => void {
listeners.add(listener)
return () => {
listeners.delete(listener)
}
}
export function buildLangSwitchHref(
targetLang?: TblLang | GameLang,
currentPath?: string,
currentSearch?: string,
): string {
const target = targetLang ? toTblLang(targetLang) : (getTblLang() === 'ENG' ? 'CHI' : 'ENG')
const searchStr = currentSearch ?? (typeof window !== 'undefined' && window.location ? window.location.search : '')
const params = new URLSearchParams(searchStr || '')
params.delete('lang')
const qs = params.toString()
const targetPage = target === 'ENG' ? '/acts-en.html' : '/acts-zh.html'
void currentPath
return qs ? `${targetPage}?${qs}` : targetPage
}