feat: Add font loading functionality for multi-language support and preload base font
This commit is contained in:
BIN
qwen3-tts-frontend/fonts/noto-serif-jp-regular.woff2
Normal file
BIN
qwen3-tts-frontend/fonts/noto-serif-jp-regular.woff2
Normal file
Binary file not shown.
BIN
qwen3-tts-frontend/fonts/noto-serif-kr-regular.woff2
Normal file
BIN
qwen3-tts-frontend/fonts/noto-serif-kr-regular.woff2
Normal file
Binary file not shown.
BIN
qwen3-tts-frontend/fonts/noto-serif-tc-regular.woff2
Normal file
BIN
qwen3-tts-frontend/fonts/noto-serif-tc-regular.woff2
Normal file
Binary file not shown.
@@ -3,6 +3,7 @@ import { authApi } from '@/lib/api'
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import type { UserPreferences } from '@/types/auth'
|
||||
import i18n from '@/locales'
|
||||
import { loadFontsForLanguage, detectBrowserLanguage } from '@/lib/fontManager'
|
||||
|
||||
interface UserPreferencesContextType {
|
||||
preferences: UserPreferences | null
|
||||
@@ -24,6 +25,8 @@ export function UserPreferencesProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const fetchPreferences = async () => {
|
||||
if (!isAuthenticated || !user) {
|
||||
const browserLang = detectBrowserLanguage()
|
||||
await loadFontsForLanguage(browserLang)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -38,9 +41,11 @@ export function UserPreferencesProvider({ children }: { children: ReactNode }) {
|
||||
setPreferences(prefs)
|
||||
setHasAliyunKey(keyVerification.valid)
|
||||
|
||||
if (prefs.language) {
|
||||
i18n.changeLanguage(prefs.language)
|
||||
}
|
||||
const lang = prefs.language || detectBrowserLanguage()
|
||||
await Promise.all([
|
||||
i18n.changeLanguage(lang),
|
||||
loadFontsForLanguage(lang),
|
||||
])
|
||||
|
||||
const cacheKey = `user_preferences_${user.id}`
|
||||
localStorage.setItem(cacheKey, JSON.stringify(prefs))
|
||||
@@ -48,8 +53,14 @@ export function UserPreferencesProvider({ children }: { children: ReactNode }) {
|
||||
const cacheKey = `user_preferences_${user.id}`
|
||||
const cached = localStorage.getItem(cacheKey)
|
||||
if (cached) {
|
||||
setPreferences(JSON.parse(cached))
|
||||
const cachedPrefs = JSON.parse(cached)
|
||||
setPreferences(cachedPrefs)
|
||||
if (cachedPrefs.language) {
|
||||
await loadFontsForLanguage(cachedPrefs.language)
|
||||
}
|
||||
} else {
|
||||
const browserLang = detectBrowserLanguage()
|
||||
await loadFontsForLanguage(browserLang)
|
||||
setPreferences({ default_backend: 'aliyun', onboarding_completed: false })
|
||||
}
|
||||
} finally {
|
||||
@@ -87,7 +98,10 @@ export function UserPreferencesProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
const changeLanguage = async (lang: 'zh-CN' | 'zh-TW' | 'en-US' | 'ja-JP' | 'ko-KR') => {
|
||||
await i18n.changeLanguage(lang)
|
||||
await Promise.all([
|
||||
i18n.changeLanguage(lang),
|
||||
loadFontsForLanguage(lang),
|
||||
])
|
||||
await updatePreferences({ language: lang })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
@font-face {
|
||||
font-family: 'Noto Serif';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/noto-serif-regular.woff2') format('woff2');
|
||||
unicode-range: 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;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Noto Serif';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/noto-serif-sc-regular.woff2') format('woff2');
|
||||
unicode-range: U+4E00-9FFF, U+3400-4DBF, U+20000-2A6DF, U+2A700-2B73F,
|
||||
U+2B740-2B81F, U+2B820-2CEAF, U+F900-FAFF, U+2F800-2FA1F;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
153
qwen3-tts-frontend/src/lib/fontManager.ts
Normal file
153
qwen3-tts-frontend/src/lib/fontManager.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
type Language = 'zh-CN' | 'zh-TW' | 'en-US' | 'ja-JP' | 'ko-KR'
|
||||
|
||||
interface FontConfig {
|
||||
name: string
|
||||
file: string
|
||||
unicodeRange?: string
|
||||
}
|
||||
|
||||
const fontConfigs: Record<Language, FontConfig[]> = {
|
||||
'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-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-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-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-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-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<string>()
|
||||
|
||||
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 async function loadFontsForLanguage(language: Language): Promise<void> {
|
||||
const configs = fontConfigs[language]
|
||||
if (!configs) return
|
||||
|
||||
const loadPromises = configs.map(async (config) => {
|
||||
const fontKey = `${config.name}-${config.file}`
|
||||
|
||||
if (loadedFonts.has(fontKey)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const fontFace = createFontFace(config)
|
||||
await fontFace.load()
|
||||
document.fonts.add(fontFace)
|
||||
loadedFonts.add(fontKey)
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load font ${config.file}:`, error)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(loadPromises)
|
||||
}
|
||||
|
||||
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-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
|
||||
}
|
||||
|
||||
const fontFace = createFontFace(baseConfig)
|
||||
fontFace.load()
|
||||
.then(() => {
|
||||
document.fonts.add(fontFace)
|
||||
loadedFonts.add(fontKey)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('Failed to preload base font:', error)
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import { createRoot } from 'react-dom/client'
|
||||
import './locales'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { preloadBaseFont } from './lib/fontManager'
|
||||
|
||||
preloadBaseFont()
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
|
||||
Reference in New Issue
Block a user