225 lines
6.6 KiB
Markdown
225 lines
6.6 KiB
Markdown
# Localization Setup Guide
|
|
|
|
## Overview
|
|
|
|
Ứng dụng đã được cấu hình hỗ trợ 2 ngôn ngữ: **English (en)** và **Tiếng Việt (vi)** sử dụng `expo-localization` và `i18n-js`.
|
|
|
|
## Cấu trúc thư mục
|
|
|
|
```
|
|
/config
|
|
/localization
|
|
- i18n.ts # Cấu hình i18n (khởi tạo locale, enable fallback, etc.)
|
|
- localization.ts # Export main exports
|
|
|
|
/locales
|
|
- en.json # Các string tiếng Anh
|
|
- vi.json # Các string tiếng Việt
|
|
|
|
/hooks
|
|
- use-i18n.ts # Hook để sử dụng i18n trong components
|
|
|
|
/state
|
|
- use-locale-store.ts # Zustand store cho global locale state management
|
|
```
|
|
|
|
## Cách sử dụng
|
|
|
|
### 1. Import i18n hook trong component
|
|
|
|
```tsx
|
|
import { useI18n } from "@/hooks/use-i18n";
|
|
|
|
export default function MyComponent() {
|
|
const { t, locale, setLocale } = useI18n();
|
|
|
|
return (
|
|
<View>
|
|
<Text>{t("common.ok")}</Text>
|
|
<Text>{t("navigation.home")}</Text>
|
|
<Text>Current locale: {locale}</Text>
|
|
</View>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 2. Thêm translation keys
|
|
|
|
#### Mở file `/locales/en.json` và thêm:
|
|
|
|
```json
|
|
{
|
|
"myFeature": {
|
|
"title": "My Feature Title",
|
|
"description": "My Feature Description"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Mở file `/locales/vi.json` và thêm translation tương ứng:
|
|
|
|
```json
|
|
{
|
|
"myFeature": {
|
|
"title": "Tiêu đề Tính năng Của Tôi",
|
|
"description": "Mô tả Tính năng Của Tôi"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Sử dụng trong component
|
|
|
|
```tsx
|
|
const { t } = useI18n();
|
|
|
|
return <Text>{t("myFeature.title")}</Text>;
|
|
```
|
|
|
|
## Cách thay đổi ngôn ngữ
|
|
|
|
```tsx
|
|
const { setLocale } = useI18n();
|
|
|
|
// Thay đổi sang Tiếng Việt
|
|
<Button onPress={() => setLocale('vi')} title="Tiếng Việt" />
|
|
|
|
// Thay đổi sang English
|
|
<Button onPress={() => setLocale('en')} title="English" />
|
|
```
|
|
|
|
**Lưu ý:** Ngôn ngữ được chọn sẽ được **lưu tự động vào storage**. Khi người dùng tắt app và mở lại, app sẽ sử dụng ngôn ngữ được chọn trước đó.
|
|
|
|
## Persistence (Lưu trữ tùy chọn ngôn ngữ) - Zustand Global State
|
|
|
|
Localization sử dụng **Zustand** cho global state management. Ngôn ngữ được chọn tự động được lưu vào `AsyncStorage` với key `app_locale_preference`.
|
|
|
|
**Quy trình:**
|
|
|
|
1. Khi app khởi động, `useLocaleStore.getState().initLocale()` được gọi trong `app/_layout.tsx`
|
|
2. Store sẽ load locale từ storage nếu có
|
|
3. Nếu không có, store sẽ detect ngôn ngữ thiết bị (`getLocales()`)
|
|
4. Khi người dùng gọi `setLocale('vi')`, nó sẽ:
|
|
- Cập nhật Zustand store ngay lập tức
|
|
- **Tự động lưu vào storage** để dùng lần tiếp theo
|
|
- Tất cả components lắng nghe sẽ re-render ngay lập tức
|
|
|
|
**Kết quả:** Khi bạn toggle switch language trong settings:
|
|
|
|
- ✅ Tab labels cập nhật ngay lập tức
|
|
- ✅ UI labels cập nhật ngay lập tức
|
|
- ✅ Không cần click vào tab hoặc navigate
|
|
- ✅ Locale được persist vào storage
|
|
|
|
### Zustand Store Structure
|
|
|
|
```tsx
|
|
// /state/use-locale-store.ts
|
|
const { locale, setLocale, isLoaded } = useLocaleStore((state) => ({
|
|
locale: state.locale, // Locale hiện tại
|
|
setLocale: state.setLocale, // Async function để thay đổi locale
|
|
isLoaded: state.isLoaded, // Flag để biết khi locale đã load từ storage
|
|
}));
|
|
```
|
|
|
|
## Fallback Mechanism
|
|
|
|
Nếu một key không tồn tại trong ngôn ngữ hiện tại, ứng dụng sẽ tự động sử dụng giá trị từ ngôn ngữ English (default locale).
|
|
|
|
### Ví dụ:
|
|
|
|
- Nếu key `auth.newFeature` chỉ tồn tại trong `en.json` mà không có trong `vi.json`
|
|
- Khi ngôn ngữ được set là Vietnamese (`vi`), nó sẽ hiển thị giá trị từ English
|
|
|
|
## Persistence (Lưu trữ tùy chọn ngôn ngữ)
|
|
|
|
Ngôn ngữ được chọn tự động được lưu vào `AsyncStorage` với key `app_locale_preference`.
|
|
|
|
**Quy trình:**
|
|
|
|
1. Khi app khởi động, hook `useI18n` sẽ load giá trị từ storage
|
|
2. Nếu có giá trị lưu trữ, app sẽ sử dụng ngôn ngữ đó
|
|
3. Nếu không có, app sẽ detect ngôn ngữ thiết bị (`getLocales()`)
|
|
4. Khi người dùng gọi `setLocale('vi')`, nó sẽ:
|
|
- Thay đổi ngôn ngữ hiện tại
|
|
- **Tự động lưu vào storage** để dùng lần tiếp theo
|
|
|
|
**Kết quả:** Người dùng có thể tắt app, mở lại, và app sẽ vẫn sử dụng ngôn ngữ đã chọn trước đó.
|
|
|
|
## Supported Locales
|
|
|
|
Hiện tại app hỗ trợ:
|
|
|
|
- **en** - English
|
|
- **vi** - Tiếng Việt
|
|
|
|
Nếu muốn thêm ngôn ngữ khác:
|
|
|
|
1. Tạo file `locales/[language-code].json` (ví dụ: `locales/ja.json`)
|
|
2. Thêm translations
|
|
3. Import trong `config/localization/i18n.ts`:
|
|
|
|
```ts
|
|
import ja from "@/locales/ja.json";
|
|
|
|
const translations = {
|
|
en,
|
|
vi,
|
|
ja,
|
|
};
|
|
```
|
|
|
|
4. Cập nhật `app.json` để thêm locale mới:
|
|
```json
|
|
"supportedLocales": {
|
|
"ios": ["en", "vi", "ja"],
|
|
"android": ["en", "vi", "ja"]
|
|
}
|
|
```
|
|
|
|
## Translation Keys Hiện Có
|
|
|
|
Xem file `locales/en.json` và `locales/vi.json` để xem danh sách tất cả keys có sẵn.
|
|
|
|
## Best Practices
|
|
|
|
1. **Luôn add key vào cả 2 file** `en.json` và `vi.json` cùng lúc
|
|
2. **Giữ cấu trúc JSON nhất quán** giữa các language files
|
|
3. **Sử dụng snake_case cho keys** (không sử dụng camelCase)
|
|
4. **Nhóm liên quan keys** vào categories (ví dụ: `common`, `navigation`, `auth`, etc.)
|
|
5. **Để fallback enable** để tránh lỗi nếu key bị thiếu
|
|
|
|
## Device Language Detection
|
|
|
|
Ngôn ngữ của thiết bị sẽ được tự động detect khi app khởi động. Nếu thiết bị set language là Tiếng Việt, app sẽ tự động sử dụng `vi` locale.
|
|
|
|
Device language được lấy từ:
|
|
|
|
```tsx
|
|
import { getLocales } from "expo-localization";
|
|
|
|
const deviceLanguage = getLocales()[0].languageCode; // 'en', 'vi', etc.
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### App không detect đúng ngôn ngữ thiết bị
|
|
|
|
- Kiểm tra console log trong `config/localization/i18n.ts`
|
|
- Đảm bảo language code của thiết bị khớp với các supported locales
|
|
|
|
### Translation key không hiện
|
|
|
|
- Kiểm tra xem key có tồn tại trong cả 2 files `en.json` và `vi.json` không
|
|
- Kiểm tra spelling của key (case-sensitive)
|
|
- Kiểm tra syntax JSON có hợp lệ không
|
|
|
|
### Fallback không hoạt động
|
|
|
|
- Đảm bảo `i18n.enableFallback = true` trong `config/localization/i18n.ts`
|
|
- Kiểm tra key có tồn tại trong `en.json` không (default fallback language)
|
|
|
|
## References
|
|
|
|
- [Expo Localization Guide](https://docs.expo.dev/guides/localization/)
|
|
- [i18n-js Documentation](https://github.com/fnando/i18n-js)
|