152 lines
4.8 KiB
TypeScript
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
|
|
}
|