ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Shaka Player UI 本地化系统设计原则:最接近匹配与一致性优先

Shaka Player UI 本地化系统设计原则:最接近匹配与一致性优先 Shaka Player UI 本地化系统设计原则最接近匹配与一致性优先【免费下载链接】shaka-playerJavaScript player library / DASH HLS client / MSE-EME player项目地址: https://gitcode.com/GitHub_Trending/sh/shaka-player导读本文基于 Shaka Player 官方设计文档 localization-design-principles.md 展开系统讲解其 UI 本地化Localization系统的核心设计决策如何在用户指定的多个语言偏好中为每一条 UI 文案如播放暂停字幕等找到最不突兀的本地化翻译同时保证一致性优先于精确性。读完本文你将理解 Locale、Phrase、Context、Localized Phrase 等术语的含义掌握 self / parent / siblings / children 四组回退搜索的完整顺序、多语言偏好间的切换规则以及为查询优先、一次性扁平化合并的性能设计思路并结合 ui/localization.js 与 lib/util/language_utils.js 的源码与测试理解其底层实现。概述为什么需要一套本地化设计原则Shaka Player 的 UI 库需要向全世界不同语言的用户展示控件文案。UI 上每一段文字都可能需要在数十种语言间切换而真实场景远比把英文翻译成法文复杂用户可能同时偏好多种语言例如英语母语、法语二语同一语言存在地区差异en与en-US、en-GB、en-CA某个词条在用户的首选语言中缺失却存在于其他语言中词条缺失时的降级方案既要尽量可读又不能造成同一界面混杂多国语言的割裂感。本文档的目的正是让阅读该系统的人理解这些设计决策背后的原因——为什么系统会返回某个结果比系统返回了什么更重要。术语定义设计文档首先给出了一套统一的术语后续所有搜索策略都建立在这些定义之上术语定义示例Locale一种语言的标识符具体规则见 Talking About Languagesen-CAPhrase在某个 locale 中具有一定含义的任何词串-Context对某条短语的一段描述The title for a button that stops video playback停止视频播放的按钮标题Localized Phrase特定 locale 中、映射到某个 Context 的短语arretfr-CA 中停止其中 Locale 的具体构成与关系在配套文档 talking-about-languages.md 中进一步细化一个 Locale 由三个组件构成——language小写两位字母优先取自 ISO 639、region大写两位字母优先取自 ISO 3166、dialect小写多字符并且必须符合language、language-REGION、language-REGION-dialect三种模式之一。Locale 之间按树状结构组织因此文档使用parent / child / siblings / grandparent这类树术语来描述关系例如en是en-US的 parenten-US是en-US-tx的 parenten-US与en-CA互为 siblings。补充在实际实现中Shaka 刻意不支持 dialect。这一点在 fuzzy-locale-matching.md 中明确说明——读取 locale 时有意丢弃 dialect 组件以简化搜索因为至今未见 dialect 出现在内容中。lib/util/language_utils.js 的normalize()正是这样实现的它只保留language、region与 RFC 5646 的私有扩展x-前缀其余全部丢弃同时把三位语言码ISO 639-2映射为两位码ISO 639-1并强制语言小写、区域大写。核心设计原则一致性优先于精确性为某个 Context 与 Locale 找到最接近的Localized Phrase同时尊重一致性优先于准确性。设计文档将核心原则浓缩为这一句话系统应尽可能以最不突兀least jarring的方式返回对用户有意义的结果。什么是最不突兀先看一个简单例子用户希望在 en-US 下获得停止视频播放的按钮标题这条文案系统没有 en-US 的翻译但拥有 en、en-CA、fr-CA 的翻译。问题是应该返回en的翻译语言相同、地区缺失还是fr-CA的翻译地区相同、语言不同直觉告诉我们应选en——这就是最接近的含义。而一致性优先的考量来自更现实的情况人们通常掌握不止一种语言因此偏好可能包含多个语言。再看一个更准确的例子用户希望在 en-US 或 fr-CA 下获得同一条文案系统没有 en-US但拥有 en、en-CA、fr-CA。此时若允许跨偏好各自取最接近结果界面可能出现一部分英语、一部分法语。虽然每一条单独看都更准确但整体体验反而割裂——这违背了least jarring的目标。因此系统选择优先把整个界面保持在同一种语言内详见下文语言间搜索。两层搜索先语言内再语言间当系统寻找最接近的本地化短语时实际执行两级搜索语言内搜索Searching Within A Locale——在单个 locale 内部按相似度分组寻找匹配语言间搜索Searching Between Locales——当存在多个偏好 locale 时决定如何跨偏好选择。两级搜索的先后关系是先在第一个偏好 locale 内做完整搜索只有当它内部完全找不到匹配时才移动到下一个偏好 locale。这一规则决定了整套回退行为的走向。语言内搜索self / parent / siblings / children 四组回退在单个 locale 内搜索匹配时系统把与该 locale 相关的所有语言划分为四个分组按精确度从高到低排列分组含义例如 locale 为 en-US例如 locale 为 enSelf完全相同的 locale[ en-US ][ en ]Parent仅有语言组件无地区的上级[ en ][ ]Siblings同语言、不同地区[ en-CA, en-GB ][ ]Children由该 locale 派生的带地区子级[ ][ en-CA, en-GB, en-US ]搜索按self → parent → siblings → children的顺序逐组进行一旦在某个组的某个 locale 中为给定 Context 找到本地化短语立即停止并返回该结果。下面两个例子完整展示了这一搜索顺序。假设系统可用的 locale 为en, en-US, en-GB, en-CA, fr, fr-CA。搜索 en 时self 命中直接返回en若无则依次尝试 children 中的en-US、en-GB、en-CAAvailable locales: en, en-US, en-GB, en-CA, fr, fr-CA Search order for en: | 1. self | - | 2. parent | - | 3. siblings | - | 4. children | ----------------------------------------------------------------- | a. en | | | | | | a. en-US | | | | | | | | b. en-GB | | | | | | | | c. en-CA |搜索 en-US 时self 优先然后是 parenten接着是 siblingsen-GB、en-CAchildren 为空Available locales: en, en-US, en-GB, en-CA, fr, fr-CA Search order for en-US: | 1. self | - | 2. parent | - | 3. siblings | - | 4. children | ------------------------------------------------------------------- | a. en-US | | a. en | | a. en-GB | | | | | | | | b. en-CA | | |源码印证这四组关系在 lib/util/language_utils.js 中都有对应的判定函数——isParentOf()要求父 locale 只有语言组件、子 locale 有语言与地区组件且语言相同isSiblingOf()要求两个 locale 都含地区组件且语言相同areLanguageCompatible()只比较语言组件。而 ui/localization.js 的updateCurrentMap_()在构造搜索顺序时正是按偏好 locale 本身 → base 语言parent→ 排序后的 siblings → 排序后的 children → fallback依次加入集合其中 siblings 与 children 都先经过排序以保证无论本地化表的插入顺序如何等价距离的语言都能以稳定、确定的顺序出现。语言间搜索一个偏好完整失败后才轮到下一个当用户提供多个偏好 locale例如 en-US或fr-CA时系统只有在前一个 locale 完全没有找到匹配的情况下才会移动到后一个 locale。原因正如核心原则所言无论匹配得多宽松我们都更倾向于让整个界面显示在同一种语言里。文档给出了多偏好下的完整搜索顺序示例。假设可用 locale 为en, en-US, en-GB, en-CA, fr, fr-CA, fr-FR, ...Search Order for multiple preferences: | Preference 1 | - | Preference 2 | - ... - | Preference N | | (en-US) | | (fr-CA) | | (...) | ---------------------------------------------------------------- | a. en-US | | a. fr-CA | | ... | | b. en | | b. fr | | | | c. en-GB | | c. fr-FR | | | | d. en-CA | | | | |即先在整个 Preference 1en-US的四组回退中寻找全部失败后再进入 Preference 2fr-CA的四组回退依此类推直到 Preferences N。这一整语言替换而非逐条最优的策略就是一致性优先于准确性在语言间维度上的落地。测试佐证test/ui/localization_unit.js 中的用例prefers a relative of an earlier locale to an exact later one专门验证了这一点——当偏好列表为[HALFLING, ELVISH]时即使ELVISH在第二个位置是精确匹配系统仍优先返回第一个偏好HALFLING的亲属 locale 中的翻译HALFLING_COMMON_VALUE。因为语言内搜索优先于语言间搜索。为查询优先插入少、查询多预先生成扁平映射本地化系统有两个关键操作插入insertion与查询look-up。设计上假定插入发生的频率远低于查询因此系统必须优先保证查询的效率。为此系统采取的策略是把本来需要逐表查找的所有本地化表一次性扁平化flatten为单一 Map。这项工作在任何请求到来之前完成使每次请求都退化为一次直接的表查询table look-up而非多次表查询。合并时有一个关键细节按偏好顺序的逆序进行合并。这样后合并的更偏好的条目会覆盖先合并的次偏好的条目最终每个 key 下留下的都是优先级最高的值。文档用如下示意说明合并顺序Preference Order : en-US en en-GB en-CA Merge Order : en-CA en-GB en en-US Merged : [ A4 ] [ B1 ] [ C2 ] [ D1 ] [ E2 ] [ F3 ] [ G1 ] ^ ^ ^ ^ ^ ^ ^ en-US : ^ [ B1 ] ^ [ D1 ] ^ ^ [ G1 ] en : ^ [ B2 ] [ C2 ] [ D2 ] [ E2 ] ^ [ G2 ] en-GB : ^ [ B3 ] [ C3 ] [ D3 ] [ E3 ] [ F3 ] [ G3 ] en-CA : [ A4 ] [ B4 ] [ D4 ] [ E4 ] [ G4 ] ...可以看到合并从最不偏好的en-CA开始en-GB覆盖同 key 的旧值接着en、en-US依此覆盖最终 merged 表中B1来自 en-US、C2来自 en、F3来自 en-GB等都是各自优先级最高的值。整个查找过程因此从遍历多张表变成单表 get。源码印证这一步在 ui/localization.js 中实现——updateCurrentMap_()把按偏好顺序构建好的 locale 列表逐个取出对应 Map 存入mergeOrder然后mergeOrder.reverse()最后依次把每张 Map 的键值对写入currentMap_。由于后写覆盖先写最终currentMap_即为最接近当前偏好的单一映射。而公开的查询入口 resolve(id) 对currentMap_.get(id)做一次直接查找找不到时才返回空字符串并派发UNKNOWN_LOCALIZATION事件。源码级落地Localization 类与四大事件设计原则最终沉淀为一个可导出的shaka.ui.Localization类继承自shaka.util.FakeEventTarget核心公开 API 与设计文档一一对应API对应设计环节说明constructor(fallbackLocale)兜底语言假定该语言应有几乎全部词条通常为 enchangeLocale(locales)多偏好切换传入按偏好排序的 locale 列表规范化后重建扁平映射对无法精确匹配的 locale 派发UNKNOWN_LOCALES事件并派发LOCALE_CHANGED事件insert(locale, localizations, conflictResolution)插入向某 locale 追加/合并词条表之后重建映射并派发LOCALE_UPDATED支持链式调用resolve(id)查询单表查询当前最优翻译找不到返回空字符串并派发UNKNOWN_LOCALIZATION事件resolveDictionary(dictionary)批量查询面向数据绑定框架的便捷方法把字典中每个 key 原地替换为resolve(key)的结果getCurrentLocales()状态查询返回当前偏好 locale 集合空集合表示无偏好插入冲突由shaka.ui.Localization.ConflictResolution枚举控制USE_OLD保留旧值与USE_NEW使用新值默认行为。这一设计在 insert() 中体现仅当表中不存在该 key 或指定USE_NEW时才写入新值。系统共定义四个对外事件帮助应用感知本地化状态的缺口与变化见 localization.jsUNKNOWN_LOCALESunknown-locales请求的 locale 完全没有词条时触发事件携带缺失的 locale 列表系统仍会继续用最接近的匹配工作UNKNOWN_LOCALIZATIONunknown-localization某词条在偏好、亲属及 fallback 中都找不到时触发resolve()返回空字符串MISSING_LOCALIZATIONSmissing-localizations词条在偏好 locale 缺失、但在亲属或 fallback 中找到时触发用于帮助开发者发现翻译缺口LOCALE_CHANGED/LOCALE_UPDATEDlocale 切换成功或插入导致已解析值可能变化时触发UI 可据此刷新已显示文本。语言工具函数relatedness 与 findClosestLocale设计文档中四组回退的直觉在工具层被形式化为可量化的亲缘度分数。lib/util/language_utils.js 的relatedness(target, candidate)为两个 locale 计算 0~4 的亲缘评分分数越高匹配越好分数关系示例target en-US4精确匹配locale 兼容en-US3candidate 是 target 的 parenten2candidate 是 target 的 siblingen-CA、en-GB1candidate 是 target 的 childtarget 为 en 时en-US0无关fr-CA另一个更贴近设计文档在一个集合中找最接近项语义的函数是findClosestLocale(target, searchSpace)language_utils.js其优先级顺序与文档完全一致精确匹配 → parent → sibling → child → 无匹配返回 null。它常用于媒体内容的语言选择例如在播放器可用音轨语言中为用户偏好找最接近项与 UI 本地化系统的self → parent → siblings → children回退逻辑互为印证。与模糊语言匹配Fuzzy Locale Matching的关系本地化设计原则与 fuzzy-locale-matching.md 描述的是同一哲学的两面Fuzzy Locale Matching回答的是内容选择问题用户偏好en-UK但我们只有en应该放什么给用户看其策略同样遵循 Locale Compatible → Parent Locale → Language Compatible 的三级回退例如en-UK→enParent Locale、fr→fr-CALanguage Compatible、zk→ No MatchLocalization Design Principles回答的是 UI 文案问题按钮标题在偏好语言中缺失时回退到哪种语言的翻译其四组回退self / parent / siblings / children正是 fuzzy matching 三级策略在 UI 文案维度上的细化与扩展。两者共享同一套 Locale 树模型与兼容性定义Locale Compatible / Region Compatible / Language Compatible见 talking-about-languages.md因此对语言的处理口径完全一致——都主动丢弃 dialect、都优先保证整界面/整清单语言的统一。实际语料ui/locales 目录设计原则的最终载体是ui/locales/目录下的语言表。该目录当前包含 50 个 locale 的 JSON 文件如en.json、en-GB.json、fr.json、es-419.json、pt-BR.json、zh.json、zh-TW.json、zh-HK.json等。每个文件是一个{ KEY: 本地化文本 }的映射例如 en.json 中的AD_DURATION: Ad duration、AUTOPLAY: Autoplay、LIVE: Live、MULTIPLE_LANGUAGES: Multiple languages等条目均通过localization.resolve(LocIds.XXX)按设计文档的搜索顺序被 UI 控件消费可参考 ui/language_utils.js 中localization.resolve(LocIds.SURROUND)的用法。其中两个特殊 locale 值得注意见 ui/locales/README.mdar-XB从右向左的英语与en-XA带重音的英语是仅用于测试的元语言用于识别 RTL 排版问题与硬编码文本oc奥克语与sjn辛达林语托尔金作品中的精灵语不由 Google 维护需要社区保持更新——sjn正是 test/ui/localization_unit.js 中大量使用精灵语/矮人语假 locale 进行回退行为测试的灵感来源。测试验证回退行为的完整覆盖设计文档中的每一种搜索行为都能在 test/ui/localization_unit.js 中找到对应的单元测试插入与冲突insert可新增数据、可替换旧数据、可用USE_OLD保留旧数据fallback 回退单个/多个未知 locale 回退到最终 fallback已知 locale 优先于 fallback列表中的第一个已知 locale 优先parent 翻译可用[ELVISH_WOODLAND]→ELVISH的值sibling 翻译可用同语言不同地区等价距离时按字母序选择child 翻译可用[HALFLING]→HALFLING_COMMON的值事件UNKNOWN_LOCALES在切换到未加载 locale 时触发、补齐后不再触发UNKNOWN_LOCALIZATION在 preferred/base/sibling/fallback 全缺失时触发并返回空串MISSING_LOCALIZATIONS在词条仅存在于 fallback/base/sibling 时触发LOCALE_CHANGED在切换包括切换到相同 locale时触发。这些测试以可执行的方式固化了设计文档中的全部搜索顺序规则任何改动若违反self → parent → siblings → children前一个偏好完全失败才进入下一个偏好一致性优先于准确性等约束都会被测试直接拦截。总结Shaka Player 的本地化设计原则可以浓缩为四点以 Context 为桥梁UI 文案通过短语语义描述Context而非字符串本身来定位翻译从而天然支持多语言与翻译协作四组回退层次分明语言内搜索严格按 self → parent → siblings → children 的精度顺序命中即止一致性优先多语言偏好之间整语言切换宁可损失单条精确度也要避免界面语言混杂查询优先的扁平化预先按偏好逆序合并所有语言表为单一 Map让高频查询退化为一次Map.get。这套原则不仅指导着shaka.ui.Localization的实现与ui/locales/语料库的维护也通过单元测试被完整固化构成了一个设计文档—源码—测试三层互相印证的体系是理解 Shaka Player UI 国际化架构的最佳入口。【免费下载链接】shaka-playerJavaScript player library / DASH HLS client / MSE-EME player项目地址: https://gitcode.com/GitHub_Trending/sh/shaka-player创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表