Internationalization (i18n)
Relevant source files
The following files were used as context for generating this wiki page:
The internationalization (i18n) system in Endfield-Pomodoro provides bilingual support for the application, enabling all UI text to be displayed in either English (EN) or Chinese (CN). The system is designed to be simple, centralized, and type-safe. All translation strings are managed within a single file, src/utils/i18n.ts, and are accessed throughout the application via a custom React hook, useTranslation.
The application determines the default language based on the user's browser settings and persists the user's language preference in localStorage. This allows for a seamless experience across sessions. Language changes are applied instantly without requiring a page refresh, as the UI dynamically re-renders with the new language strings.
Architecture
The i18n architecture is centered around the main App.tsx component, which manages the application's language state. This state is passed down to child components, which then use the useTranslation hook to retrieve the appropriate text strings from the translations object.
Data Flow
The following diagram illustrates the data flow for language selection and translation rendering.
This diagram shows that the user changes the language via the settings UI, which updates the state in App.tsx. This state is saved to localStorage and also used by the useTranslation hook to provide the correct translation function to all child components.
Sources: src/App.tsx:31-45, 142-148, 411-419, src/utils/i18n.ts:1-248
Core Components
The i18n system is built upon a few key files and structures.
types.ts: Language Definition
The Language enum provides a type-safe way to represent the supported languages throughout the application.
export const Language = {
EN: 'EN',
CN: 'CN'
} as const;
export type Language = typeof Language[keyof typeof Language];
Sources: src/types.ts:8-12
i18n.ts: Translation Storage and Hook
This file is the heart of the i18n system. It contains:
translationsobject: A large object that maps language enums (EN,CN) to a dictionary of translation keys and their corresponding string values.useTranslationhook: A function that takes the current language as an argument and returns a translation function,t. Thetfunction accepts a translation key and returns the translated string for the active language.
// src/utils/i18n.ts
import { Language } from '../types';
export const translations = {
[Language.EN]: {
APP_TITLE: 'Endfield Protocol',
DASHBOARD: 'DASHBOARD',
// ... more keys
},
[Language.CN]: {
APP_TITLE: '末端协议-番茄钟',
DASHBOARD: '总控台',
// ... more keys
}
};
export const useTranslation = (lang: Language) => {
return (key: keyof typeof translations['EN']) => {
return translations[lang][key] || key;
};
};
Sources: src/utils/i18n.ts:3-248
App.tsx: State Management
The main App component is responsible for managing and persisting the language setting.
Initialization
On initial load, the application attempts to load settings from localStorage. If no settings are found, it uses DEFAULT_SETTINGS. The default language within DEFAULT_SETTINGS is determined by inspecting navigator.languages. If a Chinese language variant ('zh') is detected, it defaults to Language.CN; otherwise, it defaults to Language.EN.
// src/App.tsx
const DEFAULT_SETTINGS: Settings = {
// ... other settings
language: ((() => {
const browserLangs = navigator.languages || [navigator.language];
return browserLangs.some(lang => lang?.toLowerCase().startsWith('zh')) ? Language.CN : Language.EN;
})()),
// ...
};
const App: React.FC = () => {
const [settings, setSettings] = useState<Settings>(() => {
const saved = localStorage.getItem(STORAGE_KEYS.SETTINGS);
if (saved) {
// ... parsing logic
return loadedSettings;
}
return DEFAULT_SETTINGS;
});
// ...
};
Sources: src/App.tsx:29-37, 65-79
Persistence
An useEffect hook ensures that any changes to the settings object, including the language, are immediately saved to localStorage.
// src/App.tsx
useEffect(() => {
try {
localStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
} catch (e) {
console.error('Failed to persist settings', e);
}
}, [settings]);
Sources: src/App.tsx:142-148
Usage
Language Selection
The user can change the language from the "SYSTEM CONFIG" view. A <CustomSelect> component displays the available languages and updates the application's state upon selection.
The sequence of changing the language is as follows:
Sources: src/App.tsx:411-419
Consuming Translations in Components
Components receive the translation function t and use it to render text. In App.tsx, the t function is created based on the current language state. This function is then used for all text within App.tsx and can be passed down to child components if needed, or child components can create their own instance of the t function by using the useTranslation hook with a language prop.
Example from PlayerInterface.tsx:
// src/components/PlayerInterface.tsx
const PlayerInterface: React.FC<PlayerInterfaceProps> = ({ language, ... }) => {
const t = useTranslation(language);
return (
// ...
<span className="text-[10px] text-theme-dim uppercase tracking-widest">
{isLoading ? t('CONNECTING') : t('STATUS')}
</span>
// ...
);
};
Sources: src/App.tsx:121, src/components/PlayerInterface.tsx:46, 92-94, src/components/TaskManager.tsx:14
Key Files and Roles
| File | Role in i18n |
|---|---|
src/utils/i18n.ts | Centralizes all translation strings and provides the useTranslation hook. |
src/types.ts | Defines the Language enum for type safety. |
src/App.tsx | Manages and persists the current language state. Provides the language setting to the rest of the app. |
src/components/* | Consumer components that use the useTranslation hook to display localized text. |
index.html | Sets the default lang attribute on the <html> tag to zh-CN. |
Sources: src/utils/i18n.ts, src/types.ts:8-12, src/App.tsx, index.html:2
Conclusion
The internationalization system of Endfield-Pomodoro is a straightforward and effective implementation that leverages React's state management and hooks. By centralizing strings in i18n.ts and managing the language state in the top-level App.tsx component, the system is easy to maintain and extend. The use of a Language enum from types.ts ensures type safety, reducing potential runtime errors. This architecture allows for dynamic, real-time language switching and provides a solid foundation for adding more languages in the future.