Как в браузере узнать, какой десятичный разделитель использует операционная система?

82

Я разрабатываю веб-приложение.

Мне нужно правильно отобразить некоторые десятичные данные, чтобы их можно было скопировать и вставить в определенное GUIприложение, которое не находится под моим контролем.

Приложение с графическим интерфейсом зависит от языкового стандарта и принимает только правильный десятичный разделитель, установленный в системе.

Я могу предположить десятичный разделитель Accept-Languageи предположение будет правильным в 95% случаев, но иногда это не удается.

Есть ли способ сделать это на стороне сервера (желательно, чтобы я мог собирать статистику) или на стороне клиента?

Обновить:

Вся суть задачи в том, чтобы делать это автоматически.

Фактически, это веб-приложение представляет собой своего рода онлайн-интерфейс к устаревшему графическому интерфейсу, который помогает правильно заполнять формы.

Пользователи, которые его используют, в основном не имеют представления о том, что такое десятичный разделитель.

Accept-LanguageРешение реализовано и работает, но я хотел бы, чтобы улучшить его.

Обновление2:

Мне нужно получить очень конкретную настройку: установленный десятичный разделитель Control Panel / Regional and Language Options / Regional Options / Customize.

Я имею дело с четырьмя типами операционных систем:

  1. Русская винда с запятой как DS (80%).
  2. Английская Windows с точкой в ​​DS (15%).
  3. Русская Windows с точкой в ​​качестве DS для работы плохо написанных англоязычных приложений (4%).
  4. Английская Windows с запятой в качестве DS для работы плохо написанных русских приложений (1%).

Все 100% клиентов находятся в России, и устаревшее приложение связано с формами, выпущенными российским правительством, поэтому запрос страны даст 100% Российской Федерации, а GeoIP - 80% Российской Федерации и 20% других (неверных). ответы.

Quassnoi
источник

Ответы:

129

Вот простая функция JavaScript, которая вернет эту информацию. Протестировано в Firefox, IE6 и IE7. Мне приходилось закрывать и перезапускать браузер между каждым изменением настроек в Панели управления / Язык и региональные стандарты / Региональные параметры / Настройка. Однако он уловил не только запятую и точку, но и необычные нестандартные вещи, например, букву «а».

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

Это помогает?

Крис Нильсен
источник
3
У меня это сработало в Firefox и IE8, но не в Google Chrome. У меня нет Opera.
Мэтью Талберт,
@Matthew - у меня работал в Chrome. @Quassnoi - Я не понимаю, что означает этот последний комментарий. Если он говорит, что функция не работает, какое значение имеет то, что этот человек знает?
Matchu
Осторожно! В Chrome toLocaleString работает правильно, только если вызывается непосредственно для Number. В моей системе: [1.1,1.2] .toLocaleString () -> «1.1,1.2» | (1.1) .toLocaleString () -> "1,1"
Тарнай Калман
1
Функция не работает в языковых стандартах, которые используют более одного символа для своих DecimalSeparator(например, ,,). Windows LOCALE_SDECIMALпозволяет десятичному разделителю содержать до трех символов. (Вот почему он не работает на моем ПК). В этом случае лучше использовать Accept-Languageбраузер. Что по-прежнему не учитывает возможность указывать свои собственные, DecimalSeparatorнапример\o/
Ян Бойд
4
@IanBoyd прав насчет локалей со строкой из более чем одного символа в качестве десятичного разделителя, но n = /^1(.+)1$/.exec(n.toLocaleString())[1]он сделает это, и это проще, чем использование Accept-Languageзаголовка.
ygormutti
15

Получение разделителей для текущего или заданного языкового стандарта возможно с помощью Intl.NumberFormat#formatToParts.

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

Он работает только для браузеров, поддерживающих Intl API . В противном случае требуется полифил Intl

Примеры:

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

Бонус:

Мы могли бы расширить его, чтобы получить либо десятичный, либо групповой разделитель данной локали:

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

Примеры:

> getSeparator('en-US', 'decimal')
"."
> getSeparator('en-US', 'group')
","
> getSeparator('fr-FR', 'decimal')
","
> getSeparator('fr-FR', 'group')
" "
JBE
источник
В настоящее время это наиболее подходящий способ получить такую ​​информацию. BTW Intlподдерживается даже в IE 11: caniuse.com/#feat=internationalization
Константин Смолянин
3
Это не будет работать в IE 11, так как formatToParts не поддерживается.
Гаджендра Кумар
11

Спросите пользователя, а не угадайте. Настройте его в своем веб-приложении.

Отредактировано для добавления:

Я думаю, что можно угадать настройку по умолчанию, которая работает нормально, скажем, в 95% случаев. Я имел в виду, что пользователь все еще должен иметь возможность отменять любые предположения, сделанные программой. Я уже слишком много раз разочаровывался, когда программное обеспечение пыталось быть слишком умным и не позволяло исправлять.

Laalto
источник
Забавно, это была моя первая идея, но я переборщил, пытаясь сделать это автоматически ...
PhiLho
1
Плохая идея, разве что как запасной вариант. Большинство пользователей неловко нечувствительны к культуре и даже не поймут, что такое «десятичный разделитель» без объяснения (а затем будут возмущены тем, что им приходится устанавливать что-то, что «все знают»).
Майкл Боргвардт
2
@Iaalto: это сделало бы вопрос почти таким же важным, как "Минимизировать размер базы данных (рекомендуется) или Увеличить возможности поиска?"
Quassnoi 02
Что ж, это не должно быть слишком сложно. Просто позвольте пользователю выбрать страну, а затем выбрать десикмальный сепаратор и другие параметры соответственно.
7
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;
    }
Доктор Облак
источник
1
возврат к "." в случае некоторых непонятных браузеров ... в остальном это почти то же самое ...
Доктор Облак
7

Почему бы и нет

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

user3023011
источник
2
может какая-то странная локаль может пропускать ведущий ноль? Я бы предпочел одну там, на всякий случай.
Хуанги Йордан 01
5

Я могу предположить десятичный разделитель из Accept-Language, и предположение будет верным в 95% случаев, но иногда это не удается.

Это лучший курс действий ИМО. Для обработки сбоев добавьте ссылку, чтобы установить ее вручную рядом с областью отображения.

Майкл Боргвардт
источник
Как это сделать? Насколько я понимаю, вы можете использовать такую ​​библиотеку браузера, как эта github.com/dansingerman/jQuery-Browser-Language
Lime
@William: Acccept-Language, о котором говорит OP, - это HTTP-заголовок, отправленный браузером, который сообщает серверу, какой язык предпочитает пользователь, обычно это язык установки браузера или ОС.
Майкл Боргвардт
4

Используя ответы других людей, я скомпилировал следующие служебные функции десятичного разделителя и разделителя тысяч:

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

Наслаждайтесь!

Хуанги Йордан
источник
Да, я использовал этот метод как полифил для браузеров, которые не поддерживают formatToParts(Safari и IE).
Марко Боначи
1
В некоторых регионах не используется разделитель тысяч ниже 10000. Например(1000).toLocaleString("es-PE") # "1000"
Madacol
1

Я думаю, вам нужно полагаться на JavaScript, чтобы получить настройки локали.
Но, видимо, у JS нет прямого доступа к этой информации.
Я вижу, что Dojo Toolkit полагается на внешнюю базу данных для поиска информации о локали, хотя, например, он может не учитывать изменения настроек учетной записи.
Другой обходной путь, который я вижу, - это иметь небольшой тихий Java-апплет, который запрашивает эту информацию из системы, и JavaScript, чтобы получить ее из Java.
Я могу дать больше информации, если вы не знаете, как это сделать (если, конечно, вы хотите пойти по этому запутанному маршруту).

[EDIT] Итак, я обновил свои знания о поддержке локализации в Java ...
В отличие от того, что я думал изначально, у вас не будет напрямую десятичного разделителя или символов разделителя тысяч напрямую, как если бы вы делали с разделителем строк или разделителем пути: вместо этого Java предлагает API для форматирования предоставленных вами чисел или дат.
Почему-то в этом есть смысл: в Европе часто ставят символ валюты после числа, в некоторых странах (Индия?) Есть более сложное правило разделения цифр и т. Д.

Еще одна вещь: Java правильно находит текущий языковой стандарт в системе, но не берет информацию оттуда (возможно, по указанным выше причинам). Вместо этого он использует свой собственный набор правил. Поэтому, если у вас есть испанский язык, в котором вы заменили десятичный разделитель восклицательным знаком, Java не будет его использовать (но, возможно, ни ваше приложение, во всяком случае ...).

Итак, я пишу апплет, раскрывающий службу (функции) JavaScript, позволяющий форматировать числа в соответствии с текущим языковым стандартом. Вы можете использовать его как таковой, используя JavaScript для форматирования чисел в браузере. Или вы можете просто скормить ему номер образца и извлечь оттуда символы, используя их локально или отправив обратно на сервер.

Я доделываю и тестирую свой апплет и вскоре выкладываю его туда.

PhiLho
источник
@PhiLho: было бы неплохо узнать. Это веб-приложение представляет собой своего рода справочную систему, поэтому подойдет любой уродливый взлом, он не должен быть элегантным, если работает в IE, Firefox и Opera.
Quassnoi 02
1

Хорошо, мне есть что показать, скорее доказательство концепции, чем готовый продукт, но из-за отсутствия точных спецификаций я оставляю это таким образом (или я буду его чрезмерно спроектировать). Отправляю отдельным сообщением, потому что оно будет длинноватым. Я воспользовался возможностью, чтобы попробовать еще немного jQuery ...

Код Java: 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);
  }
}

Пример HTML-страницы с использованием указанного выше апплета: 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>

Протестировано в Firefox 3.0, IE 6, Safari 3.1 и Opera 9.50, в Windows XP Pro SP3. С первыми двумя работает без проблем, в Safari у меня странная ошибка после вызова init ():

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)

но он все еще работает.

Я не могу заставить его работать с Opera: апплет загружается правильно, так как я вижу трассировку вызова init () в консоли Java, у меня нет ошибок, когда JavaScript вызывает функции Java (кроме случаев, когда я добавляю и вызываю метод получение параметра JSObject, как ни странно), но функции Java не вызываются (я добавил трассировку вызовов).
Я считаю, что Liveconnect работает в Opera, но пока не понимаю, как это сделать. Я изучу еще немного.
[Обновление] Я удалил ссылки на несуществующий файл jar (который не останавливает другие браузеры), и я получил трассировку вызовов, но он не обновляет страницу.
Ммм, если я это сделаю, alert(applet.GetLocaleInformation());я получил информацию, так что это может быть проблема с jQuery.

PhiLho
источник
@PhiLho: работает, но по-прежнему некорректно считывает разделитель из Windows. Когда он видит следующее: Locale for русский (Россия): country=Россия (RU / RUS), lang=русский (ru / rus), variant= ()он использует запятую, несмотря на то, что я заменил ее точкой в ​​настройках Windows.
Quassnoi 07
Пожалуйста, прочтите мое обновление моего первого сообщения об ограничениях этой системы. Я не знаю, можем ли мы добиться большего с помощью веб-браузера, кроме, возможно, использования некоторого собственного кода.
PhiLho
1

Даже если вы знали, в какой локали работает это «приложение с графическим интерфейсом», вам все равно нужно выяснить, как она получает текущую локаль и как она определяет десятичный разделитель.

Я не знаю, как это делается на Mac, но в Windows приложения должны запрашивать настройки пользователя, установленные через панель управления. Вполне возможно, что это загадочное приложение игнорирует эти настройки и вместо этого использует свои собственные внутренние настройки.

Или, возможно, они берут текущую локаль и делают вывод об остальном, а не говорят.

Даже в этом случае на английском языке числа даются группами по 3 цифры, с запятой, разделяющей группы. то есть:

5,197,359,078

Если номер не был целым числом, содержащим номер телефона :

519-735-9078

Если, конечно, это не целое число, содержащее номер счета :

5197359078

В этом случае вы возвращаетесь к жестко запрограммированной переопределенной логике.

Изменить: удален пример валюты, так как у валюты есть собственные правила форматирования.

Ян Бойд
источник
0

Подобно другим ответам, но сжато как константа :

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

Кроме того, чтобы получить разделитель тысяч :

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

Просто разместите код в верхней части вашего JS, а затем вызовите по мере необходимости, чтобы вернуть символ.


Например (где я живу), чтобы удалить запятые из "1,234,567":

console.log( "1,234,567".replaceAll(thousands,"") ); //prints "1234567" to console.  
Ashleedawg
источник
-1

«Есть ли способ сделать это на стороне сервера (желательно, чтобы я мог собирать статистику) или на стороне клиента?»

Нет, не можешь. Этот графический интерфейс смотрит на некоторые настройки пользователя или компьютера. Во-первых, вы, вероятно, не знаете, на какие настройки смотрит этот пользовательский интерфейс. Во-вторых, с веб-приложением вы, вероятно, не сможете проверить эти настройки (на стороне клиента -> Javacsript).

Ян Бойд
источник
@RWC: Я знаю, куда смотрит приложение с графическим интерфейсом: региональные настройки Windows.
Quassnoi 02
-4

Другое возможное решение: вы можете использовать что-то вроде GeoIP (пример в PHP), чтобы определить местоположение пользователя и принять решение на основе этой информации.

окоман
источник
1
Однако необходимо разрешить пользователю переопределить это. Мой друг работает на юге Англии; компания, с которой он работает, направляет весь доступ в Интернет через прокси-сервер в Испании, поэтому GeoIP всегда показывает, что он находится за много сотен миль от его фактического местоположения.
NickFitz 02