多语言方案介绍与迁移(三) - 技巧与迁移

953 阅读5分钟

介绍

在前两篇完整地介绍了如何在项目中引入多语言后,本篇文章将介绍一些小技巧让多语言开发更加方便,并在最后分享一个迁移旧项目的经历。

技巧

1. 为html设置好lang属性

lang属性用于标识页面的语言,有助于搜索引擎检索信息,优化网站SEO。屏幕阅读器也会根据该属性切换语言。

lang的枚举值可以在IANA Language Subtag Registry中查阅,具体选用规则可以参考w3规范

2. 通过预处理器简化本地化样式

除了多语言的文本外,对不同语种的样式调整也是非常常见的,所以我们可以利用css预处理器优化这部分逻辑,下面以scss为例:

// 指定语言特殊样式
@mixin multiLanguage($languages) {
  html[lang='#{$languages}'] & {
    @content;
  }
}
// 多语言图片
@mixin multiLanguageImg($image, $languages: 'en-US' 'ar-EG', $suffix: '.png') {
  background-image: url($image + '/' + nth($languages, 1) + $suffix);
  @each $key in $languages {
    @include multiLanguage($key) {
      background-image: url($image + '/' + $key + $suffix);
    }
  }
}
// 阿拉伯语等语言阅读顺序是从右到左
@mixin rtlDirection {
  html[dir='rtl'] & {
    @content;
  }
}

/** 
 * 多语言图片目录
└─banner
  ├─ar-EG.png
  └─en-US.png
 * **/

// 调用例子
.image {
  height: 35px;
  width: 100%;
  @include multiLanguageImg('~@/assets/images/banner');
  background-repeat: no-repeat;
  background-size: auto 35px;
  @include rtlDirection() {
    background-position: right;
  }
}

这种方式是通过html上的langdir属性获取当前页面的状态,所以需要提前对html文档做好处理。

3. 在js中获取多语言图片

在某些情况下,我们需要通过img标签或其他方式获取多语言的图片,这种情况下,常见的方案是通过Map语言与图片的映射关系,获取到指定图片。但这种方式麻烦,且一旦新增语种,需要修改的地方会非常多。

这里我们可以借助require.context的方法,引入多语言目录下的图片。

require.context是webpack的一个API,可以获取到指定目录下所有模块的引用。在typescript环境下,需要安装@types/webpack-env,并在tsconfig.json文件中添加compilerOptions:: { "types": ["node", "webpack-env"], ... },引入webpack类型库。

// 获取当前语言的图片
// i18n部分为伪代码,需根据项目i18n方案进行调整
const getIntlImg = (getImgs: __WebpackModuleApi.RequireContext) => {
  const currentLang = i18n.language || i18n.defaultLanguage;
  let src = getImgs.keys().find((v) => {
    return v.includes(currentLang);
  });
  if (!src) {
    src = getImgs.keys().find((v) => {
      return v.includes(currentLang);
    });
  }
  return src && getImgs(src);
};

/** 
 * 多语言图片目录
└─banner
  ├─ar-EG.png
  └─en-US.png
 * **/

// 调用代码:
export default function Page() {
  return (
    <img
      src={getIntlImg(
        require.context('@/assets/images/banner', false),
      )}
    />
  )
}

迁移

迁移这部分是由于一个旧多语言项目需要增加多一种语言的需求开始的。

这个旧项目是多个静态页面组成,为了性能考虑,没有使用框架,都是直接使用Vanilla JS。更致命的是,在不同时期由不同人维护时,多语言的适配没有标准,甚至连语言的标识都不一样,比如有些使用en,有些使用en-US等。下面我列举出当时的使用几种多语言方案:

  1. data-[lang]
<th data-en="Result" data-hi="परिणाम"></th>
:root [data-en]::before {
  content: attr(data-en);
}
:root[lang='hi-IN'] [data-hi]::before {
  content: attr(data-hi);
}
  1. lang标签+data-lang
<lang data-lang="en_US">Result</lang>
<lang data-lang="hi_IN">परिणाम</lang>
lang {
  display: none;
}
:root[lang='en_US'] lang[data-lang='en_US'] {
  display: inline;
}

:root[lang='hi_IN'] lang[data-lang='hi_IN'] {
  display: inline;
}
  1. 使用额外的js将指定id的元素内容进行替换
<span id="result"></span>
const initLangugeDom = function (language) {
  languageValue = LANGUAGE[language][item];
  for (item in languageValue) {
    if (document.querySelector("#" + item)) {
      document.querySelector("#" + item).innerHTML = languageValue[item];
    }
  }
};

前两种方式一旦新增语种,就需要一行行去修改html代码,拓展性非常差,所以为了后续能顺利开发,我需要将使用前两种方案的代码修改为了第三种。当然,第三种方案也需要修改,直接使用id进行翻译的替换容易出现重复id、用途不明的问题,所以我用data-id自定义属性替换了id。

为了更有效率的完成迁移工作,我写了两个脚本,用于替换页面翻译改为data-id的标签并将翻译整合为对象导出。

两个脚本的思路是一样的,首先遍历一次英文文本,自动生成翻译对象的key,后续再将添加翻译文本并修改原html dom结构。

在chrome打开页面后,调出控制台Sources面板,点击New snippet按钮,创建代码片段。利用这种方式,可以保存代码,在多个页面重复执行获取修改后的翻译对象与html文档。

source.jpg

// 两个方案相似,这里贴出第一个多语言方案的迁移脚本
const obj = {}
const keyList = []
Array.from(document.querySelectorAll("[data-en]")).forEach(item=>{
    const langKey = item.dataset.en
    let textKey = langKey.toLowerCase().replaceAll(/\s/g, '_')
    textKey = textKey.replaceAll(/\W/g, '')
    textKey = textKey.split('_').slice(0, 5).join('_')
    if (keyList.includes(textKey)) {
        let index = 2
        while (1) {
            if (!keyList.includes(`${textKey}_${index}`)) {
                keyList.push(`${textKey}_${index}`)
                break
            }
            index++
        }
        console.log('重复key', textKey)
    } else {
        keyList.push(textKey)
    }
}
)
function setLangObj(langKey, searchKey) {
    obj[langKey] = {}
    Array.from(document.querySelectorAll(`[data-${searchKey}]`)).forEach((item,index)=>{
        let text = item.dataset[searchKey]
        item.removeAttribute(`data-${searchKey}`)
        if (langKey === 'en') {
            item.setAttribute('data-id', keyList[index])
        }
        obj[langKey][keyList[index]] = text
    }
    )
}
setLangObj('en', 'en')
setLangObj('hi', 'hi')

console.log('翻译对象', obj)
console.log('html', document.getElementsByTagName('html')[0].innerHTML)

总结

本文介绍了几个多语言的使用技巧,方便开发者处理多语言特定的图片与样式。然后简述了近期的一个迁移需求,但整个处理方案并不完美,没有实现在代码层面自动扫描文本的功能,这一方面也是项目的历史包裹过重的缘故,希望后续有机会再调研下自动扫描文本实现原理与方案实践。

参考链接

多语言方案介绍与迁移(一) - Google Sheets

多语言方案介绍与迁移(二) - 结合nextjs-ssg方案

sass mixin文档

require.context文档

Why use the language attribute?

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天