Saltar al contenido principal

Internacionalización

Flarum cuenta con un potente sistema de traducción (basado en el traductor de Symfony) que permite que la interfaz muestre información en prácticamente cualquier idioma. Deberías considerar aprovechar el traductor de Flarum mientras desarrollas tu extensión, incluso si no tienes intención de usarla en más de un idioma.

Por un lado, este sistema le permitirá cambiar la información mostrada por su extensión sin editar el código real. También le proporcionará las herramientas necesarias para tratar eficazmente fenómenos como la pluralización y la concordancia de género. Y nunca se sabe: puede ser útil más adelante si decide que quiere añadir más idiomas y hacer que su extensión esté disponible para usuarios de todo el mundo.

Esta guía le mostrará cómo crear un archivo de configuración regional que contenga recursos lingüísticos para su extensión, y cómo usar el traductor para acceder a esos recursos desde su código. Aprenderás a tratar con traducciones más complejas que implican variables y etiquetas HTML, así como pluralización y género.

También describiremos el formato estándar que debe seguirse al desarrollar recursos lingüísticos para Flarum, y ofreceremos algunos consejos que pueden ayudarte a que tus recursos lingüísticos sean más fáciles de localizar. Pero primero, comencemos con una visión general de cómo Flarum prioriza los recursos cuando muestra la salida para una extensión de terceros.

Cómo traduce Flarum

Puede establecer que una clave haga referencia a otra sustituyendo su traducción por un signo de igualdad (=), un signo de mayor que (>) y un espacio, seguido de la clave de traducción completa a la que se hará referencia. Al instalar la extensión, el compilador de Flarum resolverá estas referencias para crear un conjunto completo de traducciones que pueda utilizar.

Flarum pulls translations from several sources:

  • Como un botón en el que los usuarios pueden hacer clic cuando quieren editar algunas cosas
  • Como el título de un cuadro de diálogo que se muestra cuando los usuarios hacen clic en ese botón

Por regla general, un sitio de Flarum sólo puede mostrar las traducciones correspondientes a los paquetes de idiomas que se han instalado en ese sitio. Pero Flarum hará todo lo posible dentro de esta limitación para renderizar la salida de su extensión en algún tipo de lenguaje legible para el usuario:

  1. Comenzará buscando una traducción en el idioma de visualización preferido por el usuario.
  2. Si no encuentra ninguna, buscará una traducción en el idioma por defecto del foro.
  3. Como último esfuerzo, buscará una traducción "genérica" al inglés de la salida.
  4. Si ninguna de las anteriores está disponible, se rendirá y mostrará una clave de traducción.

Dado que las traducciones al inglés podrían ser lo único que se interponga entre los usuarios del foro y las antiestéticas claves de traducción, recomendamos encarecidamente incluir un conjunto completo de recursos en inglés con cada extensión. (También tendrá que incluir recursos en inglés si desea listar su extensión en el Mercado de Flarum). A diferencia de las extensiones incluidas en Flarum, que se traducen utilizando recursos añadidos por paquetes de idiomas, una extensión de terceros tiene que proporcionar todas sus propias traducciones.

Archivo de localización

Los recursos lingüísticos de Flarum utilizan el formato de archivo YAML. Los archivos de localización de una extensión de terceros deben almacenarse en la carpeta locale de la extensión. Cada archivo de configuración regional debe nombrarse utilizando el código ISO 639-1 del idioma que contiene. Por ejemplo, un archivo que contenga traducciones al francés debe llamarse "fr.yml".

tip

Si desea proporcionar soporte para una localización específica, puede añadir un guión bajo seguido de un código de país ISO 3166-1 alpha-2; el nombre de archivo para el francés hablado en Canadá, por ejemplo, sería "fr_CA.yml". Pero debe asegurarse de incluir un archivo de configuración regional que contenga traducciones "genéricas" para el mismo idioma, de modo que Flarum tenga algo a lo que recurrir en caso de que un usuario especifique una configuración regional que usted no haya previsto.

El esqueleto de la extensión incluye una plantilla locale/en.yml donde puedes poner las traducciones al inglés de tu extensión. Si quieres añadir recursos para otro idioma o localización, simplemente duplica la plantilla y dale un nombre de archivo apropiado. A continuación, abre el archivo y empieza a añadir tus traducciones.

Añadir claves

Cada traducción en el archivo de configuración regional debe ir precedida de una clave, que se utilizará como identificador para esa traducción. La clave de identificación debe estar en snake_case y seguida de dos puntos y un espacio, como se muestra a continuación:

sample_key: This is a sample translation.

También puede utilizar claves para espaciar el nombre de sus traducciones. Para empezar, la primera línea del archivo de configuración regional debe consistir en una única clave que reúna todas las traducciones de su extensión en un único espacio de nombres. Esta clave debe coincidir exactamente con el nombre de la carpeta en la que se encuentra la extensión, kebab-case y todo eso.

Se pueden utilizar claves adicionales para dividir el espacio de nombres de la extensión en grupos. Esto es muy útil cuando quieres organizar tus traducciones de acuerdo con el lugar donde aparecen en la interfaz de usuario, por ejemplo. Estas claves intermedias de espaciado de nombres deben estar siempre en snake_case.

Cada clave de espaciado de nombres debe ir seguida de dos puntos. Las claves deben anidarse según el formato de esquema de YAML, añadiendo dos espacios de sangría por cada nivel de la jerarquía. Poniendo todo esto junto, el archivo de configuración regional para el tutorial de inicio rápido podría ser algo así:

acme-hello-world:                # Namespacing for the extension; unindented.
alert: # Namespacing for alerts; indented 2 spaces.
hello_text: "Hello, world!" hello_text: "Hello, world!" # Identifier/translation; indented 4 spaces.

Una vez que tengas esta información, puedes formar la clave de traducción completa que utilizarás para acceder a una traducción enumerando sus claves en orden desde el espacio de nombres de extensión hasta un identificador, con puntos como delimitadores. Por ejemplo, la clave de traducción completa para la traducción "Hello, world!" sería:

'acme-hello-world.alert.hello_text'

Eso es todo lo que necesitas saber sobre la mecánica de la creación de claves. Sin embargo, ten en cuenta que hay un formato estándar que los desarrolladores deben seguir al crear recursos lingüísticos para Flarum. Las reglas para traducciones de namespacing y naming ID keys se pueden encontrar en el Apéndice A.

Añadir traducciones

Los ejemplos de la sección anterior ya le han dado lo básico: usted escribe una clave de identificación seguida de dos puntos y un espacio luego escribe la traducción. Así de fácil. Aquí sólo queremos añadir algunos detalles que le ayudarán a tratar con traducciones más largas y complejas.

Comillas

Habrás notado que sólo una de las dos traducciones de ejemplo de la sección anterior iba entre comillas. Por lo general, no es necesario delimitar las traducciones de este modo. Sin embargo, debe utilizar comillas dobles para encerrar cualquier traducción que incluya uno o más de los siguientes caracteres:

`  !  `  !  @  #  %  &  *  -  =  [  ]  {  }  |  :  ,  <  >  ?

Dado que Flarum utiliza corchetes y paréntesis angulares para denotar marcadores de posición para [variables] (#including-variables) y etiquetas HTML, respectivamente, no hace falta decir que cualquier traducción que incluya dichos marcadores de posición también deberá ir entre comillas dobles.

Además, debe utilizar comillas simples para encerrar cualquier traducción que incluya uno o más caracteres de comillas dobles (") o barras invertidas (\). Esta regla tiene prioridad. Así que si una traducción incluyera tanto comillas dobles como uno o más caracteres de la lista anterior como lo hace este ejemplo, en el que un marcador de posición de una variable está delimitado por comillas tendría que encerrarlo entre comillas simples.

Bloques literales

Cuando necesite que una traducción aparezca como más de una línea de texto, la traducción debe añadirse como un bloque literal. Introduzca un carácter de barra vertical (|) en el lugar en el que normalmente comenzaría la traducción, y luego añada la traducción debajo de la clave de identificación, sangrado cada línea con dos espacios adicionales:

literal_block_text: |
Estas líneas se mostrarán como se muestra aquí, con saltos de línea y todo.

También se conserva la sangría adicional: ¡esta línea tendrá una sangría de 4 espacios!

Las comillas son innecesarias, incluso cuando el bloque contiene caracteres especiales.

El bloque literal termina con la última línea con una sangría de al menos dos espacios más que la clave de identificación. Las comillas no son necesarias porque el bloque está efectivamente delimitado por estos dos espacios extra de sangría.

Los recursos lingüísticos básicos de Flarum emplean bloques literales principalmente para el contenido del cuerpo del correo electrónico.

Referencias clave

No es raro utilizar el mismo trozo de texto en más de un lugar o contexto. Supongamos que quieres que tu extensión muestre la frase "Editar cosas" en dos lugares de la interfaz de usuario:

  • As a button that users can click when they want to edit some stuff
  • As the title of a dialog box displayed when users click that button

Tu instinto podría ser el de añadir una única traducción llamémosla "edit_stuff" y utilizar esa clave de identificación dos veces en tu código. Este enfoque es eficiente, pero carece de flexibilidad: en algunos idiomas, puede que no sea posible utilizar la misma frase tanto para el botón como para el título del diálogo. Una forma mejor sería definir dos claves para su uso en el código, y luego establecer que ambas hagan referencia a la misma traducción, de esta manera:

edit_stuff_button: => edit_stuff    # Se utiliza en el código que crea el botón.
edit_stuff_title: => edit_stuff # Se utiliza en el código que crea el diálogo.

edit_stuff: Edit Stuff # No se utiliza en el código.

You can set one key to reference another by replacing its translation with an equal sign (=), a greater-than sign (>), and a space, followed by the full translation key to be referenced. When the extension is installed, Flarum's compiler will resolve these references to create a complete set of translations it can use.

Hay más cosas que decir sobre las referencias; para empezar, ¡hemos ignorado totalmente el tema del espaciado de nombres en el ejemplo anterior! Y puede que se pregunte por qué sugerimos crear tres claves cuando dos serían suficientes. Para una explicación, vea las reglas para reutilizar traducciones en el Apéndice A.

Uso del traductor

Una vez que hayas añadido una traducción a tu archivo de configuración regional, con el espaciado de nombres y las claves de identificación apropiadas, puedes utilizar el método app.translator.trans() para hacer referencia a esa traducción en tu código. Por ejemplo, el archivo js/forum/src/index.js para el tutorial de inicio rápido podría tener este aspecto:

app.initializers.add('acme-hello-world', function() {
alert(app.translator.trans('acme-hello-world.alert.hello_text'));
});

Esto muestra el método de traducción básico, sin campanas ni silbidos. A continuación encontrará ejemplos de traducciones más complejas que incluyen cosas como variables y etiquetas HTML. (Tenga en cuenta que hemos omitido el espaciado de nombres en los siguientes ejemplos para mantenerlos simples; si mira el código real, encontrará que las traducciones están debidamente espaciadas por nombres según el formato estándar.)

Inclusión de variables

Puedes incluir variables en las traducciones. As an example, let's look at the code that creates the first item in Flarum's search results dropdown. Este botón cita la consulta de búsqueda introducida por el usuario información que se pasa al traductor junto con la clave de traducción, como parámetro adicional:

{LinkButton.component({
icon: 'search',
children: app.translator.trans('all_discussions_button', {query}),
href: app.route('index', {q: query})
})}

Un marcador de posición coincidente en la traducción permite al traductor saber dónde debe insertar la variable:

all_discussions_button: 'Search all discussions for "{query}"'

Dado que se utilizan corchetes para indicar el marcador de posición, la traducción en su conjunto debe ir entre comillas. Normalmente, se utilizarían comillas dobles; pero como esta traducción en particular utiliza comillas dobles para definir la consulta de búsqueda, la regla de las comillas simples tiene prioridad.

Añadir etiquetas HTML

Abstraer las traducciones del HTML puede suponer un reto único: ¿cómo tratar los elementos del HTML que afectan sólo a una parte de la frase? Afortunadamente, Flarum le ofrece una forma de añadir etiquetas a sus traducciones.

Comienza añadiendo una clave al argumento de los parámetros para cada elemento que quieras que el traductor maneje. The following snippet — from the Edit Group modal of the admin interface — shows a translation key accompanied by a parameters object with one item.

<div className="helpText">
{app.translator.trans('icon_text', {
a: <a href="https://fortawesome.github.io/Font-Awesome/icons/" tabindex="-1"/>
})}
</div>

Tenga en cuenta que cada parámetro se define utilizando una sola etiqueta HTML, con una barra inclinada añadida antes del paréntesis angular de cierre. A continuación, puede utilizar etiquetas de apertura y cierre de estilo HTML en su archivo de configuración regional para especificar a qué parte de la traducción afecta cada elemento. Una vez más, las comillas dobles son necesarias. Puede ver que no se pasan todas las etiquetas como argumento, sólo las que tienen atributos.

icon_text: "Introduce el nombre de cualquier clase de icono <a>FontAwesome</a>, <em>sin</em> el prefijo <code>fa-</code>."

Por supuesto, puedes dar a un parámetro el nombre que quieras ¡podrías usar <fred> y </fred> para encerrar el texto de tu enlace si realmente quisieras! Pero le recomendamos que se ciña lo más posible a las etiquetas HTML que se están representando, para que sus localizadores puedan entender lo que está pasando.

Los localizadores pueden reordenar los elementos según sea necesario, e incluso optar por omitir las etiquetas si así lo desean. Pero no pueden añadir ninguna etiqueta propia: el traductor simplemente ignorará cualquier etiqueta de estilo HTML que no se corresponda con un parámetro correctamente definido en el código.

Manejo de la pluralización

En ocasiones, puede ser necesario proporcionar versiones alternativas de una traducción para acomodar la pluralización de una palabra o frase. Flarum uses theICU MessageFormat syntax to support selecting versions of translations based on variables. The most frequent use case is pluralization and genderization.

const remaining = this.minPrimary - primaryCount;
return app.translator.transChoice(
'choose_primary_placeholder',
remaining,
{ count: remaining }
);

Este ejemplo es del modal Choose Tags de la extensión Tags, donde se indica al usuario cuántas etiquetas primarias más puede seleccionar. Esta es la traducción al inglés del código anterior:

choose_primary_placeholder: "Choose a primary tag|Choose {count} primary tags"

In this case, we call the pluralization variable count. This isn't required, but we strongly recommend using count for consistency.

Por supuesto, el inglés sólo tiene dos variantes: singular o plural. Tendrá que proporcionar variantes adicionales cuando cree traducciones para un idioma que tenga más de una forma plural.

In addition to =0/=1/=2/... and other, the following plural keywords are supported: zero, one, two, few, and many.

See the Symfony Translation docs for more complex examples.

Tratamiento del género

Support for grammatical gender will be added in a future version of Flarum. Detailed instructions will be provided here once that functionality becomes available.

Traducción del lado del servidor

Translation is generally handled by Flarum's front-end client. However, you can use translations in your PHP code if you need to.

First, you'll need to get an instance of the Flarum\Locale\Translator class. You'll usually do this by typehinting this class in the constructor of your class so a translator instance is injected by the IoC Container. If dependency injection is not available, you can use resolve(Translator::class). Don't forget a use Flarum\Locale\Translator; statement at the top of your PHP file!

Then, the API is similar to the JavaScript Translator class. You can use $translator->trans like you'd use app.translator.trans in JavaScript. You can learn more about the Translator's methods in Symfony's Translator documentation, which Flarum's Translator extends.

Registrando las Localidades

There's one last thing you need to do before Flarum can use your translations. You need to register them. Fortunately, Flarum makes this pretty easy. Add the following to your extend.php:

new Extend\Locales(__DIR__ . '/locale'),

Apéndice A: Formato de clave estándar

The following guidelines were created for use in the Flarum core and bundled extensions. Their purpose is to ensure that translation keys are organized and named in a consistent fashion, so Flarum's localizers will be able to create and maintain high-quality language packs with a minimum of difficulty.

Developers who wish to contribute to the development of Flarum are expected to follow these guidelines. Third-party developers may also wish to follow them, so experienced Flarum localizers who undertake the translation of third-party extensions will find themselves working in familiar surroundings.

Namespacing en las traducciones

All translations are to be organized in categories, using namespacing keys arranged in up to three levels. Each level provides localizers with an important bit of information about where the translation is used:

➡ La clave de nivel superior indica qué componente utiliza la traducción.

The namespacing for translation keys used in official Flarum components, including bundled extensions, should match the name of the language pack locale file for the component in question. The namespaces for Flarum's non-extension components are fixed as shown below:

core:        # Traducciones utilizadas por el núcleo de Flarum
validation: # Traducciones utilizadas por el validador de Laravel

Translation keys used in an extension including any third-party extension need to be namespaced using the extension's name in vendor-package format where the flarum- and flarum-ext- prefixes are stripped from the package (e.g, flarum-tags for the Tags extension and foo-bar for a foo/flarum-ext-bar extension).

There should be only one first-level prefix in any locale file; it should be the first line in the locale file.

➡ La clave de segundo nivel indica qué interfaz utiliza la traducción.

Since Flarum doesn't have all that many interfaces, we've come up with a short list of second-level keys for you to choose from. We've included the more frequently used ones in the locale file template created with the extension skeleton. Below you will find the complete list, with explanations:

admin:       # Translations used by the admin interface.
forum: # Translations used by the forum user interface.
lib: # Translations used by either of the above.
views: # Translations used outside the normal JS client.
api: # Translations used in messages output by the API.
email: # Translations used in emails sent by Flarum.

The first four keys correspond roughly to the directories containing the code where the translations in that namespace will be used. (Most of your keys will probably go in admin or forum.) The remaining two keys are a bit different: the api namespace is for translations used in messages output by the API, while the email namespace contains the resources for all emails sent by the forum.

ref:         # Translations referenced by more than one key.
group: # Translations used as default group names.

These two keys don't correspond to interfaces; they're for translations that require special handling. We'll explain how to use the ref namespace when we talk about reusing translations. The group namespace holds the default group names, which are translated by the server rather than at the front end.

➡ La clave de tercer nivel indica qué parte de la interfaz de usuario utiliza la traducción.

The keys in this level are not so rigidly defined. Their main purpose is to chop the UI up into manageable chunks, so localizers can find the translations and see for themselves how they are used by the software. (Third-level keys aren't used in the ref and group namespaces, which don't need chopping.)

If you're modifying an existing location to add a new setting to the Settings page, for example you should copy our namespacing so experienced Flarum localizers will know at a glance exactly where the new translations are displayed. See English translations in flarum/core and bundled extensions for examples.

If your extension adds a new location such as a new dialog box you should feel free to create a new third-level key to namespace the translations that go there. Take a couple minutes to familiarize yourself with the namespacing in the locale files linked above, then create a new key that fits in with that scheme.

As a general rule, third-level keys should be short no more than one or two words and expressed in snake_case. They should be descriptive of the locations where the translations are used.

Cómo nombrar las claves de los identificadores

Like the third-level namespacing keys, identifier keys should be expressed in snake_case. ID keys should be arranged in alphabetical order within each namespace, so they'll be easy for developers to find. (There is one exception to this rule! ID keys in the email namespace should be listed just as they appear in your mail client: subject first, then body.)

The typical ID key consists of two parts a root and a suffix each of which may be omitted in certain circumstances. Just as the namespacing keys tell localizers where the translation is used, each part of the ID key provides a further bit of information about the translation:

➡ El sufijo indica cómo se utiliza la traducción.

We'll start with the suffix because it's the most important part of the key name. It tells localizers what sort of object they should look for when trying to find the translation in the interface. For example, the suffixes in the following list are used for GUI objects more or less related to user operations:

_button:        # Used for buttons (including dropdown menu items).
_link: # Used for links that are not shown graphically as buttons.
_heading: # Used for headings in tables and lists.
_label: # Used for the names of data fields, checkbox settings, etc. _placeholder: # Used for placeholder text displayed in fields.

These suffixes are used for informative or descriptive text elements:

_confirmation:  # Used for messages displayed to confirm an operation.
_message: # Used for messages that show the result of an operation.
_text: # Used for any text that is not a message, title, or tooltip.
_title: # Used for text displayed as the title of a page or modal.
_tooltip: # Used for text displayed when the user hovers over something.

And there are two suffixes that are used only in the email namespace:

_body:          # Used for the content of the email message.
_subject: # Used for the subject line of the email message.

The above is a complete listing of the suffixes available for use in locale files. You should take care to use them consistently, as doing so will make life easier for localizers. If you feel something is missing from the list, please file an issue with the developers; we'll consider adding a new suffix if the situation warrants it.

Suffixes should be omitted from ID keys for reused translations in the ref: namespace. This is because you can't be sure these translations will always be used in the same context. Adding a new context would mean changing the key name everywhere it's referenced so it's best to keep these translations generic.

➡ La raíz indica lo que dice la traducción.

In other words, it should be a brief summation of the translation's content. If the translation is a very short phrase no more than a few words long you may want to use it verbatim (in snake_case, of course). If it's very long, on the other hand, you should try to boil it down to as few words as possible.

In some cases, the summary may be replaced by a description of the object's function. This is commonly seen in buttons. The button that submits a form should be identified as a submit_button regardless of whether the translation says "OK" or "Save Changes". In a similar vein, the button that dismisses a dialog or message box is always a dismiss_button, even if it actually reads "OK" or "Cancel".

The root may also be omitted in certain cases usually when the suffix alone is sufficient to identify the translation. For example, it's unlikely that a page or dialog box will have more than one title and like as not, the content of the title is already given in the third-level namespacing! So the suffix can stand alone.

Suffixes are also sufficient to identify the subject and body components of an email message since each email will have only one subject line and one body. Note that the leading underscore is omitted in such cases: you would use just title, subject, or body as the ID key.

Reutilización de traducciones

Flarum's unique key references fulfill the same role as YAML anchors, but they're better in one respect: you can even reference keys in a different file! For this reason, you need to use the full translation key when referencing. Here's a more realistic example of how referencing works, complete with namespacing:

core:

forum:
header:
log_in_link: => core.ref.log_in

log_in:
submit_button: => core.ref.log_in
title: => core.ref.log_in

ref:
log_in: Log In

As you can see, we want to reuse a single translation in three different contexts (including two locations): (1) as a link displayed in the site header, (2) as a button displayed in the Log In modal, and (3) as the title of that modal. So all three of these keys have been set to reference the same full translation key.

The reused translation is identified by a key that omits the suffix as specified above and is placed in the ref namespace. The latter measure is needed to avoid conflicts that can occur if a reused translation is given the same name as a second-level namespacing key. (The email keys are a case in point.)

The ref namespace also makes it easy to track translation reuse. Imagine what would happen if you set the scalars for the button and title to reference core.forum.header.log_in_link instead:

core:

forum:
header:
log_in_link: => Log In

log_in:
submit_button: => core.forum.header.log_in_link # No hay que hacer referencia a las claves
title: => core.forum.header.log_in_link # que no están en "ref"

It would very easy to change the translation for the header link without realizing that you're also changing things in the Log In modal to say nothing of any extensions that might also be referencing that key! This sort of thing will be less likely to occur if you keep your reused translations in the ref namespace.

For this reason, any key references in the core resources must point to keys in the core.ref namespace. Key references in the resources for bundled extensions may point to either of two locations:

  • Las traducciones específicas de la extensión deben ir en el espacio de nombres ref del archivo de configuración regional de la extensión.

  • Las traducciones utilizadas tanto por la extensión como por el núcleo deben ir en el espacio de nombres core.ref.

Third-party extensions are welcome to reference keys in the core.ref namespace, but please be aware that we cannot add translations to this namespace based on reuse in third-party extensions. A third-party dev who wants to reuse a translation from a namespace other than core.ref will need to add a properly keyed duplicate translation to the extension's locale file.

No extension should ever reference a key from another extension, as doing so will result in a dependency.

One final caution: translation keys in the ref namespace should never be inserted directly in code. This is because localizers may end up creating a translation to replace every reference to a reused translation key in which case they would be within their rights to remove that key from the locale file. If such a key were being used in the code, it would end up without a matching resource to translate it!

Añadir comentarios

Comments (and empty lines) should be added to locale files so localizers will find them easier to navigate.

We've included some block comments in the locale file template included with the extension skeleton. They are there to remind developers of some basic concepts: translation keys should not be used in more than one place; keys for reused translations should never be inserted directly in code; and so forth.

You should add comment lines above every second- or third-level namespacing key, to give localizers a more complete description of the location covered by that namespace. When copying existing keys, be sure to copy the comment as well (and modify it if necessary). If you add a new third-level key, remember to preface it with a comment to explain the location being added.

You may also wish to add inline comments after a specific translation to provide localizers with further information about that translation (such as the fact that a translation can be pluralized if necessary).

Apéndice B: Codificación para el mundo

In this appendix, we'd like to offer a few tips that may help you to avoid some of the more common pitfalls in the internationalization process. Abstracting language from code is easily one of the more humdrum tasks a programmer has to deal with, but if you don't give due attention to the subtleties involved, you're likely to end up creating your localizers an unnecessary headache or two.

Of course, it's not just about making life easier for localizers. Because when they get headaches, they will come to you for help often months (or even years) after you've put the project behind you and moved on to something else! It's the sort of situation where an ounce or two of prevention may indeed be worth several pounds of cure further down the road for everybody involved.

It's probably impossible to avoid localization issues completely; there are just too many variables. But by following a few simple guidelines, you should be able to head off many issues before they happen.

¡Elimine todo el texto codificado!

This probably goes without saying. After all, if you're going to go to the trouble of extracting translations, you might as well finish the job, right? Well, yes, but that's easier said than done. It's really quite unusual to find a program that doesn't have at least a few bits of hardcoded text floating around somewhere.

Even tiny bits of text can cause problems for localizers. A comma here, a colon there perhaps a pair of brackets inserted to make the page more legible: such things can and do cause issues for localizers. After all, not all languages use the same glyphs for these things! Just one hardcoded space can be a problem for someone trying to translate the interface into a language that doesn't use spaces to separate words.

Generally speaking, any displayed text that isn't supplied by a variable or the result of a calculation must be included in the locale files. That's easily said, but actually doing it takes a bit of perseverance.

¡Evite la sintaxis codificada!

Hardcoded text isn't the only way that code can create problems for localizers. Hardcoded syntax issues occur when the code forces words into a specific order that just won't work in other languages. They are generally the result of using two translations where just one would be more appropriate.

For an example, let's look at the line of text that's printed near the bottom of the Log In modal:

Don't have an account? Don't have an account? Sign Up

We originally coded this line as two translations, which were separated by a hardcoded space:

before_sign_up_link: "Don't have an account?"
sign_up: Sign Up

There were good reasons for doing it this way. For one thing, it made it easy to turn the second half into a link. And since the second translation is reused elsewhere, keeping it separate seemed like a no-brainer.

But there were problems with this approach. The hardcoded space seemed likely to pose issues for some localizers, as mentioned above. And splitting this text into two strings would make it impossible to render the line as a single sentence with the link embedded in the middle:

If you don't have an account yet, you can sign up instead!

Since it seemed possible that a localizer might need this sort of flexibility, we opted to abstract the line as a single translation, using HTML-style tags to enclose the link text:

sign_up_text: "Don't have an account? <a>Sign Up</a>" <a>Sign Up</a>"

This puts the (formerly hardcoded) space in the translation, so localizers who don't need it can remove it. And the tags can be positioned freely within the translation, making this approach much more flexible.

The moral of this story is: when you've got two adjacent chunks of text which seem related to each other grammatically or even semantically you should think about finding a way to incorporate them both in a single translation. Your localizers may have cause to thank you!

We might also conclude that the presence of little bits of hardcoded text such as the hardcoded space in this example may be a sort of code smell indicating the presence of a deeper issue. This isn't always the case, but it's a possibility that's worth taking into consideration.

¡No pierda de vista los plurales!

They are surprisingly easy to overlook. Of course, the need for pluralization support is fairly obvious here:

A Toby le gusta esto. Te gusta esto. A Toby y Franz les gusta esto.

And the situation is complicated by the presence of the second-person pronoun, since it always takes the plural form in English, even when we're talking about one person. That's why the app.translator call is so complicated in the code that outputs the above sentences for the Likes extension.

Now look at this similar set of sentences, output by similar code in the Mentions extension:

Toby respondió a esto. Tú respondiste a esto. Toby y Franz respondieron a esto.

In English, the simple past tense is not affected by pluralization. Since the verb phrase is always the same, it would be fairly easy to ignore the plurals and use the app.translator.trans() method to produce the single necessary translation. Of course, that simply wouldn't work for the French localizer, who needs to inflect the verb differently in each of the above three sentences.

This is the sort of situation where the humdrum chore of language abstraction requires a bit of extra care and attention. Remember to ask yourself whether each noun (or pronoun) can be pluralized. If it can, make sure to pass an appropriate variable to the translator. Of course, you don't need to provide any variant translations in the English resources

mentioned_by_text: "{users} replied to this."       # Can be pluralized ...
mentioned_by_self_text: "{users} replied to this." # Can be pluralized ...

but it would be a very good idea to add a comment after the translations in question, to alert localizers to the fact that the code will support the addition of such variants, should they be necessary.

Reutilizar las traducciones, ¡no las claves!

If the namespacing keys combine to form a complete specification of where a translation is used, and the ID key specifies exactly how the translation is used and what it says, then it stands to reason that every full translation key must be unique. In other words: you should never use the same key more than once!

Although this may sound inefficient, there's a good reason for doing things this way: it's the easiest way to ensure that localizers will have the flexibility they need. If you reuse keys in your code, you'll eventually hit a snag. Your localizers will be unable to find a single expression that fits every context where you've used some key or other and then they'll start bugging you to fix your code.

Fortunately, you can avoid many such issues if you simply take care to namespace translations correctly, name your ID keys appropriately, and always reuse translations instead of keys. Though it may seem like a bother, in the long run, the standard format will make localization much easier for everyone.