add en/vi language
This commit is contained in:
224
LOCALIZATION.md
Normal file
224
LOCALIZATION.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user