W przypadku przeglądarki, skąd mam wiedzieć, którego separatora dziesiętnego używa system operacyjny?

82

Tworzę aplikację internetową.

Muszę poprawnie wyświetlić niektóre dane dziesiętne, aby można było je skopiować i wkleić do określonej GUIaplikacji, nad którą nie mam kontroli.

Aplikacja GUI jest wrażliwa na ustawienia regionalne i akceptuje tylko prawidłowy separator dziesiętny ustawiony w systemie.

Domyślam się, że separator dziesiętny od Accept-Languagei przypuszczenie będzie poprawne w 95% przypadków, ale czasami się nie udaje.

Czy jest jakiś sposób, aby to zrobić po stronie serwera (najlepiej, abym mógł zbierać statystyki), czy po stronie klienta?

Aktualizacja:

Celem zadania jest robienie tego automatycznie.

W rzeczywistości ta aplikacja internetowa jest rodzajem interfejsu online do starszego GUI, który pomaga poprawnie wypełniać formularze.

Użytkownicy, którzy go używają, najczęściej nie mają pojęcia, czym jest separator dziesiętny.

Accept-LanguageRozwiązanie jest wdrożony i działa, ale chciałbym, aby ją poprawić.

Aktualizacja2:

Muszę pobrać bardzo konkretne ustawienie: ustawiono separator dziesiętny Control Panel / Regional and Language Options / Regional Options / Customize.

Mam do czynienia z czterema rodzajami systemów operacyjnych:

  1. Rosyjski Windows z przecinkiem jako DS (80%).
  2. Angielski Windows z kropką jako DS (15%).
  3. Rosyjski Windows z okresem jako DS, aby źle napisane angielskie aplikacje działały (4%).
  4. Angielski Windows z przecinkiem jako DS, aby źle napisane rosyjskie aplikacje działały (1%).

Wszyscy klienci w 100% znajdują się w Rosji, a starsza aplikacja dotyczy formularzy wydanych przez rosyjski rząd, więc zapytanie o kraj da 100% Federacji Rosyjskiej, a GeoIP zapewni 80% Federacji Rosyjskiej i 20% innych (niepoprawnych) odpowiedzi.

Quassnoi
źródło

Odpowiedzi:

129

Oto prosta funkcja JavaScript, która zwróci te informacje. Przetestowano w przeglądarkach Firefox, IE6 i IE7. Musiałem zamknąć i ponownie uruchomić przeglądarkę pomiędzy każdą zmianą ustawienia w Panelu sterowania / Opcje regionalne i językowe / Opcje regionalne / Dostosuj. Jednak uwzględnił nie tylko przecinek i kropkę, ale także dziwaczne niestandardowe elementy, takie jak litera „a”.

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

czy to pomaga?

Chris Nielsen
źródło
3
To działało dla mnie w Firefoksie i IE8, ale nie w Google Chrome. Nie mam Opery.
Matthew Talbert
@Matthew - pracował dla mnie w Chrome. @Quassnoi - nie rozumiem, co oznacza ten ostatni komentarz. Jeśli mówi, że funkcja nie działa, jakie to ma znaczenie, co ta osoba wie?
Matchu
Uważaj! W przeglądarce Chrome toLocaleString działa poprawnie tylko wtedy, gdy zostanie wywołane bezpośrednio na Number. W moim systemie: [1.1,1.2] .toLocaleString () -> "1.1,1.2" | (1.1) .toLocaleString () -> "1,1"
Tarnay Kálmán
1
Funkcja nie działa w ustawieniach regionalnych, które używają więcej niż jednego znaku DecimalSeparator(np ,,.). W systemie Windows LOCALE_SDECIMALseparator dziesiętny może zawierać do trzech znaków. (Dlatego nie działa na moim komputerze). W takim przypadku lepiej jest użyć Accept-Languageprzeglądarki. Co nadal nie wyjaśnia możliwości określenia własnego, DecimalSeparatornp.\o/
Ian Boyd
4
@IanBoyd ma rację co do ustawień regionalnych z ciągiem zawierającym więcej niż jeden znak jako separatorem dziesiętnym, ale n = /^1(.+)1$/.exec(n.toLocaleString())[1]zrobiłby to i jest łatwiejsze niż użycie Accept-Languagenagłówka.
ygormutti
15

Pobieranie separatorów dla aktualnego lub danego locale jest możliwe za pomocą Intl.NumberFormat#formatToParts.

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

Działa tylko w przeglądarkach obsługujących Intl API . W przeciwnym razie wymaga wypełnienia Intl

Przykłady:

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

Premia:

Możemy przedłużyć go odzyskać albo po przecinku lub grupy separator danej lokalizacji:

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

Przykłady:

> getSeparator('en-US', 'decimal')
"."
> getSeparator('en-US', 'group')
","
> getSeparator('fr-FR', 'decimal')
","
> getSeparator('fr-FR', 'group')
" "
JBE
źródło
Obecnie jest to najwłaściwszy sposób uzyskania takich informacji. BTW Intljest obsługiwany nawet w IE 11: caniuse.com/#feat=internationalization
Konstantin
3
To nie będzie działać w IE 11, ponieważ formatToParts nie jest obsługiwany.
Gajendra Kumar
11

Zapytaj użytkownika, nie zgaduj. Skonfiguruj to w swojej aplikacji internetowej.

Edytowano, aby dodać:

Myślę, że można zgadnąć domyślne ustawienie, które działa prawidłowo, powiedzmy, w 95% przypadków. Chodziło mi o to, że użytkownik powinien nadal mieć możliwość nadpisania wszelkich przypuszczeń dokonanych przez oprogramowanie. Zbyt wiele razy byłem sfrustrowany, gdy oprogramowanie próbuje być zbyt inteligentne i nie pozwala na poprawienie.

laalto
źródło
Zabawne, to był mój pierwszy pomysł, ale przesadziłem, zastanawiając się, jak to zrobić automatycznie ...
PhiLho
1
Zły pomysł, może z wyjątkiem kopii zapasowej. Większość użytkowników nie jest wrażliwa na kulturę i nawet nie zrozumie, czym jest „separator dziesiętny” bez wyjaśnienia (a wtedy będą wściekli, gdy zostaną zmuszeni do ustawienia czegoś, co „wszyscy wiedzą”).
Michael Borgwardt
2
@Iaalto: to sprawiłoby, że pytanie byłoby prawie tak wielkie, jak „Zminimalizować rozmiar bazy danych (zalecane) lub Zmaksymalizować możliwości wyszukiwania?”
Quassnoi
Cóż, to nie powinno być zbyt trudne. Po prostu pozwól użytkownikowi wybrać kraj, a następnie odpowiednio wybierz separator osadu i inne opcje.
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;
    }
Dr Obłak
źródło
1
powrót do „.” w przypadku niektórych niejasnych przeglądarek ... w inny sposób jest prawie tak samo ...
Dr Obblak
7

Dlaczego nie

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

user3023011
źródło
2
może jakieś dziwne locale może pominąć początkowe zero? Wolałbym mieć jeden, żeby się upewnić.
Juangui Jordán
5

Domyślam się, że separator dziesiętny z Accept-Language jest prawidłowy w 95% przypadków, ale czasami się nie udaje.

To jest IMO najlepszy sposób działania. Aby poradzić sobie z awariami, dodaj łącze, aby ustawić je ręcznie obok obszaru wyświetlania.

Michael Borgwardt
źródło
Jak by to zrobiono? Rozumiem, że możesz korzystać z biblioteki przeglądarki, takiej jak ta github.com/dansingerman/jQuery-Browser-Language
Lime
@William: Język akceptacji, o którym mówi OP, to nagłówek HTTP wysłany przez przeglądarkę, który mówi serwerowi, jaki język preferuje użytkownik, zwykle język instalacji przeglądarki lub system operacyjny.
Michael Borgwardt
4

Korzystając z odpowiedzi innych osób, skompilowałem następujące funkcje użytkowe separatorów dziesiętnych i tysięcy:

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

Cieszyć się!

Juangui Jordán
źródło
Tak, użyłem tej metody jako wypełnienia dla przeglądarek, które nie obsługują formatToParts(Safari i IE).
Marko Bonaci
1
Niektóre lokalizacje nie używają separatorów tysięcy poniżej 10000. Na przykład(1000).toLocaleString("es-PE") # "1000"
Madacol,
1

Myślę, że musisz polegać na JavaScript, aby zapewnić ustawienia regionalne.
Ale najwyraźniej JS nie ma bezpośredniego dostępu do tych informacji.
Widzę, że zestaw Dojo Toolkit korzysta z zewnętrznej bazy danych w celu znalezienia informacji o ustawieniach regionalnych, chociaż może na przykład nie uwzględniać zmian ustawień konta.
Innym obejściem, które widzę, jest mały, cichy aplet Java, który odpytuje te informacje z systemu i JavaScript, aby wydobyć je z Javy.
Mogę podać więcej informacji, jeśli nie wiesz, jak to zrobić (jeśli oczywiście chcesz iść tą zawiłą trasą).

[EDYTUJ] Więc zaktualizowałem swoją wiedzę na temat obsługi lokalizacji w Javie ...
W przeciwieństwie do tego, co początkowo sądziłem, nie będziesz mieć bezpośrednio separatora dziesiętnego lub tysięcy znaków, tak jak w przypadku separatora linii lub separatora ścieżki: zamiast Java oferuje interfejsy API do formatowania podanych liczb lub dat.
Jakoś to ma sens: w Europie często umieszcza się symbol waluty po liczbie, w niektórych krajach (Indie?) Obowiązuje bardziej złożona zasada oddzielania cyfr itp.

Inna sprawa: Java poprawnie wyszukuje aktualne ustawienia regionalne z systemu, ale nie pobiera stamtąd informacji (być może z powyższych powodów). Zamiast tego używa własnego zestawu reguł. Więc jeśli masz hiszpańskie ustawienia regionalne, w których zastąpiłeś separator dziesiętny znakiem wykrzyknika, Java go nie użyje (ale może i tak nie jest Twoja aplikacja ...).

Piszę więc aplet udostępniający usługę (funkcje) JavaScriptowi, umożliwiając formatowanie liczb zgodnie z aktualnymi ustawieniami lokalnymi. Możesz go używać jako takiego, używając JavaScript do formatowania liczb w przeglądarce. Możesz też po prostu podać numer próbki i wyodrębnić stamtąd symbole, używając ich lokalnie lub przesyłając je z powrotem do serwera.

Kończę, testuję mój aplet i umieszczam go tam wkrótce.

PhiLho
źródło
@PhiLho: dobrze by było wiedzieć. Ta aplikacja internetowa jest rodzajem systemu pomocy, więc każdy brzydki hack się nada, nie musi być elegancka, o ile działa w IE, Firefox i Opera.
Quassnoi
1

OK, mam coś do pokazania, bardziej dowód koncepcji niż gotowy produkt, ale ze względu na brak dokładnych specyfikacji zostawiam to w ten sposób (albo przeprojektuję). Piszę w osobnej wiadomości, bo będzie trochę za długa. Skorzystałem z okazji, aby wypróbować nieco więcej jQuery ...

Kod 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);
  }
}

Przykład strony HTML wykorzystującej powyższy aplet: 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>

Testowano w przeglądarce Firefox 3.0, IE 6, Safari 3.1 i Opera 9.50, w systemie Windows XP Pro SP3. Z dwoma pierwszymi działa bez problemu, na Safari mam dziwny błąd po wywołaniu 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)

ale nadal działa.

Nie mogę sprawić, żeby działało z Operą: aplet ładuje się poprawnie, ponieważ widzę ślad wywołania init () w konsoli Java, nie mam błędów, gdy JavaScript wywołuje funkcje Java (z wyjątkiem sytuacji, gdy dodam i wywołam metodę co ciekawe, pobieranie parametru JSObject), ale funkcje Java nie są wywoływane (dodałem śledzenie wywołań).
Wydaje mi się, że Liveconnect działa w Operze, ale nie wiem jeszcze jak. Zbadam trochę więcej.
[Aktualizacja] Usunąłem odniesienia do nieistniejącego pliku jar (co nie zatrzymuje innych przeglądarek) i otrzymałem ślad wywołań, ale nie aktualizuje to strony.
Mmm, jeśli mam, alert(applet.GetLocaleInformation());mam informacje, więc może to być problem z jQuery.

PhiLho
źródło
@PhiLho: działa, ale nadal nie odczytuje poprawnie separatora z systemu Windows. Kiedy widzi, co następuje: Locale for русский (Россия): country=Россия (RU / RUS), lang=русский (ru / rus), variant= ()używa przecinka pomimo faktu, że nadpisałem go kropką w ustawieniach systemu Windows.
Quassnoi,
Proszę o przeczytanie mojej aktualizacji pierwszej wiadomości o ograniczeniach tego systemu. Nie wiem, czy możemy zrobić to lepiej z przeglądarki internetowej, może z wyjątkiem użycia jakiegoś natywnego kodu.
PhiLho
1

Nawet jeśli nie wiedział, co to locale „GUI aplikacji” pracuje pod, trzeba jeszcze, aby dowiedzieć się, jak to jest coraz bieżące locale, i jak to jest określenie separator dziesiętny.

Nie wiem, jak to się robi na Macu, ale aplikacje Windows mają sprawdzać preferencje użytkownika ustawione w Panelu sterowania. Jest całkiem możliwe, że ta tajemnicza aplikacja ignoruje te ustawienia i używa zamiast tego własnej wewnętrznej konfiguracji.

A może raczej wybierają aktualne lokalizacje i wnioskują o resztę, zamiast mówić.

Nawet wtedy, w języku angielskim, liczby podaje się w grupach po 3 cyfry, oddzielając je przecinkami. to znaczy:

5,197,359,078

O ile numer nie był liczbą całkowitą zawierającą numer telefonu :

519-735-9078

O ile oczywiście liczba nie była liczbą całkowitą zawierającą numer konta :

5197359078

W takim przypadku wrócisz do zakodowanej na stałe zastąpionej logiki.

Edycja: Usunięto przykład waluty, ponieważ waluta ma własne reguły formatowania.

Ian Boyd
źródło
0

Podobne do innych odpowiedzi, ale skompresowane jako stała :

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

Aby uzyskać separator tysięcy :

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

Po prostu umieść kod na górze swojego JS, a następnie wywołaj zgodnie z wymaganiami, aby zwrócić symbol.


Na przykład (gdzie mieszkam), aby usunąć przecinki z "1,234,567":

console.log( "1,234,567".replaceAll(thousands,"") ); //prints "1234567" to console.  
ashleedawg
źródło
-1

„Czy jest jakiś sposób, aby to zrobić po stronie serwera (najlepiej po to, bym mógł zbierać statystyki), czy po stronie klienta?”

Nie, nie możesz. Ten GUI sprawdza niektóre ustawienia użytkownika lub komputera. Po pierwsze, prawdopodobnie nie wiesz, jakich ustawień szuka ten interfejs użytkownika. Po drugie, z aplikacją internetową prawdopodobnie nie będziesz w stanie sprawdzić tych ustawień (strona klienta -> Javacsript).

Ian Boyd
źródło
@RWC: Wiem, gdzie szuka aplikacji GUI: ustawienia regionalne systemu Windows.
Quassnoi
-4

Inne możliwe rozwiązanie: możesz użyć czegoś takiego jak GeoIP (przykład w PHP), aby określić lokalizację użytkownika i zdecydować na podstawie tych informacji.

okoman
źródło
1
Konieczne byłoby jednak zezwolenie użytkownikowi na zastąpienie tego. Mój przyjaciel pracuje na południu Anglii; firma, w której pracuje, zajmuje się trasami całego dostępu do Internetu przez serwer proxy w Hiszpanii, więc GeoIP zawsze pokazuje, że znajduje się on wiele setek mil od jego rzeczywistej lokalizacji.
NickFitz