news 2026/9/17 15:46:05

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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.java(translationMap.getWithFallBack(request.getLocale()));
  • 请求端的语言参数:GHRequest.java 的setLocale(Locale)/setLocale(String)

二、locale 语言代码:ISO 639-1 双字母码

翻译表中每个语言列的名称(如 "Spanish: es")中,冒号后的字符串就是该语言的ISO 639-1 双字符代码,例如:

语言代码
German / 德语de
English / 英语en
Simplified Chinese / 简体中文zh
French / 法语fr
Spanish / 西班牙语es
Italian / 意大利语it
Russian / 俄语ru
Japanese / 日语ja

如果你不确定自己语言的代码,可在维基百科查询该语言词条中给出的 ISO 639-1 代码。实际资源文件中,locale 往往带国家和地区后缀(如de_DEzh_CN),这是为了支持方言变体;但在 URL 查询参数与前端展示中,通常会退化为双字母形式。

三、%1$s占位符:为什么绝不能被省略

翻译条目中常出现%1$s%2$s这类字符,它们是JavaString.format位置参数占位符%1$s表示"第 1 个字符串参数",%2$s表示"第 2 个字符串参数",由 GraphHopper 在运行时填入路名、出口编号等动态值。

保留占位符至关重要,原因有两点:

  1. 语序差异:不同语言中参数出现的位置可能完全不同。例如英语是"Enter roundabout and use exit %1$s"(参数在句尾),而德语必须把"出口编号"参数放在句中并搭配动词:"In den Kreisverkehr einfahren und Ausfahrt %1$s nehmen"(“…并第 N 个出口”)。
  2. 句式重构:即使含义相同,译文也可能整体重组,必须把参数嵌入新句式的正确位置。

因此翻译时不要忘记这些占位符,也不得擅自增删个数。如果不确定某个条目的参数含义,应先在 GraphHopper Maps 上观察对应指令的英文原文,或到社区论坛询问。源码层面,TranslationHashMap.tr() 最终通过String.format(Locale.ROOT, val, params)完成渲染——占位符个数不匹配会直接导致格式化异常。

四、翻译资源文件格式与内容构成

每种语言对应一个 UTF-8 编码的.txt文件,位于 core/src/main/resources/com/graphhopper/util/,当前仓库共提供约 50 种语言,包括en_US.txtde_DE.txtfr_FR.txtzh_CN.txtzh_TW.txtja.txtru.txtes.txt等。

文件格式为简单的key=value行:

# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continue continue_onto=continue onto %1$s turn_left=turn left roundabout_exit=at roundabout, take exit %1$s board_ferry=Attention, take ferry (%1$s) web.total_ascend=%1$s total ascent
  • //#开头的行是注释,会被忽略;
  • 每行第一个=左侧为 key,右侧为译文;value 为空的行会被跳过;
  • key 会被统一转为小写后存储(见put()toLowerCase(key)),重复 key 会抛出IllegalStateException,防止覆盖;
  • 条目大致分两类:转向指令类continueturn_leftroundabout_exitleave_ferryboard_ferrypt_transfer_to等)与Web 展示类(以web.为前缀,如web.total_ascendweb.way_contains_tollweb.start_label等,供服务端组装路线汇总信息使用)。

de_DE.txt为例,同一 key 的德语译文为:

continue=dem Straßenverlauf folgen continue_onto=dem Straßenverlauf von %1$s folgen roundabout_exit=im Kreisverkehr Ausfahrt %1$s nehmen roundabout_exit_onto=im Kreisverkehr Ausfahrt %1$s auf %2$s nehmen board_ferry=Achtung, 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>.txt
  • doImport(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)→ 英语enget()内部还会将连字符-归一化为下划线_。这意味着任何未覆盖的语言最终都会安全回退到英语,不会产生空译文。路由服务在 Router.java 中正是用getWithFallBack(request.getLocale())取得译文对象。

5. 导入校验postImportHook()

doImport完成后会对每种语言执行两项自动检查,任一失败都会抛出IllegalStateException并打印错误清单(这正是下文"运行mvn clean test验证"能拦住低级错误的原因):

  1. 缺失补全:某语言缺失的条目自动用英语值补齐;
  2. 占位符校验:比较译文与英文条目中%占位符个数是否一致,并用占位符占位值实际执行一次String.format(Locale.ROOT, value, strs),捕获如%1$(缺少结尾s)之类的格式错误。

六、翻译参与全流程:从电子表格到合入

第一步:查看现有翻译并找到你的语言

翻译条目托管在共享 Google Sheets 翻译总表中。打开文档后,为你的语言新增一列(若已存在则直接编辑该列),随后定期回来更新或补充条目。你可以在 GraphHopper Maps 上实时预览自己的语言效果:在路线 URL 中显式追加locale参数,例如:

https://graphhopper.com/maps/?point=40.979898%2C-3.164062&point=39.909736%2C-2.8125&locale=de

locale=de换成zhfresja等即可切换语言(de→ 德语,en→ 英语,zh→ 简体中文,以此类推)。动手改翻译前若拿不准,可以到官方翻译讨论版块(GraphHopper discuss 的 translations 分区)咨询。

第二步:本地准备 GraphHopper 开发环境

翻译合入仓库前,需要先让 GraphHopper 在你自己的电脑上跑起来:git clone仓库后按 快速开始指南 完成源码构建。若你创建的是全新语言,还需在两处登记:

  1. 字典序加入TranslationMap.LOCALES(core/src/main/java/com/graphhopper/util/下的TranslationMap.java);
  2. 加入脚本core/files/update-translations.sh 中translations变量对应的语言列表位置(注意该列表首部的en_US SKIP SKIP结构:第一列是参考语言,后两列是跳过占位)。

第三步:导出电子表格为 TSV

进入core目录,用curl将 Google Sheets 以 TSV 格式导出到临时文件(gid=0对应翻译工作表):

cd graphhopper/core curl -L 'https://docs.google.com/spreadsheets/d/18z00Rbt6QvLIkayEV9P89vW9oU0QbTVsjRk9nz1CeFY/export?format=tsv&id=18z00Rbt6QvLIkayEV9P89vW9oU0QbTVsjRk9nz1CeFY&gid=0' > 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 -n+5跳过 TSV 表头等前 4 行,再用cut -f1,INDEX把"英文 key 列 + 该语言列"拆为key=value输出(gcut/cut自动探测,兼容 macOS 与 Linux)。

第五步:检查改动并提交

git diff git status

确认只有本次翻译相关的文件发生变更。若未创建新语言,改动应只落在若干.txt资源文件上。

第六步:构建测试验证占位符

mvn clean test

该步骤会触发前述postImportHook()的占位符一致性校验与格式化试运行:若你的译文漏了%1$s、多写了%,或写出了无法格式化的占位符,测试会直接失败并打印出错的语言与条目,从而保证"没有丢失参数占位符"(对应上文第三节的告诫)。

第七步:本地服务预览

构建通过后启动 GraphHopper 服务(见 快速开始指南),在本地路由端点追加&locale=de等参数验证效果;若页面未自动切换语言,就显式带上该参数:

http://localhost:8989/route?point=...&point=...&locale=de

第八步:提交贡献

阅读 贡献指南 后按规范提交改动,GraphHopper 维护团队也会定期将电子表格中的新翻译合入仓库,因此即使你不走完整的合入流程,只在表格中更新条目也能被周期性地同步进来。

七、客户端翻译:职责边界

需要强调的是,服务器端只翻译转向指令。GraphHopper Maps 前端界面中的其他文案(按钮、弹窗、图层面板等)属于客户端翻译范畴,由graphhopper-maps前端项目独立维护,如需贡献请直接在其翻译帮助文档中操作,与本文所述的服务端资源文件无关。

八、参与注意事项小结

事项说明
语言代码使用 ISO 639-1 双字母码,新增语言按字典序登记到TranslationMap.LOCALESupdate-translations.sh
占位符%1$s等必须保留且个数与英文一致,位置可按目标语言语序调整
资源文件key=value格式、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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 15:40:42

C++图书管理系统源代码拆解:面向对象、文件持久化与STL改造

简介&#xff1a;这份 C 图书管理系统设计源代码文档面向计算机专业课程设计、C 面向对象编程练习者及需要完成图书管理类大作业的学生&#xff0c;围绕借书、归书、书籍管理、读者管理与检索等典型业务提供可参考的源码组织思路。内容涵盖按图书编号查询现存量并登记借阅者学号…

作者头像 李华
网站建设 2026/9/17 15:40:12

Eino-Workflow架构解析与性能优化实战

1. Eino-Workflow 核心架构解析Eino-Workflow 作为新一代自动化流程引擎&#xff0c;其核心设计理念源于对复杂业务场景的抽象与简化。我在金融科技领域实施过三个基于该框架的跨系统集成项目&#xff0c;发现其模块化架构特别适合处理多条件分支的异步任务流。1.1 引擎运行原理…

作者头像 李华
网站建设 2026/9/17 15:40:04

二叉搜索树验证:原理、实现与工程优化

1. 问题背景与核心概念二叉搜索树&#xff08;Binary Search Tree, BST&#xff09;是一种基础且重要的数据结构&#xff0c;在算法面试和实际工程中都有广泛应用。这道LeetCode Hot 100的第98题要求我们验证给定的二叉树是否符合BST的性质&#xff0c;看似简单实则暗藏多个考察…

作者头像 李华
网站建设 2026/9/17 15:38:52

动平衡精度计算的标准方法:从G等级到许用不平衡量

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 15:36:41

FreeMoCap 实战指南:免费开源动作捕捉系统完整上手

FreeMoCap 实战指南&#xff1a;免费开源动作捕捉系统完整上手 【免费下载链接】freemocap Free Motion Capture for Everyone &#x1f480;✨ 项目地址: https://gitcode.com/GitHub_Trending/fr/freemocap 做角色动画却请不起动捕棚&#xff1f;一套商用动作捕捉系统…

作者头像 李华
网站建设 2026/9/17 15:33:40

ip6tables-save详解:IPv6防火墙规则备份与恢复实战

如果你在 Linux 上配置过 IPv6 防火墙&#xff0c;大概率经历过这样的场景&#xff1a;花半小时敲了一串ip6tables规则&#xff0c;各种链、各种匹配条件&#xff0c;好不容易调通了&#xff0c;结果一不小心按了重启&#xff0c;规则全没了&#xff0c;又得从头再来。或者你在…

作者头像 李华