Con un navegador, ¿cómo sé qué separador decimal utiliza el sistema operativo?

82

Estoy desarrollando una aplicación web.

Necesito mostrar algunos datos decimales correctamente para que se puedan copiar y pegar en una determinada GUIaplicación que no esté bajo mi control.

La aplicación GUI es sensible a la configuración regional y solo acepta el separador decimal correcto que está configurado en el sistema.

Puedo adivinar el separador decimal de Accept-Languagey la suposición será correcta en el 95% de los casos, pero a veces falla.

¿Hay alguna forma de hacerlo en el lado del servidor (preferiblemente, para que pueda recopilar estadísticas) o en el lado del cliente?

Actualizar:

El objetivo de la tarea es hacerlo automáticamente.

De hecho, esta aplicación web es una especie de interfaz en línea para una GUI heredada que ayuda a completar los formularios correctamente.

El tipo de usuarios que lo usan en su mayoría no tienen idea de qué es un separador decimal.

La Accept-Languagesolución está implementada y funciona, pero me gustaría mejorarla.

Actualización2:

Necesito recuperar una configuración muy específica: separador decimal establecido Control Panel / Regional and Language Options / Regional Options / Customize.

Me ocupo de cuatro tipos de sistemas operativos:

  1. Windows ruso con una coma como DS (80%).
  2. Inglés Windows con un punto como DS (15%).
  3. Windows ruso con un período como DS para hacer que las aplicaciones en inglés mal escritas funcionen (4%).
  4. Windows en inglés con una coma como DS para que las aplicaciones rusas mal escritas funcionen (1%).

El 100% de los clientes están en Rusia y la aplicación heredada se ocupa de los formularios emitidos por el gobierno ruso, por lo que solicitar un país arrojará el 100% de la Federación de Rusia, y GeoIP generará el 80% de la Federación de Rusia y el 20% de otros (incorrecto) respuestas.

Quassnoi
fuente

Respuestas:

127

Aquí hay una función de JavaScript simple que devolverá esta información. Probado en Firefox, IE6 e IE7. Tuve que cerrar y reiniciar mi navegador entre cada cambio en la configuración en Panel de control / Opciones regionales y de idioma / Opciones regionales / Personalizar. Sin embargo, no solo recogió la coma y el punto, sino también cosas extrañas personalizadas, como la letra "a".

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

¿Esto ayuda?

Chris Nielsen
fuente
3
Esto funcionó para mí en Firefox e IE8, pero no en Google Chrome. No tengo Opera.
Matthew Talbert
@Matthew: funcionó para mí en Chrome. @Quassnoi - No entiendo lo que significa ese último comentario. Si está diciendo que la función no funciona, ¿qué importa lo que la persona sepa?
Matchu
¡Cuidado! En Chrome, toLocaleString solo funciona correctamente si se llama directamente en un número. En mi sistema: [1.1,1.2] .toLocaleString () -> "1.1,1.2" | (1.1) .toLocaleString () -> "1,1"
Tarnay Kálmán
1
La función falla en las configuraciones regionales que usan más de un carácter para su DecimalSeparator(ej ,,.). Windows LOCALE_SDECIMALpermite que un separador decimal contenga hasta tres caracteres. (Por eso falla en mi PC). Es mejor usar el Accept-Languagedel navegador en ese caso. Lo que todavía no tiene en cuenta la capacidad de especificar los suyos propios, DecimalSeparatorpor ejemplo\o/
Ian Boyd
4
@IanBoyd tiene razón sobre las configuraciones regionales con una cadena de más de un carácter como separador decimal, pero n = /^1(.+)1$/.exec(n.toLocaleString())[1]lo haría, y es más fácil que usar el Accept-Languageencabezado.
ygormutti
14

Es posible recuperar separadores para la configuración regional actual o determinada mediante Intl.NumberFormat#formatToParts.

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

Solo funciona para navegadores compatibles con la API de Intl . De lo contrario, requiere un polyfill Intl

Ejemplos:

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

Prima:

Podríamos extenderlo para recuperar el separador decimal o de grupo de una configuración regional determinada:

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

Ejemplos:

> getSeparator('en-US', 'decimal')
"."
> getSeparator('en-US', 'group')
","
> getSeparator('fr-FR', 'decimal')
","
> getSeparator('fr-FR', 'group')
" "
JBE
fuente
Actualmente, esta es la forma más adecuada de obtener dicha información. Por cierto, Intlse admite incluso en IE 11: caniuse.com/#feat=internationalization
Konstantin Smolyanin
3
Esto no funcionará en IE 11 ya que formatToParts no es compatible.
Gajendra Kumar
11

Pregunte al usuario, no adivine. Tenga una configuración para ello en su aplicación web.

Editado para agregar:

Creo que está bien adivinar la configuración predeterminada que funciona bien, digamos, el 95% del tiempo. Lo que quise decir es que el usuario debería poder anular cualquier conjetura que haya hecho el software. Ya me he sentido frustrado muchas veces cuando un software intenta ser demasiado inteligente y no permite que se corrija.

laalto
fuente
Es curioso, fue mi primera idea, pero me pasé por la borda buscando cómo hacerlo automáticamente ...
PhiLho
1
Mala idea, excepto tal vez como respaldo. La mayoría de los usuarios son insensibles a la cultura y ni siquiera entenderán qué es un "separador decimal" sin una explicación (y luego se enojarán al verse obligados a establecer algo que "todos saben").
Michael Borgwardt
2
@Iaalto: esto haría una pregunta casi tan buena como "¿Minimizar el tamaño de la base de datos (recomendado) o maximizar las capacidades de búsqueda?"
Quassnoi
Bueno, eso no debería ser demasiado difícil. Simplemente deje que el usuario elija el país y luego seleccione el separador desecante y otras opciones en consecuencia.
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.Oblak
fuente
1
la alternativa a "." en el caso de algunos navegadores poco conocidos ... de otras formas es más o menos lo mismo ...
Dr.Oblak
7

Por qué no

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

user3023011
fuente
2
tal vez algún lugar extraño puede omitir el cero inicial? Preferiría tener uno allí, solo para estar seguro.
Juangui Jordán
5

Puedo adivinar el separador decimal de Accept-Language y la suposición será correcta en el 95% de los casos, pero a veces falla.

Este es en mi opinión el mejor curso de acción. Para manejar las fallas, agregue un enlace para configurarlo manualmente junto al área de visualización.

Michael Borgwardt
fuente
¿Cómo se haría esto? Entiendo que puede usar una biblioteca de navegador como esta github.com/dansingerman/jQuery-Browser-Language
Lime
@William: El lenguaje de aceptación del que habla OP es un encabezado HTTP enviado por el navegador que le dice al servidor qué idioma prefiere el usuario, generalmente el idioma de la instalación del navegador o el sistema operativo.
Michael Borgwardt
4

Usando las respuestas de otras personas, compilé las siguientes funciones de utilidad de separadores decimales y de miles:

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

¡Disfrutar!

Juangui Jordán
fuente
Sí, utilicé este método como polyfill para navegadores que no son compatibles formatToParts(Safari e IE).
Marko Bonaci
1
Algunas configuraciones regionales no usan miles de separadores por debajo de 10000. Por ejemplo(1000).toLocaleString("es-PE") # "1000"
Madacol
1

Creo que debe confiar en JavaScript para obtener la configuración regional.
Pero aparentemente JS no tiene acceso directo a esta información.
Veo que Dojo Toolkit se basa en una base de datos externa para encontrar la información de la configuración regional, aunque es posible que no tenga en cuenta los cambios de configuración de la cuenta, por ejemplo.
Otra solución que veo es tener un pequeño subprograma de Java silencioso que consulta esta información del sistema y JavaScript para sacarla de Java.
Puedo darte más información si no sabes cómo hacerlo (si quieres seguir este camino complicado, por supuesto).

[EDITAR] Así que actualicé mi conocimiento del soporte de localización en Java ...
A diferencia de lo que pensé originalmente, no tendrá directamente el separador decimal o miles de caracteres separadores directamente, como lo haría con el separador de línea o el separador de ruta: en lugar de Java ofrece API para formatear los números o las fechas que proporcione.
De alguna manera, tiene sentido: en Europa, a menudo se coloca el símbolo de la moneda después del número, algunos países (¿India?) Tienen una regla más compleja para separar dígitos, etc.

Otra cosa: Java encuentra correctamente la configuración regional actual del sistema, pero no toma información de allí (quizás por las razones anteriores). En su lugar, utiliza su propio conjunto de reglas. Entonces, si tiene una configuración regional en español donde reemplazó el separador decimal con un signo de exclamación, Java no lo usará (pero quizás tampoco su aplicación, de todos modos ...).

Así que estoy escribiendo un subprograma que expone un servicio (funciones) a JavaScript, lo que permite formatear números en la configuración regional actual. Puede usarlo como tal, usando JavaScript para formatear números en el navegador. O simplemente puede alimentarlo con algún número de muestra y extraer los símbolos de allí, utilizándolos localmente o devolviéndolos al servidor.

Termino y pruebo mi applet y lo publico allí pronto.

PhiLho
fuente
@PhiLho: sería bueno saberlo. Esta aplicación web es una especie de sistema de ayuda, por lo que cualquier truco feo servirá, no necesita ser elegante siempre que se ejecute en IE, Firefox y Opera.
Quassnoi
1

De acuerdo, tengo algo que mostrar, más una prueba de concepto que un producto terminado, pero debido a la falta de especificaciones precisas, lo dejo así (o lo sobre-diseñaré). Publico en un mensaje separado porque será un poco largo. Aproveché para probar un poco más de jQuery ...

El código 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);
  }
}

Un ejemplo de página HTML usando el subprograma anterior: 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>

Probado en Firefox 3.0, IE 6, Safari 3.1 y Opera 9.50, en Windows XP Pro SP3. Funciona sin problema con los dos primeros, en Safari tengo un error extraño después de llamar a 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)

pero todavía funciona.

No puedo hacer que funcione con Opera: el subprograma se carga correctamente, ya que puedo ver el rastro de la llamada init () en la consola de Java, no tengo errores cuando JavaScript llama a las funciones de Java (excepto si agrego y llamo a un método obteniendo un parámetro JSObject, curiosamente), pero las funciones de Java no son llamadas (agregué rastro de las llamadas).
Creo que Liveconnect funciona en Opera, pero todavía no veo cómo. Investigaré un poco más.
[Actualización] Eliminé las referencias a un archivo jar no existente (que no detiene otros navegadores) y obtuve un rastro de las llamadas, pero no actualiza la página.
Mmm, si lo hago alert(applet.GetLocaleInformation());, obtuve la información, por lo que podría ser un problema de jQuery.

PhiLho
fuente
@PhiLho: funciona, pero aún no lee correctamente el separador de Windows. Cuando ve lo siguiente: Locale for русский (Россия): country=Россия (RU / RUS), lang=русский (ru / rus), variant= ()usa una coma a pesar de que la he anulado con un punto en la configuración de Windows.
Quassnoi
Por favor, lea mi actualización de mi primer mensaje sobre las limitaciones de este sistema. No sé si podemos hacerlo mejor desde un navegador web, excepto quizás usando algún código nativo.
PhiLho
1

Incluso si usted supiera lo que esta configuración regional "Aplicación GUI" se está ejecutando bajo, usted todavía tiene que encontrar la manera que está recibiendo la localización actual, y cómo se está determinando el separador decimal.

No sé cómo se hace en una Mac, pero se supone que las aplicaciones de Windows interrogan las preferencias del usuario establecidas a través del Panel de control. Es muy posible que esta aplicación misteriosa ignore esas configuraciones y use su propia configuración interna en su lugar.

O tal vez están tomando la ubicación actual e infiriendo el resto, en lugar de que se les diga.

Incluso entonces, en inglés, los números se dan en grupos de 3 dígitos, con una coma separando los grupos. es decir:

5,197,359,078

A menos que el número sea un número entero que contenga un número de teléfono :

519-735-9078

A menos que, por supuesto, el número sea un número entero que contenga un número de cuenta :

5197359078

En cuyo caso, vuelve a la lógica anulada codificada.

Editar: ejemplo de moneda eliminado, ya que la moneda tiene sus propias reglas de formato.

Ian Boyd
fuente
0

Similar a otras respuestas, pero comprimido como una constante :

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

Además, para obtener el separador de miles :

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

Simplemente coloque el código en la parte superior de su JS y luego llame según sea necesario para devolver el símbolo.


Por ejemplo (donde vivo), para quitar las comas de "1,234,567":

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

"¿Hay alguna forma de hacerlo en el lado del servidor (preferiblemente, para que pueda recopilar estadísticas), o en el lado del cliente?"

No, no puedes. Esa GUI está mirando algunas configuraciones específicas de usuario o máquina. Primero, probablemente no sepa en qué configuración se ve esta interfaz de usuario. En segundo lugar, con una aplicación web probablemente no podrá verificar estas configuraciones (lado del cliente -> Javacsript).

Ian Boyd
fuente
@RWC: Sé hacia dónde busca la aplicación GUI: la configuración regional de Windows.
Quassnoi
-4

Otra posible solución: podría usar algo como GeoIP (ejemplo en PHP) para determinar la ubicación del usuario y decidir en base a esta información.

okoman
fuente
1
Sin embargo, sería necesario permitir que el usuario anule esto. Un amigo mío trabaja en el sur de Inglaterra; la empresa para la que trabaja enruta todo el acceso a Internet a través de un servidor proxy en España, por lo que GeoIP siempre lo muestra a cientos de kilómetros de su ubicación real.
NickFitz