Resume-Matcher 国际化(i18n)指南:UI 与内容双语言体系、翻译接入与多语言新增实践
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。多语言能力是其面向全球用户的基础设施具体表现为两层目标UI 国际化界面文本按钮、标签、导航随语言切换内容国际化由 LLM 生成的简历、求职信、外联消息、面试准备材料等按指定语言输出。仓库在 docs/agent/features/i18n.md 中明确了两套语言设置可在设置页独立配置互不干扰docs/agent/features/i18n-preparation.md 则记录了从单语言演进到多语言的具体准备步骤包括消息文件布局、翻译键结构与后端未来扩展方向。本文按语言支持 → 双语言架构 → 前端实现 → 后端实现 → 存储 → 新增语言 → 质量保障的顺序展开。二、支持的语言与权威配置源2.1 支持语言一览CodeLanguageNative NameFlagMessage FileenEnglishEnglishmessages/en.jsonesSpanishEspañolmessages/es.jsonzhChinese (Simplified)中文messages/zh.jsonjaJapanese日本語messages/ja.jsonptPortuguese (Brazilian)Portuguêsmessages/pt-BR.jsonfrFrenchFrançaismessages/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: RecordLocale, string { en: English, es: Español, zh: 中文, ja: 日本語, pt: Português, fr: Français, }; export const localeFlags: RecordLocale, string { en: , es: , zh: , ja: , pt: , fr: , };从源码结构看locales数组同时驱动了Locale类型推导与localeNames/localeFlags两个映射表的完整性约束新增语言时三处必须同步修改否则 TypeScript 类型检查会直接报错——这正是该文件被称为source of truth的原因。三、双语言体系UI Language 与 Content Language3.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是前端语言状态的统一入口其加载流程如下从 localStorage 读取resume_matcher_ui_language与resume_matcher_content_language若值在locales内则直接采用调用fetchLanguageConfig()从后端拉取已保存的content_language并覆盖本地缓存确保前后端同步加载完成后将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.tsapps/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: RecordLocale, 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.tsapps/frontend/lib/i18n/translations.ts 提供useTranslations客户端钩子import { useTranslations } from /lib/i18n; const { t } useTranslations(); button{t(common.save)}/button其底层依赖两个纯函数apps/frontend/lib/i18n/utils.tsgetNestedValue(obj, path)按点号路径遍历嵌套对象任何一步缺失都返回原始 path 字符串便于发现问题而不是抛异常applyParams(value, params)以{param}形式替换占位符例如t(greeting, { name: Ada })会把Hello, {name}渲染为Hello, Ada。此外该文件还导出了面向服务端组件的getMessages与translate(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/_FULLIMPORTANT: Generate ALL text content (summary, descriptions, skills) in {output_language}.求职信提示词COVER_LETTER_PROMPTIMPORTANT: Write in {output_language}.外联消息提示词OUTREACH_MESSAGE_PROMPTIMPORTANT: Write in {output_language}.面试准备提示词INTERVIEW_PREP_PROMPTIMPORTANT: Write in {output_language}.且额外要求不翻译 JSON 属性名只翻译字符串值标题生成提示词GENERATE_TITLE_PROMPTIMPORTANT: Write in {output_language}.技能目标计划提示词SKILL_TARGET_PLAN_PROMPT6. Generate reasons in {output_language}.差异式改进提示词DIFF_IMPROVE_PROMPT7. 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 服务层的语言解析调用链内容语言从配置到提示词的传递路径可从源码清晰还原前端setContentLanguage→PUT /config/languageapps/frontend/lib/api/config.ts 的updateLanguageConfig后端路由 apps/backend/app/routers/config.py 的update_language_config校验语言码是否在SUPPORTED_LANGUAGES内随后写入配置文件支持从旧的单一language字段自动迁移到ui_languagecontent_language双字段各服务调用时先取语言码再经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 语言仅 localStorageresume_matcher_content_language内容语言localStorage 后端配置两个键名常量定义于 apps/frontend/lib/context/language-context.tsx L11-L12UI_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完全一致。若语言码与文件名不一致如pt→pt-BR.json按实际文件命名并在第 3 步的 import 中指定正确路径。步骤 2注册到前端语言清单编辑 apps/frontend/i18n/config.ts把新语言码加入locales数组并同步补全localeNames与localeFlagsexport 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会回退到EnglishLLM 将以英文生成内容。步骤 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覆盖getNestedValue与applyParams的边界行为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类型约束与占位符完整性不被破坏。九、实践要点与 FAQQ1UI 语言切换后界面不变UI 语言完全由前端控制检查localStorage.getItem(resume_matcher_ui_language)是否更新并确认组件位于LanguageProvider内、使用了useTranslations()。Q2内容语言切换后生成的简历仍是英文优先检查三处后端SUPPORTED_LANGUAGES是否包含该语言码config.py L256LANGUAGE_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_LANGUAGES与LANGUAGE_NAMES两处——它们不参与前端 TypeScript 类型检查漏掉不会编译报错但会静默导致内容生成回退到英文。十、小结Resume-Matcher 的 i18n 设计遵循前端 UI 与后端内容解耦的原则前端以locales配置为权威、用无依赖的 JSON 静态导入渲染界面后端以SUPPORTED_LANGUAGES为边界、用{output_language}占位符驱动所有 LLM 提示词两端通过resume_matcher_content_language与/config/language接口保持同步。新增语言时五处注册点消息文件、config.ts、messages.ts、LANGUAGE_NAMES、SUPPORTED_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),仅供参考