Internationalization
Support multiple languages with i18next in EZ-Console applications.
Overview
EZ-Console uses react-i18next for internationalization (i18n) with built-in support for multiple languages. This allows you to create applications that support multiple languages and locales, providing a better user experience for global audiences.
Quick Start
Basic Usage
// src/App.tsx
import { i18n, useTranslation } from 'ez-console';
// Add translations
i18n.addResource('en', 'translation', 'product.name', 'Product Name');
i18n.addResource('zh-CN', 'translation', 'product.name', '产品名称');
// Use in components
export const ProductForm: React.FC = () => {
const { t } = useTranslation();
return (
<Form>
<Form.Item label={t('product.name')}>
<Input />
</Form.Item>
</Form>
);
};
Adding Translations
In App Component
Add translations directly in your App component:
// src/App.tsx
import { i18n } from 'ez-console';
// Add single translation
i18n.addResource('en', 'translation', 'product.name', 'Product Name');
i18n.addResource('zh-CN', 'translation', 'product.name', '产品名称');
// Add multiple translations at once
i18n.addResources('en', 'translation', {
'product.name': 'Product Name',
'product.description': 'Description',
'product.price': 'Price',
'product.create': 'Create Product',
'product.edit': 'Edit Product',
'product.delete': 'Delete Product',
});
i18n.addResources('zh-CN', 'translation', {
'product.name': '产品名称',
'product.description': '描述',
'product.price': '价格',
'product.create': '创建产品',
'product.edit': '编辑产品',
'product.delete': '删除产品',
});
In Separate Files
For better organization, create separate translation files:
// src/i18n/en/product.ts
export default {
'product.name': 'Product Name',
'product.description': 'Description',
'product.price': 'Price',
'product.stock': 'Stock',
'product.category': 'Category',
'product.create': 'Create Product',
'product.edit': 'Edit Product',
'product.delete': 'Delete Product',
'product.list': 'Product List',
'product.detail': 'Product Detail',
'product.searchPlaceholder': 'Search products...',
};
// src/i18n/zh-CN/product.ts
export default {
'product.name': '产品名称',
'product.description': '描述',
'product.price': '价格',
'product.stock': '库存',
'product.category': '分类',
'product.create': '创建产品',
'product.edit': '编辑产品',
'product.delete': '删除产品',
'product.list': '产品列表',
'product.detail': '产品详情',
'product.searchPlaceholder': '搜索产品...',
};
Then load translations in your App component:
// src/App.tsx
import { i18n } from 'ez-console';
import enProduct from './i18n/en/product';
import zhProduct from './i18n/zh-CN/product';
i18n.addResources('en', 'translation', enProduct);
i18n.addResources('zh-CN', 'translation', zhProduct);
Using Translations
In Components
Use the useTranslation hook to access translations:
import { useTranslation } from 'ez-console';
export const ProductForm: React.FC = () => {
const { t } = useTranslation();
return (
<Form>
<Form.Item label={t('product.name')}>
<Input placeholder={t('product.searchPlaceholder')} />
</Form.Item>
<Button type="primary">{t('product.create')}</Button>
</Form>
);
};
With Parameters
Use parameters for dynamic content:
// Add translation with parameter
i18n.addResource('en', 'translation', 'product.itemsCount',
'Showing {{count}} products');
i18n.addResource('zh-CN', 'translation', 'product.itemsCount',
'显示 {{count}} 个产品');
// Use in component
const { t } = useTranslation();
<p>{t('product.itemsCount', { count: 10 })}</p>
// Output: "Showing 10 products" (en) or "显示 10 个产品" (zh-CN)
Pluralization
Handle plural forms correctly:
// English (requires plural forms)
i18n.addResources('en', 'translation', {
'product.count_one': '{{count}} product',
'product.count_other': '{{count}} products',
});
// Chinese (no plural form needed)
i18n.addResources('zh-CN', 'translation', {
'product.count': '{{count}} 个产品',
});
// Use in component
const { t } = useTranslation();
<p>{t('product.count', { count: 1 })}</p> // "1 product" (en) or "1 个产品" (zh-CN)
<p>{t('product.count', { count: 5 })}</p> // "5 products" (en) or "5 个产品" (zh-CN)
Default Values
Provide fallback text if a translation key doesn't exist:
const { t } = useTranslation();
<p>{t('product.unknownKey', { defaultValue: 'Fallback Text' })}</p>
Nested Keys
Use dot notation for nested translation keys:
// Translation structure
i18n.addResources('en', 'translation', {
'product.form.name': 'Product Name',
'product.form.price': 'Price',
'product.messages.createSuccess': 'Product created successfully',
});
// Usage
<Form.Item label={t('product.form.name')}>
<Input />
</Form.Item>
message.success(t('product.messages.createSuccess'));
Language Switching
Built-in Language Switcher
EZ-Console includes a built-in language switcher in the header that is automatically available when you use the EZApp component. Users can switch languages from the header dropdown.
// Automatically included in EZApp
import { EZApp } from 'ez-console';
<EZApp basePath='/' />
Programmatic Language Change
Change language programmatically using the i18n object:
import { useTranslation } from 'ez-console';
export const LanguageSelector: React.FC = () => {
const { i18n } = useTranslation();
const handleChange = (lang: string) => {
i18n.changeLanguage(lang);
};
return (
<Select
value={i18n.language}
onChange={handleChange}
>
<Select.Option value="en">English</Select.Option>
<Select.Option value="zh-CN">中文</Select.Option>
<Select.Option value="fr">Français</Select.Option>
</Select>
);
};
Getting Current Language
import { useTranslation } from 'ez-console';
export const MyComponent: React.FC = () => {
const { i18n } = useTranslation();
const currentLang = i18n.language; // e.g., 'en', 'zh-CN'
return <div>Current language: {currentLang}</div>;
};
Supported Languages
EZ-Console includes built-in translations for the following languages:
- English (en) - Default
- Chinese Simplified (zh-CN)
- French (fr)
- German (de)
- Spanish (es)
- Swedish (sv)
- Arabic (ar)
You can add support for additional languages by providing translation resources.
Translation Organization
Naming Convention
Use dot notation with a hierarchical structure:
module.component.action
module.component.field
module.component.message
Examples
product.list.title
product.form.name
product.form.submit
product.message.createSuccess
user.profile.title
user.profile.email
common.save
common.cancel
Common Translations
Keep common translations in a shared namespace for consistency:
// src/i18n/en/common.ts
export default {
'common.save': 'Save',
'common.cancel': 'Cancel',
'common.edit': 'Edit',
'common.delete': 'Delete',
'common.create': 'Create',
'common.update': 'Update',
'common.search': 'Search',
'common.reset': 'Reset',
'common.confirm': 'Confirm',
'common.submit': 'Submit',
'common.loading': 'Loading...',
'common.success': 'Success',
'common.error': 'Error',
'common.confirmDelete': 'Are you sure you want to delete?',
'common.confirmDeleteItem': 'Are you sure you want to delete {{item}}?',
'common.confirmDeleteMessage': 'This action cannot be undone.',
};
// src/i18n/zh-CN/common.ts
export default {
'common.save': '保存',
'common.cancel': '取消',
'common.edit': '编辑',
'common.delete': '删除',
'common.create': '创建',
'common.update': '更新',
'common.search': '搜索',
'common.reset': '重置',
'common.confirm': '确认',
'common.submit': '提交',
'common.loading': '加载中...',
'common.success': '成功',
'common.error': '错误',
'common.confirmDelete': '确定要删除吗?',
'common.confirmDeleteItem': '确定要删除 {{item}} 吗?',
'common.confirmDeleteMessage': '此操作无法撤销。',
};
Translation File Structure
Organize translation files by language and module:
src/i18n/
├── en/
│ ├── common.ts
│ ├── product.ts
│ ├── user.ts
│ └── index.ts
├── zh-CN/
│ ├── common.ts
│ ├── product.ts
│ ├── user.ts
│ └── index.ts
└── index.ts
// src/i18n/en/index.ts
import common from './common';
import product from './product';
import user from './user';
export default {
...common,
...product,
...user,
};
// src/App.tsx
import { i18n } from 'ez-console';
import enTranslations from './i18n/en';
import zhCNTranslations from './i18n/zh-CN';
i18n.addResources('en', 'translation', enTranslations);
i18n.addResources('zh-CN', 'translation', zhCNTranslations);
Backend i18n
Error Messages
Backend error messages should always be in English. The client can translate error codes if needed:
// ✅ Good: English error message
util.RespondWithError(ctx, util.NewErrorMessage("E4001", "Product name is required"))
// ❌ Bad: Non-English error message
util.RespondWithError(ctx, util.NewErrorMessage("E4001", "产品名称必填"))
Client-Side Error Translation
Map error codes to translation keys on the client:
// src/i18n/en/errors.ts
export default {
'errors.badRequest': 'Invalid request',
'errors.unauthorized': 'Unauthorized access',
'errors.forbidden': 'Access forbidden',
'errors.notFound': 'Resource not found',
'errors.serverError': 'Internal server error',
};
// src/utils/errorHandler.ts
import { useTranslation } from 'ez-console';
export const useErrorHandler = () => {
const { t } = useTranslation();
const getErrorMessage = (errorCode: string) => {
const errorMap: Record<string, string> = {
'E4001': t('errors.badRequest'),
'E4011': t('errors.unauthorized'),
'E4031': t('errors.forbidden'),
'E4041': t('errors.notFound'),
'E5001': t('errors.serverError'),
};
return errorMap[errorCode] || errorCode;
};
return { getErrorMessage };
};
Date and Number Formatting
Date Formatting
Use date-fns for locale-aware date formatting:
import { format } from 'date-fns';
import { enUS, zhCN, fr } from 'date-fns/locale';
const locales: Record<string, Locale> = {
'en': enUS,
'zh-CN': zhCN,
'fr': fr,
};
export const useDateFormatter = () => {
const { i18n } = useTranslation();
const formatDate = (date: Date, formatStr: string = 'PPP') => {
return format(date, formatStr, { locale: locales[i18n.language] });
};
return { formatDate };
};
Usage
const { formatDate } = useDateFormatter();
<p>{formatDate(new Date(), 'PPP')}</p>
// Output: "January 1, 2024" (en) or "2024年1月1日" (zh-CN)
Number Formatting
Use Intl.NumberFormat for locale-aware number formatting:
export const useNumberFormatter = () => {
const { i18n } = useTranslation();
const formatNumber = (num: number) => {
return new Intl.NumberFormat(i18n.language).format(num);
};
const formatCurrency = (amount: number, currency: string = 'USD') => {
return new Intl.NumberFormat(i18n.language, {
style: 'currency',
currency: currency,
}).format(amount);
};
return { formatNumber, formatCurrency };
};
Usage
const { formatNumber, formatCurrency } = useNumberFormatter();
<p>{formatNumber(1234567.89)}</p>
// Output: "1,234,567.89" (en) or "1,234,567.89" (zh-CN)
<p>{formatCurrency(99.99, 'USD')}</p>
// Output: "$99.99" (en) or "US$99.99" (zh-CN)
Best Practices
1. Use Translation Keys
Never hardcode user-facing text:
// ✅ Good
<Button>{t('common.save')}</Button>
// ❌ Bad
<Button>Save</Button>
2. Consistent Naming
Follow the naming convention consistently:
// ✅ Good
'product.form.name'
'product.form.price'
'product.list.title'
// ❌ Bad
'productName'
'priceOfProduct'
'listTitle'
3. Complete Translations
Provide translations for all supported languages:
// ✅ Good: All languages covered
i18n.addResources('en', 'translation', { 'product.name': 'Product' });
i18n.addResources('zh-CN', 'translation', { 'product.name': '产品' });
i18n.addResources('fr', 'translation', { 'product.name': 'Produit' });
// ❌ Bad: Missing translations
i18n.addResources('en', 'translation', { 'product.name': 'Product' });
// Missing zh-CN and fr
4. Context for Translators
Provide context comments in translation files:
// src/i18n/en/product.ts
export default {
// Product form labels
'product.form.name': 'Product Name', // Main product identifier
'product.form.price': 'Price', // Product price in USD
'product.form.stock': 'Stock Quantity', // Available inventory count
// Product actions
'product.actions.create': 'Create Product', // Button to create new product
'product.actions.edit': 'Edit Product', // Button to edit existing product
};
5. Parameters for Dynamic Content
Always use parameters for dynamic content:
// ✅ Good
t('product.itemsCount', { count: 10 })
// ❌ Bad
`${t('product.items')} ${count}`
6. Test All Languages
Test your application in all supported languages:
// Test helper
export const renderWithi18n = (component: ReactNode, options?: { language?: string }) => {
if (options?.language) {
i18n.changeLanguage(options.language);
}
return render(component);
};
// Test
test('renders English translation', () => {
const { getByText } = renderWithi18n(<ProductForm />, { language: 'en' });
expect(getByText('Product Name')).toBeInTheDocument();
});
test('renders Chinese translation', () => {
const { getByText } = renderWithi18n(<ProductForm />, { language: 'zh-CN' });
expect(getByText('产品名称')).toBeInTheDocument();
});
Troubleshooting
Translation Key Not Found
Issue: Translation key returns the key itself instead of the translated text.
Solution:
- Verify the translation key exists in your translation files
- Check that translations are loaded before the component renders
- Use
defaultValueas a fallback
// Use defaultValue as fallback
<p>{t('product.unknownKey', { defaultValue: 'Fallback Text' })}</p>
Language Not Switching
Issue: Language doesn't change when using the language switcher.
Solution:
- Ensure
i18n.changeLanguage()is called correctly - Check that translations exist for the target language
- Verify the language code matches (e.g.,
'zh-CN'not'zh')
Pluralization Not Working
Issue: Plural forms not displaying correctly.
Solution:
- For English, use
_oneand_othersuffixes - For languages without plurals, use a single key
// English (requires plural forms)
'product.count_one': '{{count}} product',
'product.count_other': '{{count}} products',
// Chinese (no plural form)
'product.count': '{{count}} 个产品',
Related Topics
- Built-in Components - Learn about EZ-Console components
- Forms & Validation - Create internationalized forms
- Application Shell - Configure the app structure
- API Integration - Handle API errors with i18n
Next Steps
- Learn about Built-in Components
- Explore Forms & Validation
- Review Performance Optimization
Need help? Ask in GitHub Discussions.