使用浏览器时,我如何知道操作系统使用哪个小数点?

我在开发一个网络应用程序。

我需要正确显示一些十进制数据,以便它可以被复制和粘贴到某个 GUI应用程序,不在我的控制之下。

图形用户界面应用程序是区域敏感的,它只接受系统中设置的正确小数点。

我可以从 Accept-Language中猜出小数点,95% 的情况下我的猜测都是正确的,但是有时候会失败。

有没有什么方法可以在服务器端(最好是我可以收集统计数据的服务器端)或客户端进行?

更新:

这项任务的关键就是自动完成。

事实上,这个 webapp 是一种与传统 GUI 的在线接口,可以帮助正确填写表单。

使用它的用户大多不知道小数点是什么。

Accept-Language解决方案已经实现并且可以工作,但是我想对它进行改进。

更新2:

我需要检索一个非常具体的设置: 小数点设置在 Control Panel / Regional and Language Options / Regional Options / Customize

我处理四种操作系统:

  1. 以逗号为 DS 的俄罗斯 Windows (80%)。
  2. 有一段时间为 DS 的英文视窗(15%)。
  3. 俄罗斯视窗,有一段时间作为 DS,使英语应用程序写得很差(4%)。
  4. 用逗号作为 DS 的英文 Windows 操作系统可以让写得很差的俄语应用程序工作(1%)。

所有100% 的客户都在俄罗斯,而遗留应用程序处理的是俄罗斯政府发放的表格,因此要求一个国家将得到100% 的俄罗斯,而 GeoIP 将得到80% 的俄罗斯和20% 的其他(不正确)答案。

57750 次浏览

"Is there any way to do it on server side (preferably, so that I can collect statistics), or on client side?"

No you can't. That GUI is looking at some user or machine specific settings. First, you probably do not know at what settings this UI is looking. Second, with a webapplication you will probably not be able to check these settings (clientside --> Javacsript).

Ask the user, do not guess. Have a setting for it in your web application.

Edited to add:

I think it is ok to guess the default setting that works ok, say, 95% of the time. What I meant was that the user should still be able to override whatever guesses the software made. I've been frustrated too many times already when a software tries to be too smart and does not allow to be corrected.

I think you have to rely on JavaScript to give you the locale settings.
But apparently JS doesn't have direct access to this information.
I see Dojo Toolkit relies on an external database to find the locale information, although it might not take in account setting changes, for example.
Another workaround I see is to have a small silent Java applet that query this information from the system, and JavaScript to get it out of Java.
I can give more information if you don't know how to do it (if you want to go this convoluted route, of course).

[EDIT] So I updated my knowledge of localization support in Java...
Unlike what I thought originally, you won't have directly the decimal separator or thousand separator characters directly, like you would do with line separator or path separator: instead Java offers APIs to format the numbers or dates you provide.
Somehow, it makes sense: in Europe you often put the currency symbol after the number, some countries (India?) have a more complex rule to separate digits, etc.

Another thing: Java correctly finds the current locale from the system, but doesn't take information from there (perhaps for above reasons). Instead it uses its own set of rules. So if you have a Spanish locale where you replaced decimal separator with an exclamation sign, Java won't use it (but perhaps neither your application, anyway...).

So I am writing an applet exposing a service (functions) to JavaScript, allowing to format numbers to the current locale. You can use it as such, using JavaScript to format numbers on the browser. Or you can just feed it with some sample number and extract the symbols from there, using them locally or feeding them back to the server.

I finish and test my applet and post it there soon.

I can guess the decimal separator from Accept-Language and the guess will be correct in 95% cases, but sometimes it fails.

This is IMO the best course of action. In order to handle the failures, add a link to set it manually next to the display area.

Another possible solution: You could use something like GeoIP (example in PHP) to determine the user's location and decide based on these information.

Even if you knew what locale this "GUI Application" is running under, you still have to figure out how it is getting the current locale, and how it is determining the decimal separator.

i don't know how it is done on a Mac, but on Windows applications are supposed to interrogte the user's preferences set via the Control Panel. It's quite possible this mystery applicaiton is ignoring those settings, and using their own internal setup instead.

Or perhaps they're taking the current locale, and inferring the rest, rather than being told.

Even then, in english, numbers are given in groups of 3 digits, with a comma separating the groups. i.e.:

5,197,359,078

Unless the number was an integer that contains a phone number:

519-735-9078

Unless of course the number was an integer that contains an account number:

5197359078

In which case, you're back to hard-coded overridden logic.

Edit: Removed currency example, since currency has its own formatting rules.

OK, I have something to show, more a proof of concept than a finished product, but because of lack of precise specifications, I leave it this way (or I will over-engineer it). I post in a separate message because it will be a bit long. I took the opportunity to try a bit more jQuery...

The Java code: GetLocaleInfo.java

import java.applet.*;
import java.util.Locale;
import java.text.*;


public class GetLocaleInfo extends Applet
{
Locale loc;
NumberFormat nf;
NumberFormat cnf;
NumberFormat pnf;


// For running as plain application
public static void main(String args[])
{
final Applet applet = new GetLocaleInfo();
applet.init();
applet.start();
}


public void init() // Applet is loaded
{
// Use current locale
loc = Locale.getDefault();
nf = NumberFormat.getInstance();
cnf = NumberFormat.getCurrencyInstance();
pnf = NumberFormat.getPercentInstance();
}


public void start() // Applet should start
{
// Following output goes to Java console
System.out.println(GetLocaleInformation());
System.out.println(nf.format(0.1));
System.out.println(cnf.format(1.0));
System.out.println(pnf.format(0.01));
}


public String GetLocaleInformation()
{
return String.format("Locale for %s: country=%s (%s / %s), lang=%s (%s / %s), variant=%s (%s)",
loc.getDisplayName(),
loc.getDisplayCountry(),
loc.getCountry(),
loc.getISO3Country(),


loc.getDisplayLanguage(),
loc.getLanguage(),
loc.getISO3Language(),


loc.getDisplayVariant(),
loc.getVariant()
);
}


public String FormatNumber(String number)
{
double value = 0;
try
{
value = Double.parseDouble(number);
}
catch (NumberFormatException nfe)
{
return "!";
}
return nf.format(value);
}


public String FormatCurrency(String number)
{
double value = 0;
try
{
value = Double.parseDouble(number);
}
catch (NumberFormatException nfe)
{
return "!";
}
return cnf.format(value);
}


public String FormatPercent(String number)
{
double value = 0;
try
{
value = Double.parseDouble(number);
}
catch (NumberFormatException nfe)
{
return "!";
}
return pnf.format(value);
}
}

An example of HTML page using the above applet: GetLocaleInfo.html

<!-- Header skipped for brevity -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js"></script>
<script type="text/javascript">
var applet;
$(document).ready(function()
{
applet = document.getElementById('LocaleInfo');
$('#Results').text(applet.GetLocaleInformation());
});
</script>
<script type="text/javascript">
function DoFormatting()
{
$('table.toFormat').each(function()
{
var table = $(this);
$('td', table).each(function(cellId)
{
var val = $(this);
if (val.is('.number'))
{
val.text(applet.FormatNumber(val.text()));
}
else if (val.is('.currency'))
{
val.text(applet.FormatCurrency(val.text()));
}
else if (val.is('.percent'))
{
val.text(applet.FormatPercent(val.text()));
}
});
});
}
</script>
</head>
<body>
<div id="Container">
<p>Page to demonstrate how JavaScript can get locale information from Java</p>
<div id="AppletContainer">
<object classid="java:GetLocaleInfo.class"
type="application/x-java-applet" codetype="application/java"
name="LocaleInfo" id="LocaleInfo" width="0" height="0">
<param name="code" value="GetLocaleInfo"/>
<param name="mayscript" value="true"/>
<param name="scriptable" value="true"/>
<p><!-- Displayed if object isn't supported -->
<strong>This browser does not have Java enabled.</strong>
<br>
<a href="http://java.sun.com/products/plugin/downloads/index.html" title="Download Java plug-in">
Get the latest Java plug-in here
</a> (or enable Java support).
</p>
</object>
</div><!-- AppletContainer -->
<p>
Click on the button to format the table content to the locale rules of the user.
</p>
<input type="button" name="DoFormatting" id="DoFormatting" value="Format the table" onclick="javascript:DoFormatting()"/>
<div id="Results">
</div><!-- Results -->
<table class="toFormat">
<caption>Synthetic View</caption>
<thead><tr>
<th>Name</th><th>Value</th><th>Cost</th><th>Discount</th>
</tr></thead>
<tbody>
<tr><td>Foo</td><td class="number">3.1415926</td><td class="currency">21.36</td><td class="percent">0.196</td></tr>
<tr><td>Bar</td><td class="number">159263.14</td><td class="currency">33</td><td class="percent">0.33</td></tr>
<tr><td>Baz</td><td class="number">15926</td><td class="currency">12.99</td><td class="percent">0.05</td></tr>
<tr><td>Doh</td><td class="number">0.01415926</td><td class="currency">5.1</td><td class="percent">0.1</td></tr>
</tbody>
</table>
</div><!-- Container -->
</body>
</html>

Tested on Firefox 3.0, IE 6, Safari 3.1 and Opera 9.50, on Windows XP Pro SP3. It works without problem with the first two, on Safari I have a strange error after init() call:

java.net.MalformedURLException: no protocol:
at java.net.URL.<init>(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at sun.plugin.liveconnect.SecureInvocation.checkLiveConnectCaller(Unknown Source)
at sun.plugin.liveconnect.SecureInvocation.access$000(Unknown Source)
at sun.plugin.liveconnect.SecureInvocation$2.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.plugin.liveconnect.SecureInvocation.CallMethod(Unknown Source)

but it still works.

I can't get it work with Opera: the applet loads correctly, as I can see the trace of init() call in the Java console, I have no errors when JavaScript calls the Java functions (except if I add and call a method getting a JSObject parameter, curiously), but the Java functions are not called (I added trace of the calls).
I believe Liveconnect works in Opera, but I don't see yet how. I will research a bit more.
[Update] I removed references to non-existing jar file (which doesn't stop other browsers) and I got a trace of the calls, but it doesn't update the page.
Mmm, if I do alert(applet.GetLocaleInformation()); I got the information, so it might be a jQuery issue.

Here is a simple JavaScript function that will return this information. Tested in Firefox, IE6, and IE7. I had to close and restart my browser in between every change to the setting under Control Panel / Regional and Language Options / Regional Options / Customize. However, it picked up not only the comma and period, but also oddball custom things, like the letter "a".

function whatDecimalSeparator() {
var n = 1.1;
n = n.toLocaleString().substring(1, 2);
return n;
}

function whatDecimalSeparator() {
var n = 1.1;
n = n.toLocaleString().substring(1, 2);
return n;
}


console.log('You use "' + whatDecimalSeparator() + '" as Decimal seprator');

Does this help?

function getDecimalSeparator() {
//fallback
var decSep = ".";


try {
// this works in FF, Chrome, IE, Safari and Opera
var sep = parseFloat(3/2).toLocaleString().substring(1,2);
if (sep === '.' || sep === ',') {
decSep = sep;
}
} catch(e){}


return decSep;
}

Why not

console.log(0.1.toLocaleString().replace(/\d/g, ''));

Using other people answers I compiled the following decimal and thousand separators utility functions:

var decimalSeparator = function() {
return (1.1).toLocaleString().substring(1, 2);
};
var thousandSeparator = function() {
return (1000).toLocaleString().substring(1, 2);
};

Enjoy!

Retrieving separators for the current or a given locale is possible using Intl.NumberFormat#formatToParts.

function getDecimalSeparator(locale) {
const numberWithDecimalSeparator = 1.1;
return Intl.NumberFormat(locale)
.formatToParts(numberWithDecimalSeparator)
.find(part => part.type === 'decimal')
.value;
}

It only works for browsers supporting the Intl API. Otherwise it requires an Intl polyfill

Examples:

> getDecimalSeparator()
"."
> getDecimalSeparator('fr-FR')
","

Bonus:

We could extend it to retrieve either the decimal or group separator of a given locale:

function getSeparator(locale, separatorType) {
const numberWithGroupAndDecimalSeparator = 1000.1;
return Intl.NumberFormat(locale)
.formatToParts(numberWithGroupAndDecimalSeparator)
.find(part => part.type === separatorType)
.value;
}

Examples:

> getSeparator('en-US', 'decimal')
"."
> getSeparator('en-US', 'group')
","
> getSeparator('fr-FR', 'decimal')
","
> getSeparator('fr-FR', 'group')
" "

Similar to other answers, but compressed as a constant:

const decimal=.1.toLocaleString().substr(1,1);      //returns "." in Canada

Also, to get the thousands separator:

const thousands=1234..toLocaleString().substr(1,1);   //returns "," in Canada

Just place the code at the top of your JS and then call as required to return the symbol.


For example (where I live), to remove commas from "1,234,567":

console.log( "1,234,567".replaceAll(thousands,"") ); //prints "1234567" to console.

Is there any way to do it on server side (preferably, so that I can collect statistics), or on client side?

from Server side. That could get decimal separator from system by (.NET)

string x = CultureInfo.CurrentCulture.NumberFormat.NumberDsecimalSeparator;

The rest of work is check delimiter for exporting which is different from x comma (",") or semicolon (";") in case csv export