# 国际化（i18n）

> LocaleFormats 负责格式化；文本翻译需手动完成——框架在哪里帮得上忙，在哪里帮不上。

Source: https://crossfly.dev/zh/docs/best-practices/internacionalizacao — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 国际化（i18n）

分工很清晰：**格式化是自动的**（`LocaleFormats`），**文本翻译是手动的**（由你负责）。搞清楚各自的边界，能避免去找一个根本不存在的 API。

## 自动格式化——`LocaleFormats`
`LocaleFormats`（位于 `dev.crossfly.util`）集中管理对区域设置敏感的分隔符和格式，并被 `DatePicker`/`TimePicker`/货币掩码**自动**遵循：
| 字段 | 类型 | 控制 |
|---|---|---|
| `dateFormat` | `UIConstants.DateFormat` | 日期顺序（DMY/MDY/YMD） |
| `dateSeparator` | `char` | 日期分隔符（`/`、`-`、`.`） |
| `timeSeparator` | `char` | 时间分隔符（`:`） |
| `is24Hour` | `boolean` | 12 小时制 vs 24 小时制 |
| `decimalSeparator` | `char` | 小数点（`,` 或 `.`） |
| `thousandsSeparator` | `char` | 千位分隔符 |
| `weekStart` | `byte` | 每周的第一天 |

```java
LocaleFormats.decimalSeparator = ',';
LocaleFormats.thousandsSeparator = '.';
LocaleFormats.is24Hour = true;
// 从此处起，货币掩码和 pickers 已按 pt-BR 标准格式化
```

## 文本翻译——手动
**没有内置的字符串目录。** 将你的文本外部化（用你自己的 `Map`/枚举），并根据语言进行切换：
```java
enum Idioma { PT, EN }
static Idioma idioma = Idioma.PT;
static final Map<String, Map<Idioma,String>> T = Map.of(
   "salvar", Map.of(Idioma.PT, "Salvar", Idioma.EN, "Save"),
   "cancelar", Map.of(Idioma.PT, "Cancelar", Idioma.EN, "Cancel")
);
static String t(String chave) { return T.get(chave).get(idioma); }

// 用法：
new Button(t("salvar"));
```
月份/星期名称以及各选择器的标签都是**公共字段**，你可以重新赋值以实现本地化。

## 为什么布局已经帮了忙
基于**分数/`Relative`** 的布局能容忍不同语言之间长度不一的文本（"Salvar" vs "Enregistrer"）而不至于破版——这是不使用固定宽度带来的又一好处。

## 最佳实践
- 在引导启动时根据用户的区域设置**一次性**配置 `LocaleFormats`。
- 从一开始就将**所有**可见文本外部化（事后改造代价很高）。
- 测试那些"最长"的语言，以确保布局能够容纳。

## 常见错误
- 寻找内置的字符串系统/`R.string`——它并不存在；自己做一个。
- 在日期中硬编码 `/`，而不使用 `LocaleFormats.dateSeparator`。

## 另请参阅
- [DatePicker](/docs/ui/widgets/datepicker) · [掩码与键盘](/docs/ui/forms-and-input/masks-and-keyboards)
