Web 应用程序中的数字本地化



如何在不更改字符代码的情况下设置阿拉伯数字的变体?

Eastern Arabic      ۰   ۱   ۲   ۳   ٦   ٥   ٤   ۷   ۸   ۹
Persian variant     ۰   ۱   ۲   ۳   ۴   ۵   ۶   ۷   ۸   ۹
Western Arabic      0   1   2   3   4   5   6   7   8   9 
(And other numeral systems)

下面是一个示例代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div lang="fa">0123456789</div>
<div lang="ar">0123456789</div>
<div lang="en">0123456789</div>
</body>
</html>

如何仅使用客户端技术(HTMLCSSJS)来做到这一点?
该解决方案应该不会对页面的SEO分数产生负面影响。

请注意,在Windows文本框(例如"运行")中,数字根据周围文本的语言正确显示。

另请参阅:桌面应用程序中的数字本地化

注意:使用此PHP软件包在后端进行数字本地化非常容易 https://github.com/salarmehr/cosmopolitan

下面是一种代码转换的方法:

// Eastern Arabic (officially "Arabic-Indic digits")
"0123456789".replace(/d/g, function(v) {
return String.fromCharCode(v.charCodeAt(0) + 0x0630);
});  // "٠١٢٣٤٥٦٧٨٩"
// Persian variant (officially "Eastern Arabic-Indic digits (Persian and Urdu)")
"0123456789".replace(/d/g, function(v) {
return String.fromCharCode(v.charCodeAt(0) + 0x06C0);
});  // "۰۱۲۳۴۵۶۷۸۹"

演示:http://jsfiddle.net/bKEbR/

这里我们使用 Unicode 移位,因为任何 Unicode 组中的数字都以与拉丁组中相同的顺序放置(即[0x0030 ... 0x0039])。因此,例如,对于阿拉伯语 - 印度语组移位是0x0630.

请注意,我很难区分东方字符,因此如果我犯了一个错误(Unicode 中有许多不同的东方字符组),您可以随时使用任何在线 Unicode 表计算偏移。您可以使用官方的 Unicode 字符代码图表或 Unicode 在线字符代码表。

人们必须决定这是外观问题还是转变问题。 还必须确定这是一个涉及字符级语义还是数字表示的问题。 以下是我的想法:


如果我们遇到Unicode没有放出数字字符代码的情况,这个问题将具有完全不同的语义。 然后,根据需要显示不同的字形只需使用适当的字体即可。 另一方面,如果不更改字体,就不可能像我在下面所做的那样简单地写出不同的字符。(情况并不完全完美,因为字体不一定涵盖 16 位 Unicode 集的整个范围,更不用说 32 位 Unicode 集了。

9, ٩ (Arabic), ۹ (Urdu), &#29590; (Chinese, complex), ๙ (Thai), ௯ (Tamil) etc.  

现在,假设我们接受Unicode语义,即'9',٩和'۹'是不同的字符,我们可以得出结论,问题不是关于外观(这本来属于CSS的范围),而是关于转换 - 稍后对此的一些思考,现在让我们假设是这种情况。 当专注于字符级语义时,情况与字母和字母的情况没有太大不同。 例如,希腊语"α"和拉丁语"a"被认为是不同的,尽管拉丁字母与Euboea中使用的希腊字母几乎相同。也许更引人注目的是,相应的大写变体"Α"(希腊语)和"A"(拉丁语)在几乎所有支持两种脚本的字体中在视觉上都是相同的,但就Unicode而言却是不同的。

在陈述了基本规则之后,让我们看看如何通过忽略它们来回答这个问题,特别是忽略(字符级)Unicode 语义。

(可怕、讨厌且不向后兼容) 解决方案:使用将"0"到"9"映射到所需字形的字体。 我不知道有任何这样的字体。 您必须使用@font面和一些已被适当黑客入侵的字体才能做您想做的事情。

不用说,我不是特别喜欢这个解决方案。 但是,这是我所知道的唯一简单的解决方案,它可以在服务器或客户端上"不更改字符代码"地完成问题所问的问题。 (从技术上讲,我在下面提出的 Cufon 解决方案也不会更改字符代码,但它的作用是将文本绘制到画布中要复杂得多,并且还需要调整开源代码)。


注意:任何转换解决方案,即任何更改 DOM 并将"0"到"9"范围内的字符替换为阿拉伯等价物的解决方案都将破坏期望数字以原始形式出现在 DOM 中的代码。 当然,在讨论表单和输入时,这个问题是最糟糕的。

采用转型方法的答案的一个例子是:

$("[lang='fa']").find("*").andSelf().contents().each(function() {
if (this.nodeType === 3) 
{
this.nodeValue = this.nodeValue.replace(/d/g, function(v) {
return String.fromCharCode(v.charCodeAt(0) + 0x0630);
});
}
});

注意:代码取自 VisioN 的第二个 jsFiddle。 如果这是此答案中唯一您喜欢的部分,请确保您对 VisioN 的答案投赞成票,而不是我的答案!!:-)

这有两个问题:

  1. 它弄乱了 DOM,因此可能会破坏过去有效的代码,假设它会找到"标准"形式的数字(使用数字"0"到"9")。 请参阅此处的问题: http://jsfiddle.net/bKEbR/10/例如,如果您有一个字段包含用户输入的某些整数的总和,那么当您尝试获取其值时,您可能会感到惊讶......
  2. 它没有解决input(和textarea)元素内部发生的事情的问题。 如果输入字段初始化为"42",它将零售该值。 这可以很容易地解决,但是还有实际输入的问题......人们可以决定在字符出现时更改字符,在字符更改时转换值,依此类推。如果进行了这种转换,则客户端和服务器端都需要准备好处理不同类型的数字。 在Javascript,jQuery甚至Globalize(客户端)和 ASP.NET,PHP等(服务器端)中开箱即用的东西如果输入非标准格式的数字就会中断......

稍微全面的解决方案(还要注意输入/文本区域元素,包括它们的初始值和用户输入)可能是:

//before the DOM change, test1 holds a numeral parseInt can understand
alert("Before: test holds the value:" +parseInt($("#test1").text()));
function convertNumChar(c) {
return String.fromCharCode(c.charCodeAt(0) + 0x0630);
}
function convertNumStr(s) {
return s.replace(/d/g, convertNumChar);
}
//the change in the DOM
$("[lang='fa']").find("*").andSelf().contents()
.each(function() {
if (this.nodeType === 3)        
this.nodeValue = convertNumStr(this.nodeValue);      
})
.filter("input:text,textarea")
.each(function() {
this.value = convertNumStr(this.value)
})
.change(function () {this.value = convertNumStr(this.value)});      
//test1 now holds a numeral parseInt cannot understand
alert("After: test holds the value:" +parseInt($("#test1").text()))

完整的jsFiddle可以在这里找到:http://jsfiddle.net/bKEbR/13/

不用说,这只能部分解决上述问题。 客户端和/或服务器端代码必须识别非标准数字,并将它们适当地转换为标准格式或其实际值。

这不是一个简单的问题,几行javascript就能解决。 这只是这种可能转换的最简单情况,因为需要应用简单的字符到字符映射才能从一种形式的数字转到另一种形式的数字。


另一种基于外观的方法:

基于 Cufon的解决方案(矫枉过正、非向后兼容(需要画布)等):可以相对容易地调整像 Cufon 这样的库来执行设想的操作。 Cufon 可以执行其操作并在画布对象上绘制字形,除了调整将确保当元素具有特定属性时,将使用所需的字形而不是通常选择的字形。 Cufon 和其他此类库倾向于向 DOM 添加元素并更改现有元素的外观,但不触及其文本,因此转换方法的问题不应适用。 事实上,有趣的是,虽然(调整后的)Cufon 就整体 DOM 而言提供了一个明显的转型批准,但就其心态而言,它是一个基于外观的解决方案;我称之为混合解决方案。

替代混合解决方案:使用阿拉伯语内容创建新的 DOM 元素,隐藏旧元素,但保留其 ID 和内容不变。 将阿拉伯语内容元素与其相应的隐藏元素同步。


让我们试着跳出框框思考(框是当前的Web标准)。

某些字符是唯一的事实并不意味着它们无关。 此外,这并不一定意味着它们的区别在于外观。 例如,"a"和"A"是同一个字母;在某些情况下,它们被认为是相同的,而在另一些情况下则被认为是不同的。 有了Unicode(以及之前的ASCII和ISO-Latin-1等)中的区别意味着需要一些努力来克服它。 CSS 提供了一种快速简便的方法来更改字母的大小写。 例如,body {text-transform:uppercase}会将页面正文文本中的所有字母转换为大写。 请注意,这也是外观更改而不是转换的情况:body 元素的 DOM 不会改变,只是它的渲染方式。

注意:如果CSS支持类似numerals-transform: 'ar'的东西,那可能是这个问题的理想答案。

但是,在我们急于告诉 CSS 委员会添加此功能之前,我们可能需要考虑这意味着什么。 在这里,我们正在解决一个小问题,但他们必须处理大局。

输出: 如果给出适当的参数而不是"ar",这种数字转换功能是否允许"10"(2个字符)显示为十(中文,简单),拾(中文,复杂),X(拉丁)(所有1个字符)等?

输入: 这个数字转换功能会将"十"(中文,简单)更改为阿拉伯等价物,还是只是针对"10"? 它会以某种方式巧妙地检测到"MMXI"(2012 年的拉丁数字)是一个数字而不是一个单词并相应地转换它吗?

数字表示的问题并不像人们想象的那么简单。


那么,这一切给我们留下了什么:

  1. 没有简单的基于演示的解决方案。 如果将来出现一个,它将不向后兼容。
  2. 此时此地可以有一个转换性的"解决方案",但即使像我所做的那样也适用于表单元素(http://jsfiddle.net/bKEbR/13/),服务器端和客户端也需要意识到所使用的非标准格式。
  3. 可能存在复杂的混合解决方案。 它们很复杂,但在某些情况下提供了基于表示的方法的一些优点。

CSS解决方案会很好,但实际上,当人们看到涉及其他数字系统(与标准系统之间的琐碎转换较少),小数点,符号等的大局时,问题又大又复杂。

归根结底,我认为现实且向后兼容的解决方案将是 Globalize 的扩展(和服务器端等效项),可能带有一些额外的代码来处理用户输入。 这个想法是,这不是字符级别的问题(因为一旦你考虑大局,它就不是),并且必须以与千和小数分隔符的差异相同的方式处理它:作为格式化/解析问题。

我想最好的方法是使用regexp通过向需要不同数字集的div 添加class name来搜索应该更改哪些数字字符。

你可以相当容易地使用jQuery来做到这一点。

斯菲德尔演示


编辑:如果你不想使用变量,那么看看这个修改后的演示:

杰斯菲德尔演示 2

我一直在研究一种通用的网页本地化技术,它不仅仅是数字(它类似于.po文件)

本地化文件很简单(如果需要,字符串可以包含 html)

/* Localization file - save as document_url.lang.js ... index.html.en.js: */
items=[
{"id":"string1","value":"Localized text of string1 here."},
{"id":"string2", "value":"۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ "}
];
rtl=false; /* set to true for rtl languages */

这种格式对于翻译人员(或机械土耳其人)来说很有用

和基本页面模板

<html><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head><title>My title</title>
<style>.txt{float:left;margin-left:10px}</style>
</head>
<body onload='setLang()'>
<div id="string1" class="txt">This is the default text of string1.</div>
<div id="string2" class="txt">0 1 2 3 4 5 6 7 8 9 </div>
</body></html>
<script>
function setLang(){
for(var i=0;i<items.length;i++){
term=document.getElementById(items[i].id)
if(term)term.innerHTML=items[i].value
if(rtl){  /* for rtl languages */ 
term.style.styleFloat="right"
term.style.cssFloat="right"
term.style.textAlign="right"
}
}
}
var lang=navigator.userLanguage || navigator.language;
var script=document.createElement("script");
script.src=document.URL+"-"+lang.substring(0,2)+".js"
var head = document.getElementsByTagName('head')[0]
head.insertBefore(script,head.firstChild)
</script>

我试图保持它非常简单,但涵盖尽可能多的语言环境,因此可能需要额外的 css(我不得不承认缺乏对 rtl 语言的接触,因此可能需要设置更多样式)

我确实有字体检查代码,如果您知道哪些字体可以很好地支持您的字符代码,这将很有用

function hasFont(f){
var s=document.createElement("span")
s.style.fontSize="72px"
s.innerHTML="MWMWM"
s.style.visibility="hidden"
s.style.fontFamily=[(f=="monospace")?'':'monospace','sans-serif','serif']
document.body.appendChild(s)
var w=s.offsetWidth
s.style.fontFamily=[f,'monospace','sans-serif','serif']
document.body.lastChild=s
return s.offsetWidth!=w
}

用法:if(hasFont("myfont"))myelement.style.fontFamily="myfont"

一个新的(迄今为止)简单的JS解决方案是使用Intl.NumberFormat。它支持数字本地化、格式变体以及本地货币(有关更多示例,请参阅文档)。

使用一个与 MDN 自己的非常相似的例子:

const val = 1234567809;
console.log('Eastern Arabic (Arabic-Egyptian)', new Intl.NumberFormat('ar-EG').format(val));
console.log('Persian variant (Farsi)',new Intl.NumberFormat('fa').format(val));
console.log('English (US)',new Intl.NumberFormat('en-US').format(val));

Intl.NumberFormat 似乎也支持字符串数值,并指示它何时不是本地语言中的数字。

const val1 = '456';
const val2 = 'Numeric + string example, 123';
console.log('Eastern Arabic', new Intl.NumberFormat('ar-EG').format(val1));
console.log('Eastern Arabic', new Intl.NumberFormat('ar-EG').format(val2));
console.log('Persian variant',new Intl.NumberFormat('fa').format(val1));
console.log('Persian variant',new Intl.NumberFormat('fa').format(val2));
console.log('English',new Intl.NumberFormat('en-US').format(val1));
console.log('English', new Intl.NumberFormat('en-US').format(val2));

对于区域设置标识符(传递给指示语言环境的构造函数NumberFormat字符串),我尝试了上面的值,它们似乎很好。我尝试查找所有可能值的列表,并通过 MDN 遇到了这个文档和这个可能有帮助的列表。

我不熟悉SEO,因此不确定这如何回答这部分问题。

你可以试试这个: 这是CSS源代码:

@font-face
{
font-family: A1Tahoma;
src: url(yourfont.eot) format('eot')
, url(yourfont.ttf) format('truetype')
, url(yourfont.woff) format('woff')
, url(yourfont.svg) format('svg');
}
p{font-family:A1Tahoma; font-size:30px;}  

这是HTML代码:

<p>سلام به همه</p>
<p>1234567890</p>  

请记住,4种字体类型用于任何浏览器,如IE,FIREFOX等。
"Salam Reza ,to Mituni in karo anjam bedi ta un fonte dekhaheto be site ezafe koni。

我创建了一个jquery插件,可以将西方阿拉伯数字转换为东方数字(仅限波斯语)。但它可以扩展以将数字转换为任何所需的数字系统。我的jQuery插件有两个优点:

  1. 正确检测和转换子节点中的数字。
  2. 适当地检测和转换点字符。

您可以从 github 克隆此插件。 我的插件代码:

(function( $ ){
$.fn.persiaNumber = function() {
var groupSelection = this;
for(i=0; i< groupSelection.length ; i++){
var htmlTxt = $(groupSelection[i]).html();
var trueTxt = convertDecimalPoint(htmlTxt);
trueTxt = convertToPersianNum(trueTxt);
$(groupSelection[i]).html(trueTxt);
}
function convertToPersianNum(htmlTxt){
var otIndex = htmlTxt.indexOf("<");
var ctIndex = htmlTxt.indexOf(">");
if(otIndex == -1 && ctIndex == -1 && htmlTxt.length > 0){
var trueTxt = htmlTxt.replace(/1/gi, "۱").replace(/2/gi, "۲").replace(/3/gi, "۳").replace(/4/gi, "۴").replace(/5/gi, "۵").replace(/6/gi, "۶").replace(/7/gi, "۷").replace(/8/gi, "۸").replace(/9/gi, "۹").replace(/0/gi, "۰");
return trueTxt;
}
var tag = htmlTxt.substring(otIndex,ctIndex + 1);
var str = htmlTxt.substring(0,otIndex);
str = convertDecimalPoint(str);
str = str.replace(/1/gi, "۱").replace(/2/gi, "۲").replace(/3/gi, "۳").replace(/4/gi, "۴").replace(/5/gi, "۵").replace(/6/gi, "۶").replace(/7/gi, "۷").replace(/8/gi, "۸").replace(/9/gi, "۹").replace(/0/gi, "۰");
var refinedHtmlTxt = str + tag;
var htmlTxt = htmlTxt.substring(ctIndex + 1, htmlTxt.length);
if(htmlTxt.length > 0 && otIndex != -1 || ctIndex != -1){
var trueTxt = refinedHtmlTxt;
var trueTxt =  trueTxt + convertToPersianNum(htmlTxt); 
}else{
return refinedHtmlTxt+ htmlTxt;
}
return trueTxt;         
}
function convertDecimalPoint(str){
for(j=1;j<str.length - 1; j++){
if(str.charCodeAt(j-1) > 47 &&  str.charCodeAt(j-1) < 58 && str.charCodeAt(j+1) > 47 &&  str.charCodeAt(j+1) < 58 && str.charCodeAt(j) == 46)
str = str.substring(0,j) + '٫' + str.substring(j+1,str.length);
}
return str;
}
};
})( jQuery );

http://jsfiddle.net/VPWmq/2/

您可以通过以下方式转换数字:

const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
const number = 44653420;
convertedNumber = String(number).replace(/d/g, function(digit) {
return persianDigits[digit]
})
console.log(convertedNumber) // ۴۴۶۵۳۴۲۰

如果有人正在寻找使用以下代码转换方法本地化为孟加拉语号码:

$("[lang='bang']").text(function(i, val) {
return val.replace(/d/g, function(v) {
return String.fromCharCode(v.charCodeAt(0) + 0x09B6);
});
});

您也可以访问此处查看孟加拉语的ASCII十六进制码的UNICODE

最新更新