前端开发过程中,有时我们需要获取用户浏览器所用的语言,进而向用户显示不同的内容。如何获取用户浏览器语言呢?

最直接的,就是访问浏览器内置的 navigator.language 属性:

var lang = navigator.language 

根据你的浏览器的设置,这段代码会返回不同的值,比如 zh-CNen-USzh-TWzh 之类,如下图:

这个值表示当前浏览器的首选语言,也即当浏览器发起 HTTP 请求时,HTTP 请求头中的 Accept-Language 字段的内容中排在最前面的语言。

浏览器还支持一个 navigator.languages 属性,返回一个数组,内容为当前浏览器首选的多个语言,前后顺序表示优先级顺序。

HTTP 请求头中的 Accept-Language 的值形如这样:

accept-language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,ja;q=0.5

值以逗号 , 分隔为几组,其中 q=0.9 这样的值表示优先级顺序。例如 en-US;q=0.8 是一组,表示 en-US 是浏览器可接受的语言之一,优先级是 0.8。如果省略 q 值则表示优先级为 1(最高)。

一般来说,获取到 navigator.language 即可以了,这个值即表示用户浏览器首选的语言,通常来说,这个语言也是浏览器界面所使用的语言。

但是……

浏览器首选语言 ≠ 浏览器界面语言

以 Chrome 为例,我们完全可以让 Chrome 的界面显示为英语(en-US),却让浏览器请求的首选语言为中文(zh-CN),也完全可以反过来,让界面显示为中文,请求的首选语言为英语。

以 Chrome 为例,浏览器的界面语言与首选语言可以不一致,上图中界面的语言为中文,但首选语言是英语(美国)。

这有什么差别呢?

界面(UI)语言是浏览器显示自身时使用的语言,比如浏览器菜单等处的文案所显示的语言,首选语言则是浏览器发起 HTTP 请求时所标注的语言,即 HTTP 头中 Accept-Language 字段中包含的语言。

上面的 navigator.languagenavigator.languages 属性,都只能获得浏览器的首选语言,而无法获得浏览器的界面语言。

那么……

我们可以通过 JavaScript 方法获取用户浏览器的界面语言吗?

通常来说,不可以。浏览器没有为 JavaScript 提供任何接口来访问浏览器的界面语言。

但是,实际操作中,我们居然真的能找到一些方法,有限制地判断当前浏览器的界面语言。

注:以下内容基于 Chrome 74。

首先,我们来看一个普通的标签:<input>

W3C 网站上,对 <input> 标签的各种属性有详细的定义,其中比较有趣的是对 type 属性为 submit<input> 标签的说明:

When an input element's type attribute is in the Submit Button state, the rules in this section apply.

The input element represents a button that, when activated, submits the form. If the element has a value attribute, the button's label must be the value of that attribute; otherwise, it must be an implementation-defined string that means "Submit" or some such. The element is a button, specifically a submit button.

注意其中我用粗体标出的部分,这一句的意思是:“如果这个 <input> 没有给定 value 属性,那么应当显示一个由实现方定义的含义为「提交」的值。”也就是说,submit 类型的 <input> 会有一个默认值,且这个值由实现方(比如浏览器)自行定义。

接下来,我们编写一个最简单的 HTML 代码片断进行试验:

<input type="submit">

简单测试即可发现,上面这个 <input> 按钮的确会显示一个默认的文本,根据浏览器界面语言的不同,值可能是“提交”、“Submit”之类。

如果浏览器界面语言是英语,则显示“Submit”。
如果浏览器界面语言是中文,则显示“提交”。

可以看到,<input type="submit"> 按钮上显示的文本语言,和浏览器界面的语言一致,即 W3C 标准中提到的“implementation-defined string”。(注:上面的测试在 Chrome 74 中进行,不同的浏览器中这个文本可能不同。)

类似的 <input type="file"> 也会根据浏览器界面语言的不同显示不同的文本。

那么,是不是可以直接读取这个值,然后判断语言呢?

遗憾的是不能。如果直接用 JavaScript 去访问这个节点的 value 属性,只能得到一个空字符串,尽管它实际上显示了值。

<input type="submit" id="ipt">
console.log(document.getElementById('ipt').value); // 返回空字符串

看起来似乎山穷水尽了,直到偶然间读到这篇帖子,里面提到一个很有创意的点子:中文的“提交”和英文的“Submit”长度不一样,我们可以通过比较这个 <input> 按钮长度的方式来判断当前浏览器的界面语言。

一个 demo 见下图:

值为“Submit”的按钮和值为“提交”的按钮长度不同。

上图中的三个按钮的 HTML 如下:

<input type="submit">
<input type="submit" value="Submit">
<input type="submit" value="提交">

即第一个不设置 value,这样它就会使用浏览器界面语言中定义的值,然后再添加两个值分别为英语和中文的 <input>,看哪个的长度和第一个 <input> 的值相同,即可判断出当前浏览器的界面语言是英语还是中文。

完整 demo 参见这个链接:https://codepen.io/oldj/pen/ydyXqR

这样,通过判断 <input type="submit"> 元素宽度的方式,我们便有了一个判断浏览器界面语言的方法。不过这个方法并不完美,只能做出一些有限制的判断,比如如果已知浏览器界面语言是英文或中文,则可以做出判断,但如果界面语言完全未知,判断起来就麻烦了。

另外,有一些语言不同但字符长度一样,甚至可能字符内容也一样,比如简体中文 zh-CN 与繁体中文 zh-TW 中的“提交”文字完全一样,西班牙语与葡萄牙语中表示提交的文字都是“Enviar”,这些情况下这个方法就无法区分了。

进一步……

上面提到,我们无法通过 document.getElementById('ipt').value 之类的方法来获取 <input type="submit"> 按钮显示的文本值(即“implementation-defined string”),直接访问返回的会是一个空字符串。那还有其他方法吗?

测试了一下,发现还真的有,在提交 Form 表单的时候,这个“implementation-defined string”是会一起提交到服务器端的。

建一个 HTML 页面,内容中包含以下代码:

<form method="get">
	<input type="submit" name="sub">
</form>

运行,点击那个提交按钮,表单会以 GET 的方式提交到当前地址:

可以看到,提交按钮的值被当作参数传了过来。

提交表单需要页面刷新,有办法直接在当前页面通过 JavaScript 拿到这个值吗?试验了一下,只需要将这个表单提交到当前页面上一个不可见的 <iframe> 即可。代码如下:

<form id="my_form" action="about:blank" target="ifr" method="get">
	<input type="submit" name="sub">
</form>

<iframe name="ifr" width="1" height="1" style="display: none;"></iframe>

<div id="result">
  The implementation-defined string of input[type=submit] is: <span></span>
</div>

<script>
function check () {
  $('#my_form input[type=submit]').click()
  setTimeout(() => {
    let h = window.frames.ifr.window.location.href
    //console.log(h)
    let m = h.match(/sub=([^&amp;]+)/)
    $('#result span').html(m ? decodeURIComponent(m[1]) : 'unknow')
  }, 100)
}

check()
</script>

运行,即可在当前页面获得提交按钮的“implementation-defined string”,如下图所示:

如果浏览器界面语言是英语,则得到“Submit”。
如果浏览器界面语言是中文,则得到“提交”。

完整 demo 可见:https://codepen.io/oldj/pen/NZPeMo

这样,我们就得到了 <input type="submit"> 的“implementation-defined string”,从而可以根据其值判断当前浏览器的界面语言(而不是首选语言)。当然,这个方法也有缺点,如果两种语言里表示“提交”的字符串相同(比如简体中文和繁体中文,葡萄牙语和西班牙语),那么就无法进行区分。

另外,以上测试都是在 Chrome 74 下进行的,其他浏览器中“implementation-defined string”有可能不同,比如 Windows 10 中文环境下的 IE 11、Edge 中的值不是“提交”,而是“提交查询内容”,Firefox 下则为“提交查询”。不过也正是因为多了一些字,倒是可以区分出简体中文与繁体中文了。

小结

  1. 可以通过 navigator.languagenavigator.languages 属性访问浏览器的首选语言,首选语言即浏览器发送 HTTP 请求时请求头中 Accept-Language 的语言;
  2. 浏览器首选语言与浏览器界面语言可以不同,<input type="submit"> 等没有指定 value 属性的标签时会自动显示一个由浏览器实现自定义的值(“implementation-defined string”),这个值的语言通常与浏览器的界面语言一致;
  3. 这个“implementation-defined string”无法通过 JavaScript 直接访问,但可以通过读取 <input type="submit"> 元素宽度来判断,或者通过提交表单的形式获取。