ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

GraphHopper 路线转向提示多语言翻译机制与本地化贡献指南

GraphHopper 路线转向提示多语言翻译机制与本地化贡献指南 GraphHopper 路线转向提示多语言翻译机制与本地化贡献指南【免费下载链接】graphhopperOpen source routing engine for OpenStreetMap. Use it as Java library or standalone web server.项目地址: https://gitcode.com/GitHub_Trending/gr/graphhopper导读GraphHopper 是面向 OpenStreetMap 的开源路由引擎其服务器端会为每次导航请求生成向左转进入环岛并驶出第 N 个出口等转向提示turn instructions。为了让全球用户以母语接收这些指令GraphHopper 内置了一套由 Google Sheets 驱动的多语言翻译工作流翻译条目集中托管在电子表格中通过脚本自动导出为各语言资源文件由 TranslationMap 在运行时加载并提供带占位符参数、带英语回退的翻译查询。本文以 docs/core/translations.md 为骨架结合源码与脚本完整讲解这套机制的原理、参与流程与工程细节帮助你为 GraphHopper 新增或修正一种语言。一、翻译系统的整体架构GraphHopper 的翻译体系分为两层服务器端核心路由引擎只翻译转向指令turn instructions即由路由算法产出的转弯/环岛/上桥/上轮渡等每一步操作描述。客户端GraphHopper Maps 前端除转向指令外的其余界面文案按钮、标签、设置项等均由前端项目维护走独立的翻译渠道见下文客户端翻译小节。整个服务端翻译链路可概括为Google Sheets翻译总表 │ curl 导出 TSV ▼ core/files/update-translations.sh 脚本 │ cut 按列拆分 ▼ core/src/main/resources/com/graphhopper/util/locale.txt 资源文件 │ TranslationMap.doImport() 类路径加载 ▼ TranslationMap 内存映射locale → Translation │ getWithFallBack() / get() ▼ Router 组装 ResponsePath 时按请求 locale 取出译文 │ tr(key, params) → String.format ▼ 导航转向指令文本如 at roundabout, take exit 2关键源码入口翻译管理器TranslationMap.java路由服务中的调用点Router.javatranslationMap.getWithFallBack(request.getLocale())请求端的语言参数GHRequest.java 的setLocale(Locale)/setLocale(String)。二、locale 语言代码ISO 639-1 双字母码翻译表中每个语言列的名称如 Spanish: es中冒号后的字符串就是该语言的ISO 639-1 双字符代码例如语言代码German / 德语deEnglish / 英语enSimplified Chinese / 简体中文zhFrench / 法语frSpanish / 西班牙语esItalian / 意大利语itRussian / 俄语ruJapanese / 日语ja如果你不确定自己语言的代码可在维基百科查询该语言词条中给出的 ISO 639-1 代码。实际资源文件中locale 往往带国家和地区后缀如de_DE、zh_CN这是为了支持方言变体但在 URL 查询参数与前端展示中通常会退化为双字母形式。三、%1$s占位符为什么绝不能被省略翻译条目中常出现%1$s、%2$s这类字符它们是JavaString.format位置参数占位符%1$s表示第 1 个字符串参数%2$s表示第 2 个字符串参数由 GraphHopper 在运行时填入路名、出口编号等动态值。保留占位符至关重要原因有两点语序差异不同语言中参数出现的位置可能完全不同。例如英语是Enter roundabout and use exit %1$s参数在句尾而德语必须把出口编号参数放在句中并搭配动词In den Kreisverkehr einfahren und Ausfahrt %1$s nehmen“…并取第 N 个出口”。句式重构即使含义相同译文也可能整体重组必须把参数嵌入新句式的正确位置。因此翻译时不要忘记这些占位符也不得擅自增删个数。如果不确定某个条目的参数含义应先在 GraphHopper Maps 上观察对应指令的英文原文或到社区论坛询问。源码层面TranslationHashMap.tr() 最终通过String.format(Locale.ROOT, val, params)完成渲染——占位符个数不匹配会直接导致格式化异常。四、翻译资源文件格式与内容构成每种语言对应一个 UTF-8 编码的.txt文件位于 core/src/main/resources/com/graphhopper/util/当前仓库共提供约 50 种语言包括en_US.txt、de_DE.txt、fr_FR.txt、zh_CN.txt、zh_TW.txt、ja.txt、ru.txt、es.txt等。文件格式为简单的keyvalue行# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continuecontinue continue_ontocontinue onto %1$s turn_leftturn left roundabout_exitat roundabout, take exit %1$s board_ferryAttention, take ferry (%1$s) web.total_ascend%1$s total ascent以//或#开头的行是注释会被忽略每行第一个左侧为 key右侧为译文value 为空的行会被跳过key 会被统一转为小写后存储见put()中toLowerCase(key)重复 key 会抛出IllegalStateException防止覆盖条目大致分两类转向指令类continue、turn_left、roundabout_exit、leave_ferry、board_ferry、pt_transfer_to等与Web 展示类以web.为前缀如web.total_ascend、web.way_contains_toll、web.start_label等供服务端组装路线汇总信息使用。以de_DE.txt为例同一 key 的德语译文为continuedem Straßenverlauf folgen continue_ontodem Straßenverlauf von %1$s folgen roundabout_exitim Kreisverkehr Ausfahrt %1$s nehmen roundabout_exit_ontoim Kreisverkehr Ausfahrt %1$s auf %2$s nehmen board_ferryAchtung, auf Fähre umsteigen (%1$s)五、源码解析TranslationMap 如何加载与回退TranslationMap.java 是服务端翻译的内存管理器核心逻辑如下1. 语言清单LOCALES类顶部的静态常量LOCALES列出全部受支持语言的 locale按字典序排列且英语en_US必须在列表最前因为它充当所有其他语言的参考基准。该清单必须与脚本core/files/update-translations.sh中的语言列表保持一致新增语言时两处都要改。2. 加载入口doImport()提供两个重载doImport()从classpath加载core/src/main/resources/com/graphhopper/util/下的locale.txtdoImport(File folder)从外部目录加载同构文件。加载完成后调用postImportHook()做一致性校验。GraphHopper 在 GraphHopper.java 启动时即通过new TranslationMap().doImport()初始化并暴露给路由组件。3. 兼容性别名add()add()在注册翻译对象的同时处理了新旧 JDK 的 locale 命名差异iw旧 JDK 希伯来语与he互相注册别名in旧 JDK 印度尼西亚语与id互相注册别名无国家后缀的语言代码会回填为同语言翻译保证get(de)也能命中de_DE的翻译。4. 回退机制getWithFallBack()public Translation getWithFallBack(Locale locale) { Translation tr get(locale.toString()); if (tr null) { tr get(locale.getLanguage()); if (tr null) tr get(en); } return tr; }查找顺序为完整 locale如zh_CN→ 语言代码如zh→ 英语en。get()内部还会将连字符-归一化为下划线_。这意味着任何未覆盖的语言最终都会安全回退到英语不会产生空译文。路由服务在 Router.java 中正是用getWithFallBack(request.getLocale())取得译文对象。5. 导入校验postImportHook()doImport完成后会对每种语言执行两项自动检查任一失败都会抛出IllegalStateException并打印错误清单这正是下文运行mvn clean test验证能拦住低级错误的原因缺失补全某语言缺失的条目自动用英语值补齐占位符校验比较译文与英文条目中%占位符个数是否一致并用占位符占位值实际执行一次String.format(Locale.ROOT, value, strs)捕获如%1$缺少结尾s之类的格式错误。六、翻译参与全流程从电子表格到合入第一步查看现有翻译并找到你的语言翻译条目托管在共享 Google Sheets 翻译总表中。打开文档后为你的语言新增一列若已存在则直接编辑该列随后定期回来更新或补充条目。你可以在 GraphHopper Maps 上实时预览自己的语言效果在路线 URL 中显式追加locale参数例如https://graphhopper.com/maps/?point40.979898%2C-3.164062point39.909736%2C-2.8125localede将localede换成zh、fr、es、ja等即可切换语言de→ 德语en→ 英语zh→ 简体中文以此类推。动手改翻译前若拿不准可以到官方翻译讨论版块GraphHopper discuss 的 translations 分区咨询。第二步本地准备 GraphHopper 开发环境翻译合入仓库前需要先让 GraphHopper 在你自己的电脑上跑起来git clone仓库后按 快速开始指南 完成源码构建。若你创建的是全新语言还需在两处登记字典序加入TranslationMap.LOCALEScore/src/main/java/com/graphhopper/util/下的TranslationMap.java加入脚本core/files/update-translations.sh 中translations变量对应的语言列表位置注意该列表首部的en_US SKIP SKIP结构第一列是参考语言后两列是跳过占位。第三步导出电子表格为 TSV进入core目录用curl将 Google Sheets 以 TSV 格式导出到临时文件gid0对应翻译工作表cd graphhopper/core curl -L https://docs.google.com/spreadsheets/d/18z00Rbt6QvLIkayEV9P89vW9oU0QbTVsjRk9nz1CeFY/export?formattsvid18z00Rbt6QvLIkayEV9P89vW9oU0QbTVsjRk9nz1CeFYgid0 tmp.tsv第四步运行更新脚本生成资源文件./files/update-translations.sh tmp.tsv rm tmp.tsv脚本逻辑见 update-translations.sh遍历语言清单跳过SKIP占位列对每个 locale 在src/main/resources/com/graphhopper/util/locale.txt生成文件首行写入请勿手工编辑的提示注释用tail -n5跳过 TSV 表头等前 4 行再用cut -f1,INDEX把英文 key 列 该语言列拆为keyvalue输出gcut/cut自动探测兼容 macOS 与 Linux。第五步检查改动并提交git diff git status确认只有本次翻译相关的文件发生变更。若未创建新语言改动应只落在若干.txt资源文件上。第六步构建测试验证占位符mvn clean test该步骤会触发前述postImportHook()的占位符一致性校验与格式化试运行若你的译文漏了%1$s、多写了%或写出了无法格式化的占位符测试会直接失败并打印出错的语言与条目从而保证没有丢失参数占位符对应上文第三节的告诫。第七步本地服务预览构建通过后启动 GraphHopper 服务见 快速开始指南在本地路由端点追加localede等参数验证效果若页面未自动切换语言就显式带上该参数http://localhost:8989/route?point...point...localede第八步提交贡献阅读 贡献指南 后按规范提交改动GraphHopper 维护团队也会定期将电子表格中的新翻译合入仓库因此即使你不走完整的合入流程只在表格中更新条目也能被周期性地同步进来。七、客户端翻译职责边界需要强调的是服务器端只翻译转向指令。GraphHopper Maps 前端界面中的其他文案按钮、弹窗、图层面板等属于客户端翻译范畴由graphhopper-maps前端项目独立维护如需贡献请直接在其翻译帮助文档中操作与本文所述的服务端资源文件无关。八、参与注意事项小结事项说明语言代码使用 ISO 639-1 双字母码新增语言按字典序登记到TranslationMap.LOCALES与update-translations.sh占位符%1$s等必须保留且个数与英文一致位置可按目标语言语序调整资源文件keyvalue格式、UTF-8 编码位于core/src/main/resources/com/graphhopper/util/自动校验postImportHook()负责缺失补全与占位符格式检查mvn clean test会拦截错误回退顺序完整 locale → 语言代码 →en任何语言都能安全兜底编辑原则资源文件由脚本生成请勿手工编辑应修改电子表格后重新导出服务端边界只翻转向指令界面文案走客户端翻译渠道相关资源翻译规范原文docs/core/translations.md翻译管理器实现core/src/main/java/com/graphhopper/util/TranslationMap.java更新脚本core/files/update-translations.sh语言资源目录core/src/main/resources/com/graphhopper/util/路由中的调用点core/src/main/java/com/graphhopper/routing/Router.java请求 locale 参数web-api/src/main/java/com/graphhopper/GHRequest.java源码构建指南docs/core/quickstart-from-source.md贡献规范CONTRIBUTING.md【免费下载链接】graphhopperOpen source routing engine for OpenStreetMap. Use it as Java library or standalone web server.项目地址: https://gitcode.com/GitHub_Trending/gr/graphhopper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进