网络应用程序中的数字本地化

27

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

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>

我该如何只使用客户端技术(HTML,CSS,JS)来实现此操作?解决方案不应对页面的SEO得分产生负面影响。
请注意,在Windows文本框(例如运行)中,数字根据周围文本的语言正确显示。
另请参阅:桌面应用程序中的数字本地化 注意:使用此PHP包https://github.com/salarmehr/cosmopolitan可以轻松在后端完成数字本地化。

1
https://dev59.com/zXI-5IYBdhLWcg3wxrqQ - jjj
http://snipplr.com/view/52525/ - Fivell
请参阅此帖子 - Mahozad
9个回答

18

这里有一种利用代码转移的方法:

// 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在线字符表


因此,您在答案中的旧链接应该被替换为评论中提到的新版本。 - Handsome Nerd
1
不一定是Reza,有其他选择也很好,这取决于其他人是否需要另一种方法。大多数博客网站的教程实际上都在评论部分中有“修复”方法。;-) - arttronics
我应该改变什么来将字符转换为孟加拉语? - Farhan Ibn Wahid
你的回答对我非常有用。我有一个问题,是否有任何方法将数字本地化为英语? - hossein fti
@hosseinfti 使用相同的代码,但替换为 0x06300x06C0 - VisioN
显示剩余3条评论

7

需要决定的是这个问题涉及外观还是转换。还需要确定这是否涉及字符级语义或数字表示。以下是我的想法:


如果Unicode没有将数字符号分开,那么这个问题就会具有完全不同的语义。此时,显示适当的字形只是使用适当字体的问题。另一方面,如果不更改字体,我无法像下面那样简单地将不同的字符写出。(情况并非完美,因为字体不一定覆盖16位Unicode集的整个范围,更不用说32位Unicode集了。)

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

假设我们接受Unicode语义,即 '9','٩'和'۹'是不同的字符,我们可以得出结论,问题不在于外观(这是CSS所涵盖的范畴),而在于转换-稍后我们会讨论一些想法,现在假设这是情况。 在关注字符级语义时,情况与字母表和字母发生的情况并没有太大差异。例如,希腊字母'α'和拉丁字母'a'被认为是不同的,即使拉丁字母表与在欧布阿使用的希腊字母表几乎相同。也许更具戏剧性的是,对应的大写变体'Α'(希腊)和'A'(拉丁)在几乎支持两种脚本的所有字体中都是视觉上相同的,但在Unicode方面是不同的。
规则已经说明,现在看看如何回答问题,方法是忽略它们,特别是忽略(字符级)Unicode语义。
(可怕、恶心和不向后兼容的)解决方案:使用将“0”到“9”映射到所需字形的字体。我不知道有这样的字体。您必须使用@font-face和一些已被适当破解以实现您想要的功能的字体。不用说,我对这个解决方案并不特别喜欢。但是,这是我所知道的唯一一个简单解决问题的方法,而不会在服务器或客户端上更改字符代码。 (严格来说,我下面提出的Cufon解决方案也不会更改字符代码,但是它所做的,将文本绘制到画布中,要复杂得多,并且需要调整开源代码)。
注意:任何转换解决方案,即将“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 等(服务器端)提供的内容,如果输入非标准格式的数字,将会出现问题...
稍微更全面的解决方案(还考虑了 input/textarea 元素,包括它们的初始值和用户输入)可能是:
//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委员会添加此功能之前,我们可能需要考虑一下这意味着什么。在这里,我们正在解决一个微小的问题,但他们必须处理整体情况。
输出: 如果给定适当的参数,这种数字转换功能是否允许'10'(2个字符)显示为十(中文,简体),拾(中文,繁体),X(拉丁)(所有1个字符)等?
输入: 这种数字转换功能是否会将“十”(中文,简体)更改为其阿拉伯语等价物,还是仅针对“10”?它是否会聪明地检测到“MMXI”(2012年的拉丁数字)是一个数字而不是单词,并相应地进行转换?
数字表示的问题并不像看起来那么简单,这需要我们认真考虑。
所以,这一切都给了我们什么启示:
  1. 没有简单的基于表现的解决方案。即使未来出现了这样的解决方案,也将无法向后兼容。
  2. 可以进行转换“解决方案”,但即使像我一样使其与表单元素一起工作(http://jsfiddle.net/bKEbR/13/),需要服务器端和客户端意识到使用的非标准格式。
  3. 可能存在复杂的混合解决方案。它们是复杂的,但在某些情况下提供了基于表现方法的某些优势。
CSS解决方案是不错的,但实际上,当我们考虑涉及其他数字系统(从和标准系统的转换不那么琐碎)的大局面、小数点、符号等时,问题就变得更加复杂。
最终我认为现实且向后兼容的解决方案将是扩展Globalize(和服务器端等效项),可能需要一些额外的代码来处理用户输入。思想是这不是一个字符级的问题(因为一旦你考虑到整个情况,它就不是),而且它将被视为与千位分隔符和小数点的差异一样处理:作为格式化/解析问题。

小提示:我对中文不是很了解,但是“它是否会聪明地检测到“MMXI”(2012年的拉丁数字)是一个数字而不是一个单词,并相应地进行转换?”这实际上是另一种数字系统符号,与任何语言都没有关系,因此将数字转换为另一种数字系统的问题——是一个单独的问题,而不是逐个字符的转换(请注意,“text-transform”在中文字符上无效,因为这些字符没有大写表示形式)。 - impulsgraw
此外,我认为应该提出此功能的请求,因为它对于Web开发人员和用户来说将发挥极大的作用。首次,它只能实现西方和东方阿拉伯数字,例如:numerals-transform: unset | we-ar | ea-ar;,然后可以扩展到其他可能的变体(unset表示混合数字输入的可能性)。 - impulsgraw
也许值得实现这个功能只是为了 JavaScript 表示,因此检索包含东阿拉伯数字的输入值将自动由浏览器转换为西阿拉伯数字,因此 parseInt 将返回一个有效数字而不是 NaN。 - impulsgraw

2
我想最好的方法是使用正则表达式来搜索哪些数字字符应该通过添加类名来更改,以便将不同的数字集添加到需要的 div 中。
您可以很容易地使用 jQuery 完成这个操作。 jsfiddle DEMO
编辑:如果您不想使用变量,那么请查看此修订版演示: jsfiddle DEMO 2

注意:我的答案方法会删除所有子数字元素的动态绑定事件。请参考:jsFiddle - arttronics
1
回答你的问题,这是因为你执行了 .html(val) 方法,它替换了整个标记。它的一个缺点是删除所有非默认 DOM 元素属性(包括绑定事件)。 - VisioN
好的,现在我完全明白了,VisioN。感谢你的提醒。我添加了更多的jQuery代码,并且把所有可能用到的东西都加进去了,最终得到了这个jsFiddle,解决了OP的问题,但是代价是增加了很多标记...不过我已经学到了教训。再次感谢。 - arttronics

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"

如果有字体“myfont”,就将“myelement”元素的字体族设置为“myfont”。

1
@arttronics - 谢谢,感谢您的编辑,我也进一步整理了一下。 - technosaurus
好的观点。我不确定۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹是否真的应该是9876543210或0123456789。如果有人发言,我会很乐意更新它。 - technosaurus
۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ = 0123456789 - Handsome Nerd

1
一个新的(截至目前)且简单的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,因此不确定它如何回答问题的这一部分。

0
你可以尝试这个: 这是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,你可以完成这项工作,以便将那个字体添加到网站中。”


1
谢谢,问题不在于字体,即使设置了波斯语Unicode字体也无法解决问题。 - Handsome Nerd

0
我创建了一个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/


您的方法删除了所有动态绑定事件:http://jsfiddle.net/VPWmq/3/. - VisioN
是的,我的方法移除了所有动态绑定的事件。但是在 DOM 更新后,我们可以使用 live 方法。[http://jsfiddle.net/VPWmq/4/] - Mohsen Hosseinalizadeh
1
现在 live 已经被弃用了。在事件委托模式下最好使用 on - VisioN

0

你可以用以下方法将数字进行转换:

const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
const number = 44653420;

convertedNumber = String(number).replace(/\d/g, function(digit) {
    return persianDigits[digit]
})
console.log(convertedNumber) // ۴۴۶۵۳۴۲۰

0

如果有人正在寻找使用this代码转换方法本地化成孟加拉数字:

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接