# General Translation Knowledge Base: 如何在 .NET 应用中翻译 .resx 文件？
URL: https://generaltranslation.com/zh/kb/translate-resx-files.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 为每个中性的 .resx 文件创建一个以区域性命名的副本（例如 Resources.fr.resx），只翻译每个条目的值，名称、注释以及 {0} 等占位符均保持不变。.NET 会将每个区域性构建为一个附属程序集，并按 fr-CA → fr → 中性文件的顺序回退。General Translation 的 CLI 可根据中性的源文件生成这些区域性文件。
Category: 翻译
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

.resx 文件是一个由命名资源组成的 XML 列表。中性文件 (例如 `Resources.resx`) 存放默认语言的内容，每种译文则是与之同级、文件名中带有区域性标识的文件 (例如 `Resources.fr.resx`) ，其中包含相同的名称和翻译后的值。运行时，`ResourceManager` 会选取与当前 UI 区域性匹配的文件；如有缺失内容，则沿父区域性逐级回退查找。

## .resx 文件包含哪些内容

每个资源都是一个 `<data>` 元素。其 `name` 属性是代码用来查找的键，`<value>` 存放文本内容，可选的 `<comment>` 则向译者说明该文本的用途。以下是一段节选 (实际文件开头还有一个 schema 块和若干 `<resheader>` 条目，均由 Visual Studio 自动生成) ：

```xml title="Resources/SharedResource.resx"
<data name="Checkout" xml:space="preserve">
  <value>Checkout</value>
  <comment>Button that starts payment for the items in the cart.</comment>
</data>
<data name="ItemsInCart" xml:space="preserve">
  <value>You have {0} items in your cart.</value>
  <comment>{0} is the number of items.</comment>
</data>
```

法语文件中的 `name` 属性保持不变，只是值换成了法语，变化的仅有字符串值。名称是查找键；图像等非字符串资源带有 `type` 属性，通常还带有 `mimetype` 属性，一般保持原样。Microsoft 建议使用 Visual Studio 编辑 .resx 文件，而不要手动编辑，因为 XML 必须保持格式良好，并符合预定义的格式 ([Microsoft：创建资源文件](https://learn.microsoft.com/en-us/dotnet/core/extensions/create-resource-files)) 。

## .NET 如何找到正确的翻译

每个翻译文件都应与中性文件使用相同的根名称，并在扩展名前加上区域性名称：`Resources.fr.resx`、`Resources.fr-CA.resx`。构建时，中性资源会嵌入主程序集，而每种区域性则会编译为一个附属程序集，存放在以该区域性命名的文件夹中，例如 `fr/MyApp.resources.dll`。

当应用请求某个字符串时，.NET 会先在所请求区域性的附属程序集中查找，再依次查找其父区域性，最后使用中性资源。以 `fr-CA` 请求为例，查找顺序为 `fr-CA`、`fr`，最后是中性文件。如果连默认区域性的资源文件都不存在，则会抛出 `MissingManifestResourceException` 或 `MissingSatelliteAssemblyException` 异常。在 Linux 和 macOS 上，区域性文件夹名称必须与 `CultureInfo.Name` 完全一致，包括大小写 ([Microsoft: Package and deploy resources](https://learn.microsoft.com/en-us/dotnet/core/extensions/package-and-deploy-resources)) 。

借助回退机制，同一语言的多个地区只需翻译一次。Microsoft 建议为 `de-DE`、`de-AT` 和 `de-CH` 的用户提供单个 `de` 文件，区域文件中只保留有差异的字符串。但这也意味着缺失翻译永远不会抛出异常：用户看到的是中性语言，而构建过程不会给出任何提示。 (参见[什么是区域设置代码？](/kb/locale-codes)。)

## 在 ASP.NET Core、Windows Forms 和 WPF 中使用 .resx

### ASP.NET Core

注册本地化服务，选择要支持的区域性 (culture) ，然后添加中间件 ([Microsoft：选择语言和区域性](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/select-language-culture)) ：

```csharp title="Program.cs"
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    var supportedCultures = new[] { "en-US", "fr" };
    options.SetDefaultCulture(supportedCultures[0])
        .AddSupportedCultures(supportedCultures)
        .AddSupportedUICultures(supportedCultures);
});

var app = builder.Build();

// 依次从查询字符串、cookie 和 Accept-Language 中选取区域性。
app.UseRequestLocalization();
```

然后注入 `IStringLocalizer<T>`，按名称查找字符串，并在名称后传入格式化参数：`localizer["ItemsInCart", count]`。资源文件以类的完整类型名 (去掉程序集名称) 命名，因此 `Controllers.HomeController` 会读取 `Resources` 文件夹中的 `Controllers.HomeController.fr.resx` (或 `Resources/Controllers` 中的 `HomeController.fr.resx`) ；而一个空的 `SharedResource` 标记类则用于归集整个应用共享的字符串。`SupportedUICultures` 决定读取哪些 .resx 文件，`SupportedCultures` 决定日期和数字的格式化方式 ([Microsoft: Provide localized resources](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/provide-resources)) 。

找不到译文时，`IStringLocalizer` 会直接返回键本身，因此 Microsoft 文档介绍了一种完全不使用中性 .resx 的工作流：`localizer["About Title"]` 在英语环境下显示 &quot;About Title&quot; ([Microsoft: Make content localizable](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/make-content-localizable)) 。这种方式在显示上没有问题，但不会留下任何可供译者或工具读取的源文件。如果希望基于文件生成译文，请使用命名键，并搭配一个以英文文本为值的中性 .resx，这也是 Microsoft 文档中介绍的传统做法。

### Windows Forms 和 WPF

在 Windows Forms 中，将窗体的 **Localizable** 属性设为 `true`，然后在其 **Language** 属性中选择一种区域性；此后在设计器中修改文本时，Visual Studio 会将其写入对应的区域性文件，例如 `Form1.es-ES.resx` ([Microsoft：本地化混合应用程序](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/walkthrough-localizing-a-hybrid-application)) 。设计器生成的文件中还包含非文本条目，例如以 `>>` 开头的名称。WPF 应用可以通过 `ResourceManager` 加载 .resx 字符串；Microsoft 的 XAML 本地化演练使用了 LocBaml 工具，但官方称其只是一个示例，“并非可用于生产环境的应用程序” ([Microsoft：本地化 WPF 应用](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/how-to-localize-an-application)) 。

### 在 Visual Studio 中编辑

从 Visual Studio 2022 17.11 版本起，Resource Explorer 会将每个 .resx 文件的各个区域性并排显示。右键单击 .resx 文件，选择 **Open With &gt; Resource Explorer** (或按 **Ctrl+Alt+R**) 即可打开。如果缺少翻译 (显示“No value”) ，或占位符与中性区域性不一致 (例如 `Hello, {name}!` 与 `Hola, {nombre}!`) ，它会发出警告 ([Microsoft: Manage application resources](https://learn.microsoft.com/en-us/visualstudio/ide/managing-application-resources-dotnet)) 。

## 翻译 .resx 文件时容易出错的地方

* **占位符。** .NET 复合格式项的形式为 `{index[,width][:formatString]}`，例如 `{0}`、`{1,-10}` 或 `{0:N2}`。译文可以调整它们的顺序，也可以重复使用同一项；但如果格式项的索引超出了参数范围，就会在运行时抛出 `FormatException`，而漏掉某一项则会导致对应的值被静默丢弃。字面量花括号需写作 `{{` 和 `}}` ([Microsoft：复合格式设置](https://learn.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting)) 。
* **名称。** 如果 `name` 属性被翻译了，查找就会落空，该字符串将回退到中性语言。
* **XML。** 包含 `&` 或 `<` 的值必须保持 `&amp;` 和 `&lt;` 的转义形式，否则文件将无法编译。
* **访问键。** 在 Windows Forms 中，`&File` 会给字母 F 加下划线。译文中必须有且仅有一个 `&`，并放在合适的字母前。
* **注释。** 注释是提供给译者的上下文，而不是需要翻译的文本。Microsoft 的 Windows App SDK 指南指出，译者无需翻译注释 ([Microsoft：本地化字符串](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/mrtcore/localize-strings)) 。
* **不同步。** 开发者在中性语言文件中新增名称时，每个区域性文件也都要同步添加。回退机制会掩盖这类缺漏，因此每次发布前都要检查是否有缺失的值。

## 翻译 .resx 文件的几种方式

| 方式                           | 保留名称和非字符串资源               | 占位符检查                           | 保持各区域性同步                                                                      | 适用场景                   |
| ---------------------------- | ------------------------- | ------------------------------- | ----------------------------------------------------------------------------- | ---------------------- |
| 在 Visual Studio Resource Explorer中手动翻译 | 是                         | 不匹配时发出警告                        | 按区域性显示缺失的值                                                                    | 团队中有人掌握的一两种语言          |
| 译者或翻译管理系统 (TMS)              | 是，前提是 TMS 能解析 .resx       | 取决于其质量保证检查                      | 通过 TMS 的代码仓库同步                                                                | 由专业译者或供应商负责翻译工作        |
| 编写脚本调用机器翻译 API 或 LLM         | 仅当脚本解析 XML 并只发送值时         | 需自行编写                           | 重新运行并对比差异                                                                     | 你愿意自行维护脚本及其校验          |
| 将文件粘贴到聊天助手中                  | 无法保证                      | 无                               | 手动                                                                            | 一次性文件，且你会逐行检查          |
| General Translation CLI      | 仅翻译字符串值；名称、注释和非字符串资源均原样保留 | 若译文改动或遗漏了占位符，则不予采用，.NET 会为该条目回退 | 未更改的条目复用已有译文；每次发布时运行 [`gt translate`](/docs/cli/reference/commands/translate) | 资源文件存放在代码仓库中，且每次发布都会变动 |

如果只涉及一两种语言，且有团队成员能审校，可使用Resource Explorer。如果专业译者在代码库之外工作，可使用 [TMS](/kb/what-is-a-translation-management-system)。如果 .resx 文件每次发布都会变动，且翻译后的文件需要与代码一同提交，请选择 General Translation。 (参见 [General Translation 与 TMS 对比](/kb/general-translation-vs-tms)。)

## 如何使用 General Translation 翻译 .resx 文件

General Translation 的 CLI `gt` 可通过 `resx` 文件键翻译 .resx 文件，以及采用相同 schema 的 `.resw` 文件 (需要 `gt` 2.23.1 或更高版本) 。中性文件的文件名中不含区域性 (culture) ，因此请将 `include` 指向该文件，排除已翻译的同级文件，并使用 `transform` 在扩展名前插入区域性：

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["fr", "es-MX"],
  "files": {
    "resx": {
      "include": ["Resources/*.resx"],
      "exclude": ["Resources/*.[locales].resx"],
      "transform": {
        "match": "\\.resx$",
        "replace": ".{locale}.resx"
      }
    }
  }
}
```

在 `Resources` 文件夹中，此配置会读取 `Controllers.HomeController.resx`，并写入 `Controllers.HomeController.fr.resx` 和 `Controllers.HomeController.es-MX.resx`，同时保留 ASP.NET Core 的点分名称不变。请在 `locales` 中列出你已发布的所有区域性：`[locales]` 排除规则只会跳过已配置区域设置的翻译后的文件，因此不在列表中的 `Resources.de.resx` 会被当作源文件读取。对于按语言分文件夹存放 `.resw` 文件的 UWP 和 WinUI 项目，请使用 `"include": ["Strings/[locale]/Resources.resw"]`，且无需设置 `transform`。

即使在 .NET 代码仓库中，CLI 也需要在 Node.js 20 或更高版本上运行；请按照 [CLI 快速入门](/docs/cli/quickstart) 中的说明安装，使用 [`npx gt login`](/docs/cli/reference/commands/login) 登录，并设置 `GT_PROJECT_ID` (参见 [CLI 凭据](/docs/cli/guides/configuring#credentials)) 。如果应用已有译文，请先使用 [`npx gt upload`](/docs/cli/reference/commands/upload) 导入，然后运行 [`npx gt translate`](/docs/cli/reference/commands/translate)。在 CI 中，请将具有 **Files &gt; Write** 和 **Translation queue &gt; Enabled** 权限的项目 API 密钥存储为 `GT_API_KEY`。

翻译后的文件是源文件的副本，只替换了字符串值，因此名称、条目顺序、schema 和 `<resheader>` 块、注释以及非字符串资源均保持原样。带有 `mimetype`、类型不是 `System.String`、使用 `>>` 设计器名称或值为空的条目不会发送翻译。每个 `<comment>` 都会作为上下文发送，并且会遵循注释中的 Microsoft 本地化指令 (如 `{Locked}`) 。`{0}`、`{0:N2}`、`{1,-10}`、`{count}` 等占位符以及双大括号都会原样保留，单个 `&` 访问键会保留在合适的字母前面，下载的文件也会沿用源文件的编码和字节顺序标记。 (请参阅 [.resx 参考](/docs/cli/reference/formats/resx-files)。) 

你可以在[仪表板](/docs/platform/dashboard/guides/reviewing-translations)或 Visual Studio 中审校译文；在本地编辑文件后，请运行 [`npx gt save-local`](/docs/cli/reference/commands/save-local)，以便后续运行时保留你的修改。[上下文分组](/docs/platform/dashboard/guides/defining-context-for-translations)会将同一套词汇表和自定义提示词同时应用于你的 .NET 资源和 Web 应用的 [JSON 文件](/kb/translate-json-files)，确保产品名称在两处的译法保持一致。

## 常见问题

### .resx 和 .resw 有什么区别？

两者的 schema 相同。UWP 和 WinUI 应用将 `.resw` 文件按语言分别存放在不同的文件夹中，例如 `Strings/en-US/Resources.resw` 和 `Strings/de-DE/Resources.resw`，而不是在文件名中标注区域性 ([Microsoft：本地化字符串](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/mrtcore/localize-strings)) 。

### 如果已有 fr，还需要为 fr-CA 单独创建文件吗？

不需要。`fr-CA` 请求中缺失的名称都会回退到 `fr`，因此只需为有差异的字符串添加 `fr-CA` 文件即可。

### 为什么添加法语文件后，我的 ASP.NET Core 应用仍然显示英语？

请检查以下几点：`fr` 是否已添加到 `SupportedUICultures` 中；文件名是否与对应类的类型名称一致；如果在 Linux 或 macOS 上运行，构建产物中的区域性文件夹 (例如 `fr`) 是否与 `CultureInfo.Name` 完全一致 (包括大小写) 。

## 要点总结

* 每个译文都是一个同级的 .resx 文件，文件名中带有区域性名称，例如 `Resources.fr.resx`；只翻译值，切勿翻译 `name` 属性。
* .NET 会按 `fr-CA` → `fr` → 中性资源的顺序回退，因此缺少译文时会显示中性语言的内容，而不会报错。
* 如果格式项引用的索引超出了所提供参数的范围，就会抛出 `FormatException` 异常；请检查每个译文中 `{0}` 形式的占位符。
* Visual Studio 2022 17.11 的 Resource Explorer 会标记各区域性中缺失的值和不匹配的占位符。
* General Translation 的 CLI 通过 `resx` 配置键 (2.23.1+) 翻译 .resx 和 .resw 文件，会保留名称、注释和非字符串资源，并剔除任何破坏占位符的译文。

## Related questions

- [如何翻译 i18n JSON 文件？](https://generaltranslation.com/zh/kb/translate-json-files.mdx): 只翻译 JSON 区域设置文件中的字符串值。非复数键、{{name}} 之类的占位符以及标签都应保持不变，但复数键会因语言而异：例如波兰语需要 inbox_few 和 inbox_many。请使用能识别文件格式的工具或 API，然后先解析并验证输出，再进行审校。
- [如何翻译 PO 和 POT 文件？](https://generaltranslation.com/zh/kb/translate-po-files.mdx): 填写每一个 msgstr（包括 Plural-Forms 头部字段要求的每种复数形式），并保持 msgid、msgctxt 和占位符不变。运行 msgfmt --check 可检查头部信息、复数形式数量，以及带有 format 标志的条目中的占位符；此外还需单独检查是否有空的 msgstr。General Translation 的 CLI 会根据你的 POT 模板，为每种语言生成一个翻译好的 PO 文件。
- [什么是翻译管理系统（TMS）？](https://generaltranslation.com/zh/kb/what-is-a-translation-management-system.mdx): 翻译管理系统（TMS）是一种用于存储源内容及其译文、并运行翻译工作流的软件，涵盖翻译记忆库、词汇表、审校、机器翻译和集成等功能。其翻译记忆库通过精确匹配或模糊匹配复用以往的译文。常见的 TMS 有 Crowdin、Lokalise、Phrase、Smartling 和 Transifex。
- [General Translation 与 TMS（Crowdin、Lokalise、Phrase）对比](https://generaltranslation.com/zh/kb/general-translation-vs-tms.mdx): 如果开发者希望译文生成、源自代码的上下文以及审校流程都与产品发布紧密衔接，请选择 General Translation。你可以保留现有的消息目录和渲染库，也可以直接编写内联 JSX，无需翻译键。TMS 适合以语言供应商、CAT 编辑器和 XLIFF 交换为核心的翻译项目，其集成可以自动完成文件传输。
- [如何在每次部署时保持译文同步更新？](https://generaltranslation.com/zh/kb/translations-in-ci.mdx): 在每次生产环境构建之前，将翻译作为流水线中的一个步骤运行，这样新增文案就会在引入它的那次部署中以翻译好的形式一同上线。General Translation 的 gt CLI 通过 npx gt translate 实现这一点，所需的项目 API 密钥和项目 ID 以 CI 密钥的形式存储。

## Previous and next questions

- Previous question: [如何使用 strings.xml 本地化 Android 应用？](https://generaltranslation.com/zh/kb/localize-android-app.mdx)
- Next question: [如何在 AI 翻译中保持术语一致？](https://generaltranslation.com/zh/kb/ai-translation-glossary.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/zh/kb/sitemap.md) for every article.
