type Language = 'zh-CN' | 'zh-TW' | 'en-US' | 'ja-JP' | 'ko-KR' interface FontConfig { name: string file: string unicodeRange?: string } const fontConfigs: Record = { 'zh-CN': [ { name: 'Noto Serif', file: '/fonts/noto-serif-sc-regular.woff2', unicodeRange: 'U+4E00-9FFF, U+3400-4DBF, U+20000-2A6DF, U+2A700-2B73F, U+2B740-2B81F, U+2B820-2CEAF, U+F900-FAFF, U+2F800-2FA1F', }, { name: 'Noto Serif', file: '/fonts/noto-serif-latin-regular.woff2', unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD', }, ], 'zh-TW': [ { name: 'Noto Serif', file: '/fonts/noto-serif-tc-regular.woff2', unicodeRange: 'U+4E00-9FFF, U+3400-4DBF, U+F900-FAFF', }, { name: 'Noto Serif', file: '/fonts/noto-serif-sc-regular.woff2', unicodeRange: 'U+20000-2A6DF, U+2A700-2B73F, U+2B740-2B81F, U+2B820-2CEAF, U+2F800-2FA1F', }, { name: 'Noto Serif', file: '/fonts/noto-serif-latin-regular.woff2', unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD', }, ], 'en-US': [ { name: 'Noto Serif', file: '/fonts/noto-serif-latin-regular.woff2', unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD', }, ], 'ja-JP': [ { name: 'Noto Serif', file: '/fonts/noto-serif-jp-regular.woff2', unicodeRange: 'U+3000-30FF, U+4E00-9FFF, U+FF00-FFEF', }, { name: 'Noto Serif', file: '/fonts/noto-serif-latin-regular.woff2', unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD', }, ], 'ko-KR': [ { name: 'Noto Serif', file: '/fonts/noto-serif-kr-regular.woff2', unicodeRange: 'U+AC00-D7AF, U+1100-11FF, U+3130-318F', }, { name: 'Noto Serif', file: '/fonts/noto-serif-latin-regular.woff2', unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD', }, ], } const loadedFonts = new Set() function createFontFace(config: FontConfig): FontFace { const descriptors: FontFaceDescriptors = { style: 'normal', weight: '400', display: 'swap', } if (config.unicodeRange) { descriptors.unicodeRange = config.unicodeRange } return new FontFace(config.name, `url(${config.file}) format('woff2')`, descriptors) } export function loadFontsForLanguage(language: Language): void { const configs = fontConfigs[language] if (!configs) return configs.forEach((config) => { const fontKey = `${config.name}-${config.file}` if (loadedFonts.has(fontKey)) { return } try { const fontFace = createFontFace(config) document.fonts.add(fontFace) loadedFonts.add(fontKey) } catch (error) { console.warn(`Failed to register font ${config.file}:`, error) } }) } export function detectBrowserLanguage(): Language { const browserLang = navigator.language.toLowerCase() if (browserLang.startsWith('zh-tw') || browserLang.startsWith('zh-hk') || browserLang.startsWith('zh-mo')) { return 'zh-TW' } if (browserLang.startsWith('zh')) { return 'zh-CN' } if (browserLang.startsWith('ja')) { return 'ja-JP' } if (browserLang.startsWith('ko')) { return 'ko-KR' } return 'en-US' } export function preloadBaseFont(): void { const baseConfig: FontConfig = { name: 'Noto Serif', file: '/fonts/noto-serif-latin-regular.woff2', unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD', } const fontKey = `${baseConfig.name}-${baseConfig.file}` if (loadedFonts.has(fontKey)) { return } try { const fontFace = createFontFace(baseConfig) document.fonts.add(fontFace) loadedFonts.add(fontKey) } catch (error) { console.warn('Failed to register base font:', error) } }