Localizar un widget embebido: ¿idioma de la página o del navegador?

widget

Una visitante de una tienda online en español encuentra un botón de pago que no hace nada. Hace clic en el pequeño icono de bug de la esquina y le aparece «What's on your mind?» y un botón que dice «Send». Lo entiende. Pero escribe tres palabras en lugar de tres frases, porque describir un problema en otro idioma cuesta, y nadie te debe ese esfuerzo.

Hasta esta semana, eso pasaba en todos los sitios que usaban BugDump y no estaban en inglés. El widget hablaba inglés, y traducirlo significaba pasarle tú mismo las 36 cadenas. La mayoría no lo hacía. Ahora viene en inglés, ruso, español, francés y portugués, y elige uno por su cuenta. Elegir resultó ser la parte interesante.

Actualización del 3 de octubre: el alemán llegó en el SDK 0.0.42 como sexto idioma. También necesitó palabras más cortas: el botón de adjuntar dice «Datei» (archivo) y la etiqueta de las notas de voz, «Audio». Los detalles están en la entrada del registro de cambios.

El widget de reporte de bugs de BugDump en francés, español, portugués y ruso

Tres sitios de los que un widget puede sacar el idioma

Un widget embebido se ejecuta en la página de otra persona. Puede leer tres señales, y no siempre coinciden.

El atributo lang de la página. Con <html lang="es">, el sitio dice en qué idioma está su contenido. Los lectores de pantalla lo usan para elegir la pronunciación, y los navegadores lo miran cuando ofrecen traducir una página. Describe la página, no a la persona que la lee.

La lista de idiomas del navegador. navigator.languages son las preferencias del visitante en orden, algo como ["pt-BR", "pt", "en"]. navigator.language es solo la primera entrada. Describe a la persona, no la página.

Lo que el sitio le dice al widget. Un ajuste explícito en el código de inserción. La más fiable de las tres, y la que casi nadie rellena.

También está la cabecera Accept-Language, que el servidor ve en cada petición. Un widget cargado como script desde un CDN nunca llega a leerla, así que queda descartada.

Por qué empiezo por la página

Los widgets de chat de soporte suelen seguir al navegador, y para una conversación con una persona tiene sentido: quieres hablar en tu idioma. Un widget de reporte de bugs es otra cosa. Vive dentro de la interfaz de otra persona, junto a sus botones y sus textos, y trata de su producto. Un panel en francés en una página de pago en inglés parece un bug en sí mismo.

Así que el orden que elegí es:

  1. <html lang>, si nombra un idioma que el widget incluye.

  2. Si no, el primero de los idiomas del navegador del visitante que el widget incluya.

  3. Si no, inglés.

La página va primero para que un sitio en inglés siga en inglés, incluso para un visitante con el navegador en ruso. El navegador va segundo para que una página sin ningún atributo lang reciba igualmente algo razonable.

El agujero evidente es una página con el lang equivocado. Muchas plantillas de inicio traen <html lang="en"> y nadie lo toca nunca, así que un sitio escrito entero en portugués puede seguir diciendo que está en inglés. Decidí que la solución para eso es corregir el atributo, no adivinar mejor. Un lang equivocado ya perjudica a los usuarios de lectores de pantalla de ese sitio. Un widget que lo esquiva en silencio oculta el problema en vez de sacarlo a la luz.

Comparar etiquetas sin pasarse de listo

Las etiquetas de idioma llegan de muchas formas: fr, fr-CA, FR o pt_BR desde un backend que nunca ha oído hablar de BCP 47. El widget incluye un juego de cadenas por idioma, así que la comparación solo mira la parte anterior al primer - o _, en minúsculas:

JavaScript
function findLocale(tag) {
  const base = tag.toLowerCase().split(/[-_]/)[0];
  return SHIPPED_LOCALES.find((locale) => locale === base);
}

Eso significa que un visitante de Portugal recibe portugués de Brasil. No es perfecto, y prefiero decirlo a fingir que lo es.

Buscar en una lista es deliberado. base in LOCALES se lee casi igual, hasta que llega una etiqueta como constructor y coincide con una propiedad que hereda todo objeto de JavaScript. Nadie pone lang="constructor" a propósito, pero desde el punto de vista de un widget, un atributo lang es una entrada que viene de un desconocido.

Lo que se rompió por el camino

Un test que dependía de la máquina en la que se ejecutaba. Las versiones recientes de Node tienen un navigator global, y su languages sigue el locale de la máquina. Los tests de configuración del SDK se ejecutan sin DOM, así que el primer borrador habría resuelto «auto» como ruso en una máquina configurada en ruso y como inglés en CI. Ahora la detección no devuelve nada si no existe también un document, que es como debería verse un entorno que no es un navegador.

El ruso es largo. «Прикрепить» (adjuntar) tiene diez letras donde el inglés tiene seis, y empujó el tercer botón de la barra de acciones a una segunda fila. La solución no fue CSS, sino una palabra más corta: el botón en ruso ahora dice «Файл» (archivo) y deja que el icono del clip haga el resto. La etiqueta de nota de voz en los adjuntos tenía el mismo problema. Es un recuadro de 72 píxeles, y «Nota de voz» acaba ahí en puntos suspensivos, así que el español y el francés ahora dicen «Audio», y el portugués, «Áudio».

Estas cosas solo se encuentran mirando. Antes de lanzarlo, rendericé el panel en cada idioma, y todos menos el inglés necesitaron al menos una palabra más corta.

Cadenas pegadas en el código. «Uploading 1/3… 40%» se construía con la palabra incrustada, igual que «Microphone 2» y «Powered by Bugdump». En lugar de inventar una sintaxis de plantillas, la parte traducible pasó a ser solo la palabra, y el código añade detrás los números y el nombre. Funciona porque todos los idiomas que incluye el widget ponen la palabra primero. El japonés no lo haría, y el día que lo añada, esas tres cadenas se convertirán en plantillas.

Lectores de pantalla. El widget se renderiza en un shadow root dentro de la página anfitriona, así que hereda el lang de la página. Un panel en francés en una página en inglés se leería en voz alta con pronunciación inglesa. Ahora el widget pone lang en su propio elemento.

El sitio sigue teniendo la última palabra

La detección automática es un valor por defecto, no una regla. Un sitio puede indicar el idioma en su código de inserción:

HTML
<script
  src="https://bugdump.com/sdk/latest.js"
  async
  data-api-key="your-api-key"
  data-locale="fr"
></script>

translations sigue sustituyendo cualquier cadena sobre el idioma elegido, así que renombrar «Send feedback» a «Report a bug» funciona igual en francés que en inglés. Para un idioma que el widget no incluye, también funciona locale: 'nl' con tus propias cadenas en neerlandés, y lo que no definas vuelve al inglés.

En la página Widget de BugDump, la misma elección es un desplegable, y la vista previa cambia de idioma a medida que eliges:

El ajuste Idioma en la página Widget de BugDump, abierto con Automático, English, Русский, Español, Français, Português y Deutsch

Lo que costó

Los cinco idiomas van incluidos en el script. Los cuatro adicionales sumaron unos 2,5 KB con gzip, y el widget pesa ahora unos 32 KB. Pensé en cargarlos bajo demanda, pero eso supone una petición más y un panel que solo puede renderizarse cuando llega la respuesta. Un estado de carga para ahorrar dos kilobytes y medio es un mal negocio.

Lo que no construí: cambiar el idioma de un widget que ya está en pantalla, y los diseños de derecha a izquierda. Ninguno de los idiomas incluidos se escribe de derecha a izquierda, y una single-page app que cambia de idioma puede desmontar el widget y volver a iniciarlo hasta que alguien necesite más.

Si estás construyendo tu propio widget

  • Lee <html lang> antes que navigator.languages. Tu widget es parte de la página.

  • Compara por el idioma base, y declara el idioma que realmente usaste con un atributo lang propio.

  • Deja que el sitio lo sobrescriba todo, y haz que sobrescribirlo sea un solo atributo en la etiqueta script.

  • Renderiza cada cadena en cada idioma antes de lanzar. Las traducciones son más largas de lo que esperas.

El widget de BugDump hace todo esto desde el SDK 0.0.41, y quienes lo usan con la etiqueta script ya lo tienen. Traducir y personalizar la marca del widget cubre todas las opciones, y la entrada del registro de cambios tiene la versión corta.

Te puede interesar