Resume-Matcher 国际化(i18n)指南:UI 与内容双语言体系、翻译接入与多语言新增实践
2026/9/10 20:15:15 网站建设 项目流程

Resume-Matcher 国际化(i18n)指南:UI 与内容双语言体系、翻译接入与多语言新增实践

【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters & more, locally with 100+ LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher

本篇技术指南以 docs/agent/features/i18n.md 为核心骨架,结合 docs/agent/features/i18n-preparation.md 的落地计划,深入解析 Resume-Matcher 的多语言体系。你将掌握:项目支持哪些语言、UI 语言与内容语言两套设置如何独立工作、翻译消息如何在前端加载与替换、{output_language}如何贯穿到后端 LLM 提示词,以及如何在本地新增一种语言并完成前后端配置。文中所有结论均可回到仓库源码逐一验证,可直接作为开发者的上手与扩展手册。

一、项目概况与 i18n 目标

Resume-Matcher 是一个本地运行的 AI 简历工具,支持简历(Resume)、PDF、求职信(Cover Letter)等内容生成,并可对接 100+ 本地或云端 LLM。多语言能力是其面向全球用户的基础设施,具体表现为两层目标:

  1. UI 国际化:界面文本(按钮、标签、导航)随语言切换;
  2. 内容国际化:由 LLM 生成的简历、求职信、外联消息、面试准备材料等,按指定语言输出。

仓库在 docs/agent/features/i18n.md 中明确了两套语言设置可在设置页独立配置,互不干扰;docs/agent/features/i18n-preparation.md 则记录了从单语言演进到多语言的具体准备步骤,包括消息文件布局、翻译键结构与后端未来扩展方向。本文按"语言支持 → 双语言架构 → 前端实现 → 后端实现 → 存储 → 新增语言 → 质量保障"的顺序展开。

二、支持的语言与权威配置源

2.1 支持语言一览

CodeLanguageNative NameFlagMessage File
enEnglishEnglish🇺🇸messages/en.json
esSpanishEspañol🇪🇸messages/es.json
zhChinese (Simplified)中文🇨🇳messages/zh.json
jaJapanese日本語🇯🇵messages/ja.json
ptPortuguese (Brazilian)Português🇧🇷messages/pt-BR.json
frFrenchFrançais🇫🇷messages/fr.json

对应消息文件均位于 apps/frontend/messages/ 目录下。注意两点细节:

  • 语言码与文件名不一致:葡萄牙语的语言码是pt,但消息文件名为pt-BR.json(巴西葡萄牙语);
  • 默认语言为en,任何未匹配的语言都会回退到英文。

2.2 权威配置源:i18n/config.ts

语言清单的单一事实来源是 apps/frontend/i18n/config.ts,其中定义了:

export const locales = ['en', 'es', 'zh', 'ja', 'pt', 'fr'] as const; export type Locale = (typeof locales)[number]; export const defaultLocale: Locale = 'en'; export const localeNames: Record<Locale, string> = { en: 'English', es: 'Español', zh: '中文', ja: '日本語', pt: 'Português', fr: 'Français', }; export const localeFlags: Record<Locale, string> = { en: '🇺🇸', es: '🇪🇸', zh: '🇨🇳', ja: '🇯🇵', pt: '🇧🇷', fr: '🇫🇷', };

从源码结构看,locales数组同时驱动了Locale类型推导与localeNames/localeFlags两个映射表的完整性约束:新增语言时,三处必须同步修改,否则 TypeScript 类型检查会直接报错——这正是该文件被称为"source of truth"的原因。

三、双语言体系:UI Language 与 Content Language

3.1 两者的区别

维度UI LanguageContent Language
作用对象界面文本(按钮、标签、导航)LLM 生成内容(简历、求职信等)
前端控制useTranslations钩子LanguageProvider上下文
后端参与否(纯前端)是(写入后端配置并注入提示词)
存储位置localStoragelocalStorage + 后端配置

二者在 apps/frontend/app/(default)/settings/page.tsx/settings/page.tsx) 的设置页中以两个独立的分段选择器(segmented control)呈现,分别调用setUiLanguage(lang)setContentLanguage(lang)(见 设置页 L1275-L1322/settings/page.tsx#L1275-L1322))。

3.2 LanguageProvider:双语言状态的核心上下文

apps/frontend/lib/context/language-context.tsx 中的LanguageProvider是前端语言状态的统一入口,其加载流程如下:

  1. 从 localStorage 读取resume_matcher_ui_languageresume_matcher_content_language(若值在locales内则直接采用);
  2. 调用fetchLanguageConfig()从后端拉取已保存的content_language并覆盖本地缓存,确保前后端同步;
  3. 加载完成后将isLoading置为false

setContentLanguage采用乐观更新 + 失败回滚策略:先更新本地 state 与 localStorage,再调用updateLanguageConfig({ content_language: lang })持久化到后端;若请求失败,则回滚到上一个语言并打印错误日志(language-context.tsx L64-L87)。setUiLanguage则只写 localStorage,不涉及后端。

任何消费该上下文的组件都必须位于LanguageProvider之内,否则useLanguage()会抛出useLanguage must be used within a LanguageProvider错误。

四、前端翻译实现:无依赖的 JSON 加载方案

4.1 设计选型

与许多项目引入next-intl不同,Resume-Matcher 当前的 UI 翻译采用了最简方案:消息以 JSON 静态导入,按当前 UI 语言选择对应文件,无任何外部依赖。这一设计决策写在该项目的 i18n 准备文档中,也在 apps/frontend/lib/i18n/index.ts 的文件头注释里明确说明。

4.2 消息加载:messages.ts

apps/frontend/lib/i18n/messages.ts 负责把语言码映射到具体的 JSON 文件:

import en from '@/messages/en.json'; import es from '@/messages/es.json'; import zh from '@/messages/zh.json'; import ja from '@/messages/ja.json'; import pt from '@/messages/pt-BR.json'; // 注意:pt 加载 pt-BR.json import fr from '@/messages/fr.json'; export type Messages = typeof en; // 以 en 为类型基准 const allMessages: Record<Locale, Messages> = { en, es, zh, ja, pt, fr }; export function getMessages(locale: Locale): Messages { return allMessages[locale] || allMessages.en; // 未匹配时回退 en }

type Messages = typeof en是关键约束:所有语言文件的 JSON 结构必须与en.json完全一致,否则tsc/next build会编译失败(这一约束同时被独立的校验脚本利用,见第七节)。

4.3 翻译键结构

翻译消息以嵌套 JSON 组织,顶层按功能模块分区。以 apps/frontend/messages/en.json 为参考,典型结构如下:

{ "dashboard": { "title": "Dashboard", "masterResume": "Master Resume" }, "builder": { "save": "Save", "download": "Download PDF" }, "common": { "save": "Save" } }

键通过点号(.)定位嵌套层级,例如t('common.save')

4.4 翻译查询与参数替换:translations.ts + utils.ts

apps/frontend/lib/i18n/translations.ts 提供useTranslations客户端钩子:

import { useTranslations } from '@/lib/i18n'; const { t } = useTranslations(); <button>{t('common.save')}</button>

其底层依赖两个纯函数(apps/frontend/lib/i18n/utils.ts):

  • getNestedValue(obj, path):按点号路径遍历嵌套对象;任何一步缺失都返回原始 path 字符串(便于发现问题),而不是抛异常;
  • applyParams(value, params):以{param}形式替换占位符,例如t('greeting', { name: 'Ada' })会把"Hello, {name}"渲染为"Hello, Ada"

此外,该文件还导出了面向服务端组件的getMessagestranslate(locale, key, params?),便于在 Server Component 中直接按指定 locale 取翻译。

五、后端内容生成:{output_language}贯穿 LLM 提示词

5.1 语言码 → 全称的映射

LLM 提示词中使用的不是语言码,而是语言全称。映射表定义在 apps/backend/app/prompts/templates.py 顶部:

LANGUAGE_NAMES = { "en": "English", "es": "Spanish", "zh": "Chinese (Simplified)", "ja": "Japanese", "pt": "Brazilian Portuguese", "fr": "French", } def get_language_name(code: str) -> str: """Get full language name from code.""" return LANGUAGE_NAMES.get(code, "English")

注意get_language_name的兜底逻辑:未识别的语言码一律返回"English",因此后端在接收入参时务必先做语言码校验(见 5.3)。

5.2 提示词中的注入方式

所有生成类提示词模板都通过{output_language}占位符接收目标语言,例如:

  • 简历改进三档提示词(IMPROVE_RESUME_PROMPT_NUDGE/_KEYWORDS/_FULL):IMPORTANT: Generate ALL text content (summary, descriptions, skills) in {output_language}.
  • 求职信提示词(COVER_LETTER_PROMPT):IMPORTANT: Write in {output_language}.
  • 外联消息提示词(OUTREACH_MESSAGE_PROMPT):IMPORTANT: Write in {output_language}.
  • 面试准备提示词(INTERVIEW_PREP_PROMPT):IMPORTANT: Write in {output_language}.且额外要求"不翻译 JSON 属性名,只翻译字符串值"
  • 标题生成提示词(GENERATE_TITLE_PROMPT):IMPORTANT: Write in {output_language}.
  • 技能目标计划提示词(SKILL_TARGET_PLAN_PROMPT):6. Generate reasons in {output_language}.
  • 差异式改进提示词(DIFF_IMPROVE_PROMPT):7. Generate all new text in {output_language}

注意{output_language}是自定义提示词的三个必需占位符之一。后端在 apps/backend/app/routers/config.py 的update_feature_prompts端点中通过validate_prompt_placeholders校验{job_description}{resume_data}{output_language}三者是否齐全,缺失任一占位符都会返回 422 及结构化错误详情(config.py L377-L429)。这意味着即使你完全自定义求职信/外联消息提示词,也必须保留{output_language}以支持多语言内容生成。

5.3 服务层的语言解析调用链

内容语言从"配置"到"提示词"的传递路径,可从源码清晰还原:

  1. 前端setContentLanguagePUT /config/language(apps/frontend/lib/api/config.ts 的updateLanguageConfig);
  2. 后端路由 apps/backend/app/routers/config.py 的update_language_config校验语言码是否在SUPPORTED_LANGUAGES内,随后写入配置文件(支持从旧的单一language字段自动迁移到ui_language+content_language双字段);
  3. 各服务调用时先取语言码,再经get_language_name(code)转为全称注入提示词。例如:
    • apps/backend/app/services/cover_letter.py L51/L105/L152 均先output_language = get_language_name(language)再格式化提示词;
    • apps/backend/app/services/improver.py L533/L846/L936 在简历改进(nudge/keywords/full)时做同样转换;
    • apps/backend/app/services/interview_prep.py L113 使用get_language_name(language)
    • apps/backend/app/services/resume_wizard.py L355 通过get_content_language()从配置缓存读取内容语言再转换;
    • apps/backend/app/routers/enrichment.py 的多个技能/内容增强端点同样遵循该模式(L110/L216/L289/L503)。

重要事实:后端SUPPORTED_LANGUAGES = ["en", "es", "zh", "ja", "pt", "fr"](config.py L256)与前端locales完全一致,这是新增语言时前后端必须同步维护的两处清单。

5.4 已存内容不翻译

i18n 文档明确约定:数据库中的既有内容保持原始语言,不进行自动翻译。内容语言只影响新生成的文本。这一约束避免了对用户既有简历/求职信数据的破坏性改写,也意味着切换语言后,历史记录仍以当时生成的语言展示。

六、存储键位一览

Key用途存储位置
resume_matcher_ui_languageUI 语言仅 localStorage
resume_matcher_content_language内容语言localStorage + 后端配置

两个键名常量定义于 apps/frontend/lib/context/language-context.tsx L11-L12(UI_STORAGE_KEY/CONTENT_STORAGE_KEY)。后端侧,语言配置持久化在应用配置文件中(ui_language/content_language字段),并通过 apps/backend/app/config_cache.py 的缓存机制加速读取;resume_wizard.py正是通过get_content_language()读取该缓存。

七、新增一种语言的完整步骤

综合 docs/agent/features/i18n.md 与 docs/agent/features/i18n-preparation.md 两份文档,并对照当前仓库实际实现(语言码de德语仅作为示例),完整步骤如下:

步骤 1:创建翻译消息文件

apps/frontend/messages/下新建{code}.json,结构与en.json完全一致。若语言码与文件名不一致(如ptpt-BR.json),按实际文件命名,并在第 3 步的 import 中指定正确路径。

步骤 2:注册到前端语言清单

编辑 apps/frontend/i18n/config.ts,把新语言码加入locales数组,并同步补全localeNameslocaleFlags

export const locales = ['en', 'es', 'zh', 'ja', 'pt', 'fr', 'de'] as const; // localeNames 增加: de: 'Deutsch' // localeFlags 增加: de: '🇩🇪'

步骤 3:注册消息导入

编辑 apps/frontend/lib/i18n/messages.ts,新增import de from '@/messages/de.json';并加入allMessages映射。注意:由于type Messages = typeof en,此处若 JSON 键缺失或类型形状不一致,TypeScript 编译将直接失败——这是"结构一致"约束在类型层面的体现。

步骤 4:注册后端语言全称

编辑 apps/backend/app/prompts/templates.py 的LANGUAGE_NAMES字典,为语言码补充全称(如"de": "German"),否则get_language_name会回退到"English",LLM 将以英文生成内容。

步骤 5:同步后端受支持语言清单

编辑 apps/backend/app/routers/config.py L256 处的SUPPORTED_LANGUAGES,加入新语言码。若不更新,PUT /config/language会因校验失败返回 400Unsupported content language: de. Supported: [...]

步骤 6(可选):运行校验脚本

仓库提供了 scripts/check_locale_parity.py,用纯 Python 标准库(不依赖 Node/npm)校验每个语言文件与en.json的结构一致性:缺失键、叶子节点与对象形状不一致、非法 JSON 都会导致退出码 1;多余键仅作为警告输出。该脚本面向本地 pre-push 钩子场景,可在提交前快速发现问题。

八、质量保障:locale 一致性校验与测试

前端测试目录中与 i18n 相关的用例包括:

  • apps/frontend/tests/i18n-locale-parity.test.ts:验证各语言文件与en.json的键结构一致性;
  • apps/frontend/tests/i18n-utils.test.ts:覆盖getNestedValueapplyParams的边界行为;
  • apps/frontend/tests/i18n-server.test.ts:验证服务端translate能力。

后端侧,apps/backend/tests/unit/test_check_locale_parity.py 与 apps/backend/tests/unit/test_prompt_guardrails.py 分别守护校验脚本逻辑与提示词占位符约束。若你修改了消息文件,务必运行npm test(前端 vitest)与对应 Python 测试,确保typeof en类型约束与占位符完整性不被破坏。

九、实践要点与 FAQ

Q1:UI 语言切换后界面不变?UI 语言完全由前端控制,检查localStorage.getItem('resume_matcher_ui_language')是否更新,并确认组件位于LanguageProvider内、使用了useTranslations()

Q2:内容语言切换后,生成的简历仍是英文?优先检查三处:后端SUPPORTED_LANGUAGES是否包含该语言码(config.py L256);LANGUAGE_NAMES是否映射了全称(templates.py L4-L11);请求是否真的把content_language传到了生成端点(get_language_name的兜底值是"English")。

Q3:自定义求职信提示词报 422?自定义提示词必须包含{job_description}{resume_data}{output_language}三个占位符(config.py L384),后端会精确返回缺失项。

Q4:既有数据会随语言切换被翻译吗?不会。内容语言只影响后续新生成的文本,数据库中的存量内容保持原语言。

Q5:新增语言最容易漏掉哪一步?最容易漏的是后端SUPPORTED_LANGUAGESLANGUAGE_NAMES两处——它们不参与前端 TypeScript 类型检查,漏掉不会编译报错,但会静默导致内容生成回退到英文。

十、小结

Resume-Matcher 的 i18n 设计遵循"前端 UI 与后端内容解耦"的原则:前端以locales配置为权威、用无依赖的 JSON 静态导入渲染界面;后端以SUPPORTED_LANGUAGES为边界、用{output_language}占位符驱动所有 LLM 提示词;两端通过resume_matcher_content_language/config/language接口保持同步。新增语言时,五处注册点(消息文件、config.tsmessages.tsLANGUAGE_NAMESSUPPORTED_LANGUAGES)缺一不可,而check_locale_parity.py与相关测试则为此提供了自动化保障。后续若需要更深度的本地化(如日期格式、复数规则),可参考 docs/agent/features/i18n-preparation.md 中关于后端 i18n 的扩展规划,在现有{output_language}机制上继续演进。

【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters & more, locally with 100+ LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询