Más en rubyonrails.org: Más Ruby on Rails

NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS SE PUBLICAN EN https://guides.rubyonrails.org.

Rails API de Internacionalización (I18n)

La gema Ruby I18n (abreviatura de internationalization) que se envía con Ruby on Rails (a partir de Rails 2.2) proporciona un marco extensible y fácil de usar para traducir su aplicación a un solo idioma personalizado que no sea inglés o para proporcionando soporte en varios idiomas en su aplicación.

El proceso de "internacionalización" generalmente significa extraer todas las cadenas y otros bits específicos de la configuración regional (como los formatos de fecha o moneda) de su aplicación. El proceso de "localización" significa proporcionar traducciones y formatos localizados para estos bits. [^ 1]

Entonces, en el proceso de internationalizing su aplicación Rails, tiene que:

En el proceso de localizar su aplicación, probablemente querrá hacer las siguientes tres cosas:

Esta guía lo guiará a través de la API I18n y contiene un tutorial sobre cómo internacionalizar una aplicación Rails desde el principio.

Después de leer esta guía, sabrá:

El marco Ruby I18n le proporciona todos los medios necesarios para la internacionalización/localización de su aplicación Rails. También puede usar varias gemas disponibles para agregar funciones o características adicionales. Consulte la gema rails-i18n para obtener más información.

1 How I18n in Ruby on Rails Works

La internacionalización es un problema complejo. Los lenguajes naturales difieren de tantas maneras (por ejemplo, en las reglas de pluralización) que es difícil proporcionar herramientas para resolver todos los problemas a la vez. Por esa razón, la API Rails I18n se centra en:

  • proporciona soporte para inglés y otros idiomas similares desde el primer momento.
  • facilita la personalización y ampliación de todo para otros idiomas

Como parte de esta solución, cada cadena estática en el marco de Rails, p. Ej. Mensajes de validación de Active Record, formatos de fecha y hora - se ha internacionalizado. Localización de una aplicación Rails significa definir valores traducidos para estas cadenas en los idiomas deseados.

Para localizar, almacenar y actualizar contenido en su aplicación (por ejemplo, traducir publicaciones de blog), consulte la sección Translating model content.

1.1 The Overall Architecture of the Library

Por lo tanto, la gema Ruby I18n se divide en dos partes:

  • La API pública del marco i18n: un módulo Ruby con métodos públicos que definen cómo funciona la biblioteca.
  • Un backend predeterminado (que se llama intencionalmente Simple backend) que implementa estos métodos

Como usuario, siempre debe acceder solo a los métodos públicos en el módulo I18n, pero es útil conocer las capacidades del backend.

Es posible intercambiar el backend Simple enviado por uno más potente, que almacenaría datos de traducción en una base de datos relacional, diccionario GetText o similar. Consulte la sección Using different backends a continuación.

1.2 The Public I18n API

Los métodos más importantes de la API I18n son:

translate # Lookup text translations
localize  # Localize Date and Time objects to local formats

Estos tienen los alias #t y #l, por lo que puede usarlos así:

I18n.t 'store.title'
I18n.l Time.now

También hay lectores y escritores de atributos para los siguientes atributos:

load_path                 # Announce your custom translation files
locale                    # Get and set the current locale
default_locale            # Get and set the default locale
available_locales         # Permitted locales available for the application
enforce_available_locales # Enforce locale permission (true or false)
exception_handler         # Use a different exception_handler
backend                   # Use a different backend

Entonces, ¡Internacionalicemos una aplicación Rails simple desde cero en los próximos capítulos!

2 Setup the Rails Application for Internationalization

Hay algunos pasos para comenzar a utilizar el soporte I18n para una aplicación Rails.

2.1 Configure the I18n Modu

Siguiendo la filosofía de convención sobre la configuración, Rails I18n proporciona cadenas de traducción predeterminadas razonables. Cuando se necesitan diferentes cadenas de traducción, se pueden anular.

Rails agrega todos los archivos .rb y .yml del directorio config/locales a la ** ruta de carga de traducciones **, automáticamente.

La configuración regional predeterminada en.yml en este directorio contiene un par de ejemplos de cadenas de traducción:

en:
  hello: "Hello world"

This means, that in the :en locale, the key hello will map to the Hello world string. Every string inside Rails is internationalized in this way, see for instance Active Model validation messages in the activemodel/lib/active_model/locale/en.yml file or time and date formats in the activesupport/lib/active_support/locale/en.yml file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend. Esto significa que en la configuración regional :en, la clave hello se asignará a la cadena Hello world. Cada cadena dentro de Rails se internacionaliza de esta manera, vea, por ejemplo, los mensajes de validación del modelo activo en el activemodel/lib/active_model/locale/en.yml o formatos de fecha y hora en activesupport/lib/active_support/locale/en.yml. Puede usar YAML o Ruby Hashes estándar para almacenar traducciones en el backend predeterminado (Simple).

La biblioteca I18n usará inglés como configuración regional predeterminada, es decir, si no se establece una configuración regional diferente, se usará :en para buscar traducciones.

La biblioteca i18n adopta un enfoque pragmático para las claves de configuración regional (después de some discussion), que incluye solo la parte locale ("idioma"), como en, :pl, no la parte region, como :" en-US " o :" en-GB ", que se utilizan tradicionalmente para separando "idiomas" y "entorno regional" o "dialectos". Muchas aplicaciones internacionales usan solo el elemento "idioma" de una configuración regional como cs,:th o :es (para checo, tailandés y español). Sin embargo, también existen diferencias regionales dentro de los diferentes grupos de idiomas que pueden ser importantes. Por ejemplo, en la configuración regional :" en-US " tendría $ como símbolo de moneda, mientras que en :" en-GB ", tendría £. Nada le impide separar la configuración regional de otra de esta manera: solo tiene que proporcionar la configuración regional completa "Inglés - Reino Unido" en un diccionario :" en-GB ".

La ruta de carga de traducciones (I18n.load_path) es una matriz de rutas a archivos que se cargarán automáticamente. La configuración de esta ruta permite personalizar la estructura del directorio de traducciones y el esquema de nombres de archivos.

El backend carga lentamente estas traducciones cuando se busca una traducción por primera vez. Este backend se puede intercambiar con otra cosa incluso después de que ya se hayan anunciado las traducciones.

Puede cambiar la configuración regional predeterminada y configurar las rutas de carga de traducciones en config/application.rb de la siguiente manera:

  config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
  config.i18n.default_locale = :de

La ruta de carga debe especificarse antes de buscar cualquier traducción. Para cambiar la configuración regional predeterminada de un inicializador en lugar de config/application.rb:

# config/initializers/locale.rb

# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]

# Permitted locales available for the application
I18n.available_locales = [:en, :pt]

# Set default locale to something other than :en
I18n.default_locale = :pt

Tenga en cuenta que agregar directamente a I18n.load_path en lugar de al i18n configurado de la aplicación no anulará las traducciones de gemas externas.

2.2 Managing the Locale across Requests

Es probable que una aplicación localizada deba brindar soporte para múltiples configuraciones regionales. Para lograr esto, la configuración regional debe establecerse al principio de cada solicitud para que todas las cadenas se traduzcan utilizando la configuración regional deseada durante la vida útil de esa solicitud.

La configuración regional predeterminada se usa para todas las traducciones a menos que se use I18n.locale= o I18n.with_locale.

I18n.locale puede filtrarse en las solicitudes posteriores atendidas por el mismo hilo / proceso si no se configura de manera consistente en todos los controladores. Por ejemplo, la ejecución de I18n.locale =: es en una solicitud POST tendrá efectos para todas las solicitudes posteriores a los controladores que no establecen la configuración regional, sino solo en ese hilo/proceso en particular. Por esa razón, en lugar de I18n.locale= puede usar I18n.with_locale, que no tiene este problema de fuga.

La configuración regional se puede establecer en un around_action en elApplicationController:

around_action :switch_locale

def switch_locale(&action)
  locale = params[:locale] || I18n.default_locale
  I18n.with_locale(locale, &action)
end

Este ejemplo ilustra esto usando un parámetro de consulta de URL para establecer la configuración regional (por ejemplo, http://example.com/books?locale=pt). Con este enfoque, http://localhost:3000?locale=pt representa la localización portuguesa, mientras que http://localhost:3000?locale=de carga una localización alemana.

La configuración regional se puede establecer mediante uno de los muchos enfoques diferentes.

2.2.1 Setting the Locale from the Domain Name

Una opción que tiene es establecer la configuración regional desde el nombre de dominio donde se ejecuta su aplicación. Por ejemplo, queremos que www.example.com cargue la configuración regional en inglés (o la predeterminada) y www.example.es para cargar la configuración regional en español. Por tanto, el nombre de dominio de nivel superior se utiliza para la configuración regional. Esto tiene varias ventajas:

  • La configuración regional es una parte obvia de la URL.
  • Las personas comprenden intuitivamente en qué idioma se mostrará el contenido.
  • Es muy trivial de implementar en Rails.
  • A los motores de búsqueda parece gustarles que el contenido en diferentes idiomas se encuentre en dominios diferentes e interconectados.

Puedes implementarlo así en tu ApplicationController:

around_action :switch_locale

def switch_locale(&action)
  locale = extract_locale_from_tld || I18n.default_locale
  I18n.with_locale(locale, &action)
end

# Get locale from top-level domain or return +nil+ if such locale is not available
# You have to put something like:
#   127.0.0.1 application.com
#   127.0.0.1 application.it
#   127.0.0.1 application.pl
# in your /etc/hosts file to try this out locally
def extract_locale_from_tld
  parsed_locale = request.host.split('.').last
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end

También podemos configurar la configuración regional desde el subdominio de una manera muy similar:

# Get locale code from request subdomain (like http://it.application.local:3000)
# You have to put something like:
#   127.0.0.1 gr.application.local
# in your /etc/hosts file to try this out locally
def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end

Si su aplicación incluye un menú de cambio de configuración regional, entonces tendría algo como esto:

link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}")

asumiendo que establecería APP_CONFIG[:deutsch_website_url] en algún valor como http://www.application.de.

Esta solución tiene las ventajas mencionadas anteriormente, sin embargo, es posible que no pueda o no desee proporcionar diferentes localizaciones ("versiones de idioma") en diferentes dominios. La solución más obvia sería incluir el código local en los parámetros de la URL (o la ruta de la solicitud).

2.2.2 Setting the Locale from URL Params

La forma más habitual de configurar (y pasar) la configuración regional sería incluirla en los parámetros de la URL, como hicimos en I18n.with_locale (params [: locale], & action) around_action en el primer ejemplo. Nos gustaría tener URL como www.example.com/books?Locale=ja o www.example.com/ja/books en este caso.

Este enfoque tiene casi el mismo conjunto de ventajas que establecer la configuración regional desde el nombre de dominio: es decir, que es RESTful y está de acuerdo con el resto de la World Wide Web. Sin embargo, requiere un poco más de trabajo para implementar.

Obtener la configuración regional de params y configurarla en consecuencia no es difícil; incluirlo en cada URL y, por lo tanto, ** pasarlo a través de las solicitudes ** es. Para incluir una opción explícita en cada URL, p. Ej. link_to (books_url (locale: I18n.locale)), sería tedioso y probablemente imposible, por supuesto.

Rails contiene infraestructura para "centralizar decisiones dinámicas sobre las URL" en su ApplicationController#default_url_options, que es útil precisamente en este escenario: nos permite establecer "valores predeterminados" para url_for y métodos auxiliares que dependen de él (mediante la implementación/anulación default_url_options).

Podemos incluir algo como esto en nuestro ApplicationController entonces:

# app/controllers/application_controller.rb
def default_url_options
  { locale: I18n.locale }
end

Todos los métodos auxiliares que dependen de url_for (por ejemplo, ayudantes para rutas con nombre como root_path o root_url, rutas de recursos como books_path o books_url, etc.) ahora incluirán automáticamente la configuración regional en la cadena de consulta, así: http://localhost:3001/?locale=ja.

Puede estar satisfecho con esto. Sin embargo, sí afecta la legibilidad de las URL cuando la configuración regional "cuelga" al final de cada URL en su aplicación. Además, desde el punto de vista de la arquitectura, la configuración regional suele estar jerárquicamente por encima de las otras partes del dominio de la aplicación: y las URL deben reflejar esto.

Probablemente desee que las URL se vean así: http://www.example.com/en/books (que carga la configuración regional en inglés) yhttp: // www.example.com / nl / books (que carga la configuración regional holandesa). Esto se puede lograr con la estrategia "over-ridingdefault_url_options "de arriba: solo tienes que configurar tus rutas con scope:

# config/routes.rb
scope "/:locale" do
  resources :books
end

Ahora, cuando llame al método books_path debería obtener"/en/books"(para la configuración regional predeterminada). Una URL como http://localhost:3001/nl/books debería cargar la configuración regional holandesa, luego, y las siguientes llamadas a books_path deberían devolver "/nl/books " (porque la configuración regional cambió).

Dado que el valor de retorno de default_url_options se almacena en caché por solicitud, las URL en un selector de configuración regional no se pueden generar invocando ayudantes en un bucle que establece el correspondiente I18n.locale en cada iteración. En su lugar, deje intacto I18n.locale y pase una opción explícita :locale al asistente, o edite request.original_fullpath.

Si no desea forzar el uso de una configuración regional en sus rutas, puede usar un alcance de ruta opcional (indicado por los paréntesis) así:

# config/routes.rb
scope "(:locale)", locale: /en|nl/ do
  resources :books
end

Con este enfoque, no obtendrá un Routing Error cuando acceda a sus recursos como http://localhost:3001/books sin una configuración regional. Esto es útil cuando desea utilizar la configuración regional predeterminada cuando no se especifica ninguna.

Por supuesto, debe tener especial cuidado con la URL raíz (generalmente "página de inicio" o "tablero") de su aplicación. Una URL como http://localhost:3001/nl no funcionará automáticamente, porque la declaración root to: "books#index" en tu routes.rb no tiene en cuenta la configuración regional. (Y con razón: solo hay una URL "root").

Probablemente necesite mapear URL como estas:

# config/routes.rb
get '/:locale' => 'dashboard#index'

Tenga especial cuidado con el orden de sus rutas, para que esta declaración de ruta no se "coma" a otras. (Es posible que desee agregarlo directamente antes de la declaración root: to).

Eche un vistazo a varias gemas que simplifican el trabajo con rutas: routing_filter, rails-translate-routes, route_translator.

2.2.3 Setting the Locale from User Preferences

Una aplicación con usuarios autenticados puede permitir a los usuarios establecer una preferencia de configuración regional a través de la interfaz de la aplicación. Con este enfoque, la preferencia de configuración regional seleccionada por un usuario se conserva en la base de datos y se utiliza para establecer la configuración regional para las solicitudes autenticadas de ese usuario.

around_action :switch_locale

def switch_locale(&action)
  locale = current_user.try(:locale) || I18n.default_locale
  I18n.with_locale(locale, &action)
end
2.2.4 Choosing an Implied Locale

Cuando no se ha establecido una configuración regional explícita para una solicitud (por ejemplo, a través de uno de los métodos anteriores), una aplicación debe intentar inferir la configuración regional deseada.

2.2.4.1 Inferring Locale from the Language Header

El encabezado HTTP Accept-Language indica el idioma preferido para la respuesta de la solicitud. Navegadores set this header value based on the user's language preference settings, lo que lo convierte en una buena primera opción al inferir una configuración regional.

Una implementación trivial de usar un encabezado Accept-Language sería:

def switch_locale(&action)
  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
  locale = extract_locale_from_accept_language_header
  logger.debug "* Locale set to '#{locale}'"
  I18n.with_locale(locale, &action)
end

private
  def extract_locale_from_accept_language_header
    request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
  end

En la práctica, se necesita un código más robusto para hacer esto de manera confiable. La biblioteca de Iain Hecker http_accept_language o de Ryan Tomayko locale el middleware en rack proporciona soluciones a este problema.

2.2.4.2 Inferring the Locale from IP Geolocation

La dirección IP del cliente que realiza la solicitud se puede utilizar para inferir la región del cliente y, por lo tanto, su ubicación. Se pueden utilizar servicios como GeoIP Lite Country o gemas como geocoder para implementar este enfoque.

En general, este enfoque es mucho menos confiable que usar el encabezado de idioma y no se recomienda para la mayoría de las aplicaciones web.

2.2.5 Storing the Locale from the Session or Cookies

Puede tener la tentación de almacenar la configuración regional elegida en una sesión o una cookie. Sin embargo, no hagas esto. La configuración regional debe ser transparente y formar parte de la URL. De esta manera, no romperá las suposiciones básicas de las personas sobre la web en sí: si envía una URL a un amigo, debería ver la misma página y el mismo contenido que usted. Una palabra elegante para esto sería que estás siendo RESTful. Lea más sobre el enfoque RESTful en los Stefan Tilkov's articles. A veces hay excepciones a esta regla y se analizan a continuación.

3 Internationalization and Localization

¡OKAY! Ahora ha inicializado la compatibilidad con I18n para su aplicación Ruby on Rails y le ha dicho qué configuración regional debe usar y cómo conservarla entre solicitudes.

A continuación, necesitamos internationalize nuestra aplicación abstrayendo cada elemento específico de la configuración regional. Finalmente, necesitamos localizar proporcionando las traducciones necesarias para estos resúmenes.

Dado el siguiente ejemplo:

# config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  around_action :switch_locale

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action)
  end
end
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = "Hello Flash"
  end
end
# app/views/home/index.html.erb
<h1>Hello World</h1>
<p><%= flash[:notice] %></p>

rails i18n demo untranslated

3.1 Abstracting Localized Code

Hay dos cadenas en nuestro código que están en inglés y que los usuarios aparecerán en nuestra respuesta ("Hello Flash" y "Hello World"). Para internacionalizar este código, estas cadenas deben ser reemplazadas por llamadas al ayudante # t de Rails con una clave apropiada para cada cadena:

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = t(:hello_flash)
  end
end
# app/views/home/index.html.erb
<h1><%= t :hello_world %></h1>
<p><%= flash[:notice] %></p>

Ahora, cuando se renderice esta vista, mostrará un mensaje de error que le indicará que faltan las traducciones de las claves :hello_world y :hello_flash.

rails i18n demo translation missing

Rails agrega un método auxiliar t (translate) a sus vistas para que no tenga que escribir I18n.t todo el tiempo. Además, este ayudante detectará las traducciones que falten y envolverá el mensaje de error resultante en un <span class ="translation_missing">.

3.2 Providing Translations for Internationalized Strings

Agregue las traducciones que faltan en los archivos del diccionario de traducción:

# config/locales/en.yml
en:
  hello_world: Hello world!
  hello_flash: Hello flash!

# config/locales/pirate.yml
pirate:
  hello_world: Ahoy World
  hello_flash: Ahoy Flash

Debido a que el default_locale no ha cambiado, las traducciones usan el locale :en y la respuesta muestra las cadenas en inglés:

rails i18n demo translated to English

Si la configuración regional se establece a través de la URL a la configuración regional pirata (http://localhost:3000?locale=pirate), la respuesta muestra las cadenas piratas:

rails i18n demo translated to pirate

NOTA: debe reiniciar el servidor cuando agregue nuevos archivos de configuración regional.

Puede usar archivos YAML (.yml) o simples Ruby (.rb) para almacenar sus traducciones en SimpleStore. YAML es la opción preferida entre los desarrolladores de Rails. Sin embargo, tiene una gran desventaja. YAML es muy sensible a los espacios en blanco y a los caracteres especiales, por lo que es posible que la aplicación no cargue su diccionario correctamente. Los archivos Ruby bloquearán su aplicación en la primera solicitud, por lo que puede encontrar fácilmente lo que está mal. (Si encuentra algún "problema extraño" con los diccionarios YAML, intente poner la parte relevante de su diccionario en un archivo Ruby).

Si sus traducciones se almacenan en archivos YAML, ciertas claves deben escaparse. Son:

  • cierto, encendido, sí
  • falso, apagado, no

Ejemplos:

# config/locales/en.yml
en:
  success:
    'true':  'True!'
    'on':    'On!'
    'false': 'False!'
  failure:
    true:    'True!'
    off:     'Off!'
    false:   'False!'
I18n.t 'success.true'  # => 'True!'
I18n.t 'success.on'    # => 'On!'
I18n.t 'success.false' # => 'False!'
I18n.t 'failure.false' # => Translation Missing
I18n.t 'failure.off'   # => Translation Missing
I18n.t 'failure.true'  # => Translation Missing

3.3 Passing Variables to Translations

Una consideración clave para internacionalizar con éxito una aplicación es Evite hacer suposiciones incorrectas sobre las reglas gramaticales al abstraer localizadas. código. Las reglas gramaticales que parecen fundamentales en un lugar pueden no ser ciertas en otro.

La abstracción incorrecta se muestra en el siguiente ejemplo, donde los supuestos son sobre el ordenamiento de las diferentes partes de la traducción. Tenga en cuenta que Rails proporciona un ayudante number_to_currency para manejar el siguiente caso.

# app/views/products/show.html.erb
<%= "#{t('currency')}#{@product.price}" %>
# config/locales/en.yml
en:
  currency: "$"

# config/locales/es.yml
es:
  currency: "€"

Si el precio del producto es 10, la traducción correcta al español es "10 €". en lugar de "10 €" pero la abstracción no puede darlo.

Para crear una abstracción adecuada, la gema I18n se envía con una característica llamada variable interpolación que le permite utilizar variables en definiciones de traducción y pasar los valores de estas variables al método de traducción.

La abstracción adecuada se muestra en el siguiente ejemplo:

# app/views/products/show.html.erb
<%= t('product_price', price: @product.price) %>
# config/locales/en.yml
en:
  product_price: "$%{price}"

# config/locales/es.yml
es:
  product_price: "%{price} €"

Todas las decisiones gramaticales y de puntuación se toman en la propia definición, por lo que la abstracción puede dar una traducción adecuada.

Las palabras clave default y scope están reservadas y no se pueden usar como nombres de variables. Si se usa, se genera una excepción I18n::ReservedInterpolationKey. Si una traducción espera una variable de interpolación, pero esta no se ha pasado a #translate, se genera una excepción I18n::MissingInterpolationArgument.

3.4 Adding Date/Time Formats

¡OKAY! Ahora agreguemos una marca de tiempo a la vista, para que también podamos hacer una demostración de la función de date/time localization. Para localizar el formato de hora, pasa el objeto Time a I18n.l o (preferiblemente) usa el asistente #l de Rails. Puede elegir un formato pasando la opción :formato; por defecto, se usa el formato :predeterminado.

# app/views/home/index.html.erb
<h1><%= t :hello_world %></h1>
<p><%= flash[:notice] %></p>
<p><%= l Time.now, format: :short %></p>

Y en nuestro archivo de traducciones piratas agreguemos un formato de hora (ya está allí en los valores predeterminados de Rails para inglés):

# config/locales/pirate.yml
pirate:
  time:
    formats:
      short: "arrrround %H'ish"

Entonces eso te daría:

rails i18n demo localized time to pirate

En este momento, es posible que deba agregar algunos formatos de fecha/hora más para que el backend I18n funcione como se espera (al menos para la configuración regional 'pirata'). Por supuesto, existe una gran posibilidad de que alguien ya haya hecho todo el trabajo ** traduciendo los valores predeterminados de Rails para su configuración regional **. Consulte el rails-i18n repository at GitHub para obtener un archivo de varios archivos de configuración regional. Cuando coloque dichos archivos en el directorio config/locales/, automáticamente estarán listos para su uso.

3.5 Inflection Rules for Other Locales

Rails le permite definir reglas de inflexión (como reglas de singularización y pluralización) para configuraciones regionales distintas del inglés. En config/initializers/inflections.rb, puede definir estas reglas para múltiples configuraciones regionales. El inicializador contiene un ejemplo predeterminado para especificar reglas adicionales para inglés; siga ese formato para otras configuraciones regionales como mejor le parezca.

3.6 Localized Views

Supongamos que tiene un BooksController en su aplicación. Su acción index muestra el contenido en la plantilla app/views/books/index.html.erb. Cuando pones una variante localizada de esta plantilla: index.es.html.erb en el mismo directorio, Rails renderizará el contenido en esta plantilla, cuando la configuración regional esté configurada en :es. Cuando la configuración regional se establece en la configuración regional predeterminada, se utilizará la vista genérica index.html.erb. (Las versiones de Future Rails bien pueden llevar esta localización automagic a los activos en public, etc.)

Puede hacer uso de esta función, p. Ej. cuando se trabaja con una gran cantidad de contenido estático, que sería torpe de poner dentro de los diccionarios YAML o Ruby. Sin embargo, tenga en cuenta que cualquier cambio que desee hacer más adelante en la plantilla debe propagarse a todos ellos.

3.7 Organization of Locale Files

Cuando utiliza SimpleStore predeterminado que se envía con la biblioteca i18n, los diccionarios se almacenan en archivos de texto sin formato en el disco. Poner traducciones para todas las partes de su aplicación en un archivo por configuración regional podría ser difícil de gestionar. Puede almacenar estos archivos en una jerarquía que tenga sentido para usted.

Por ejemplo, su directorio config/locales podría verse así:

|-defaults
|---es.yml
|---en.yml
|-models
|---book
|-----es.yml
|-----en.yml
|-views
|---defaults
|-----es.yml
|-----en.yml
|---books
|-----es.yml
|-----en.yml
|---users
|-----es.yml
|-----en.yml
|---navigation
|-----es.yml
|-----en.yml

De esta manera, puede separar el modelo y los nombres de los atributos del modelo del texto dentro de las vistas, y todo esto de los "valores predeterminados" (por ejemplo, formatos de fecha y hora). Otras tiendas de la biblioteca i18n podrían proporcionar diferentes medios de separación.

El mecanismo de carga de configuración regional predeterminado en Rails no carga archivos de configuración regional en diccionarios anidados, como lo tenemos aquí. Entonces, para que esto funcione, debemos decirle explícitamente a Rails que busque más:

  # config/application.rb
  config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

4 Overview of the I18n API Features

Debe tener una buena comprensión del uso de la biblioteca i18n ahora y saber cómo para internacionalizar una aplicación básica de Rails. En los siguientes capítulos, cubrir sus características con más profundidad.

Estos capítulos mostrarán ejemplos que utilizan tanto el método I18n.translate como el translate view helper method (teniendo en cuenta la característica adicional proporcionada por el método de ayuda de vista).

Se cubren características como estas:

  • buscar traducciones
  • interpolar datos en traducciones
  • traducciones pluralizadas
  • usando traducciones HTML seguras (ver solo el método auxiliar)
  • localización de fechas, números, moneda, etc.

4.1 Looking up Translations

4.1.1 Basic Lookup, Scopes, and Nested Keys

Las traducciones se buscan mediante claves que pueden ser tanto símbolos como cadenas, por lo que estas llamadas son equivalentes:

I18n.t :message
I18n.t 'message'

El método translate también toma una opción :scope que puede contener una o más claves adicionales que se usarán para especificar un "namespace" o alcance para una clave de traducción:

I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

Esto busca el mensaje :record_invalid en los mensajes de error de Active Record.

Además, tanto la clave como los ámbitos se pueden especificar como claves separadas por puntos como en:

I18n.translate "activerecord.errors.messages.record_invalid"
I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

Thus the following calls are equivalent:

4.1.2 Defaults

Cuando se da una opción :default, su valor se devolverá si falta la traducción:

I18n.t :missing, default: 'Not here'
# => 'Not here'

Si el valor :default es un símbolo, se utilizará como clave y se traducirá. Se pueden proporcionar varios valores por defecto. Se devolverá el primero que dé como resultado un valor.

Por ejemplo, lo siguiente primero intenta traducir la clave : missing y luego la clave:missing. Dado que ambos no dan un resultado, se devolverá la cadena "No aquí":

I18n.t :missing, default: [:also_missing, 'Not here']
# => 'Not here'
4.1.3 Bulk and Namespace Lookup

Para buscar varias traducciones a la vez, se puede pasar una matriz de claves:

I18n.t [:odd, :even], scope: 'errors.messages'
# => ["must be odd", "must be even"]

Además, una clave puede traducirse en un hash (potencialmente anidado) de traducciones agrupadas. Por ejemplo, uno puede recibir todos los mensajes de error de Active Record como un Hash con:

I18n.t 'activerecord.errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... }

Si desea realizar la interpolación en un hash masivo de traducciones, debe pasar deep_interpolation: true como parámetro. Cuando tenga el siguiente diccionario:

en:
  welcome:
    title: "Welcome!"
    content: "Welcome to the %{app_name}"

entonces la interpolación anidada se ignorará sin la configuración:

I18n.t 'welcome', app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}

I18n.t 'welcome', deep_interpolation: true, app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the book store"}
4.1.4 "Lazy" Lookup

Rails implementa una forma conveniente de buscar la configuración regional dentro de views. Cuando tenga el siguiente diccionario:

es:
  books:
    index:
      title: "Título"

puede buscar el valor books.index.title ** dentro de la plantilla **app/views/books/index.html.erb como esta (observe el punto):

<%= t '.title' %>

El alcance de la traducción automática por parcial solo está disponible desde el método de ayuda de vista translate.

La búsqueda "Lazy" también se puede utilizar en controladores:

en:
  books:
    create:
      success: Book created!

Esto es útil para configurar mensajes flash, por ejemplo:

class BooksController < ApplicationController
  def create
    # ...
    redirect_to books_url, notice: t('.success')
  end
end

4.2 Pluralization

En muchos idiomas, incluido el inglés, solo hay dos formas, un singular y un plural, para una cadena dada, p. ej. "1 mensaje" y "2 mensajes". Otros idiomas (Arabic, Japanese, Russian y muchas más) tienen diferentes gramáticas que tienen menos plural forms. Por tanto, la API I18n proporciona una característica de pluralización flexible.

La variable de interpolación :count tiene un papel especial en el sentido de que se interpola a la traducción y se utiliza para elegir una pluralización de las traducciones de acuerdo con las reglas de pluralización definidas en el backend de pluralización. De forma predeterminada, solo se aplican las reglas de pluralización en inglés.

I18n.backend.store_translations :en, inbox: {
  zero: 'no messages', # optional
  one: 'one message',
  other: '%{count} messages'
}
I18n.translate :inbox, count: 2
# => '2 messages'

I18n.translate :inbox, count: 1
# => 'one message'

I18n.translate :inbox, count: 0
# => 'no messages'

El algoritmo para pluralizaciones en : en es tan simple como:

lookup_key = :zero if count == 0 && entry.has_key?(:zero)
lookup_key ||= count == 1 ? :one : :other
entry[lookup_key]

La traducción indicada como :one se considera singular, y el :other se usa como plural. Si el recuento es cero y hay una entrada :zero, se utilizará en lugar de :other.

Si la búsqueda de la clave no devuelve un Hash adecuado para la pluralización, se genera una excepción I18n::InvalidPluralizationData.

4.2.1 Locale-specific rules

La gema I18n proporciona un backend de pluralización que se puede utilizar para habilitar reglas específicas de la configuración regional. Incluirlo al backend simple, luego agregue los algoritmos de pluralización localizados al almacén de traducción, como i18n.plural.rule.

I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :pt, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } }
I18n.backend.store_translations :pt, apples: { one: 'one or none', other: 'more than one' }

I18n.t :apples, count: 0, locale: :pt
# => 'one or none'

Alternativamente, la gem separada rails-i18n can be used to provide a fuller set of locale-specific pluralization rules.

4.3 Setting and Passing a Locale

La configuración regional se puede establecer pseudo-globalmente en I18n.locale (que usaThread.current como, por ejemplo, Time.zone) o se puede pasar como una opción a #translate y #localize . If no locale is passed, I18n.locale is used:

I18n.locale = :de
I18n.t :foo
I18n.l Time.now

Pasar explícitamente una configuración regional:

I18n.t :foo, locale: :de
I18n.l Time.now, locale: :de

The I18n.locale defaults toI18n.default_locale which defaults to: en. The default locale can be set like this:

I18n.default_locale = :de

4.4 Using Safe HTML Translations

Las claves con un sufijo '_html' y las claves llamadas 'html' están marcadas como seguras para HTML. Cuando los use en las vistas, el HTML no se escapará.

# config/locales/en.yml
en:
  welcome: <b>welcome!</b>
  hello_html: <b>hello!</b>
  title:
    html: <b>title!</b>
# app/views/home/index.html.erb
<div><%= t('welcome') %></div>
<div><%= raw t('welcome') %></div>
<div><%= t('hello_html') %></div>
<div><%= t('title.html') %></div>

Sin embargo, la interpolación escapa según sea necesario. Por ejemplo, dado:

en:
  welcome_html: "<b>Welcome %{username}!</b>"

puede pasar de forma segura el nombre de usuario establecido por el usuario:

<%# This is safe, it is going to be escaped if needed. %>
<%= t('welcome_html', username: @current_user.username) %>

Las cadenas seguras, por otro lado, se interpolan literalmente.

La conversión automática a texto de traducción segura HTML solo está disponible desde el método auxiliar de visualización translate.

i18n demo html safe

4.5 Translations for Active Record Models

Puede utilizar los métodos Model.model_name.human y Model.human_attribute_name(atributo) para buscar de forma transparente las traducciones de sus nombres de modelo y atributo.

For example when you add the following translations:

en:
  activerecord:
    models:
      user: Customer
    attributes:
      user:
        login: "Handle"
      # will translate User attribute "login" as "Handle"

Entonces User.model_name.human (count: 2) devolverá "Clientes". Con count: 1 o sin params devolverá" Customer ".

Si está utilizando una clase que incluye ActiveModel y no hereda de ActiveRecord::Base, reemplace activerecord por activemodel en las rutas clave anteriores.

4.5.1 Error Message Scopes

Los mensajes de error de validación de Active Record también se pueden traducir fácilmente. Active Record le brinda un par de espacios de nombres donde puede colocar las traducciones de sus mensajes para proporcionar diferentes mensajes y traducciones para ciertos modelos, atributos y / o validaciones. También tiene en cuenta de forma transparente la herencia de una sola tabla.

Esto le brinda un medio bastante poderoso para ajustar de manera flexible sus mensajes a las necesidades de su aplicación.

Considere un modelo de usuario con una validación para el atributo de nombre como este:

class User < ApplicationRecord
  validates :name, presence: true
end

La clave para el mensaje de error en este caso es :blank. Active Record buscará esta clave en los espacios de nombres:

activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
errors.attributes.[attribute_name]
errors.messages

Por lo tanto, en nuestro ejemplo probará las siguientes claves en este orden y devolverá el primer resultado:

activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

Cuando sus modelos utilizan adicionalmente la herencia, los mensajes se buscan en la cadena de herencia.

Por ejemplo, puede tener un modelo de administrador heredado de Usuario:

class Admin < User
  validates :name, presence: true
end

Entonces Active Record buscará mensajes en este orden:

activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

De esta manera, puede proporcionar traducciones especiales para varios mensajes de error en diferentes puntos de la cadena de herencia de sus modelos y en los atributos, modelos o ámbitos predeterminados.

4.5.2 Error Message Interpolation

El nombre del modelo traducido, el nombre del atributo traducido y el valor siempre están disponibles para interpolación como model, attribute y value respectivamente.

Entonces, por ejemplo, en lugar del mensaje de error predeterminado"cannot be blank", puede usar el nombre del atributo así: "Please fill in your %{attribute}".

  • count, cuando esté disponible, se puede usar para pluralizar si está presente:
validation with option message interpolation
confirmation - :confirmation attribute
acceptance - :accepted -
presence - :blank -
absence - :present -
length :within, :in :too_short count
length :within, :in :too_long count
length :is :wrong_length count
length :minimum :too_short count
length :maximum :too_long count
uniqueness - :taken -
format - :invalid -
inclusion - :inclusion -
exclusion - :exclusion -
associated - :invalid -
non-optional association - :required -
numericality - :not_a_number -
numericality :greater_than :greater_than count
numericality :greater_than_or_equal_to :greater_than_or_equal_to count
numericality :equal_to :equal_to count
numericality :less_than :less_than count
numericality :less_than_or_equal_to :less_than_or_equal_to count
numericality :other_than :other_than count
numericality :only_integer :not_an_integer -
numericality :odd :odd -
numericality :even :even -

4.6 Translations for Action Mailer E-Mail Subjects

Si no pasa un asunto al método mail, Action Mailer intentará encontrar en sus traducciones. La búsqueda realizada usará el patrón <mailer_scope>.<action_name>.subject para construir la clave.

# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    #...
  end
end
en:
  user_mailer:
    welcome:
      subject: "Welcome to Rails Guides!"

Para enviar parámetros a la interpolación, use el método default_i18n_subject en el correo.

# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    mail(to: user.email, subject: default_i18n_subject(user: user.name))
  end
end
en:
  user_mailer:
    welcome:
      subject: "%{user}, welcome to Rails Guides!"

4.7 Overview of Other Built-In Methods that Provide I18n Support

Rails usa cadenas fijas y otras localizaciones, como cadenas de formato y otra información de formato en un par de ayudantes. He aquí una breve descripción general.

4.7.1 Action View Helper Methods
  • distance_of_time_in_words traduce y pluraliza su resultado e interpola el número de segundos, minutos, horas, etc. Consulte las traducciones de datetime.distance_in_words.

  • datetime_select y select_month utilice nombres de meses traducidos para completar la etiqueta de selección resultante. Consulte date.month_names para ver las traducciones. datetime_select también busca la opción de pedido de date.order (a menos que pase la opción explícitamente). Todos los ayudantes de selección de fecha traducen el mensaje usando las traducciones en el alcance datetime.prompts alcance si es aplicable.

  • Los number_to_currency, number_with_precision, number_to_percentage, number_with_delimiter, y number_to_human_size los ayudantes usan la configuración de formato de número ubicada en el alcance number.

4.7.2 Active Model Methods
  • model_name.human y human_attribute_name utilizar traducciones para nombres de modelos y nombres de atributos si están disponibles en el alcance activerecord.models. También admiten traducciones de nombres de clases heredados (por ejemplo, para su uso con STI) como se explicó anteriormente en "Error message scopes".

  • ActiveModel::Errors#generate_message (que se utiliza en las validaciones de Active Model pero también se puede utilizar manualmente) model_name.human y human_attribute_name (véase más arriba). También traduce el mensaje de error y admite traducciones para nombres de clases heredados como se explicó anteriormente en "Error message scopes".

  • ActiveModel::Errors#full_messages prepends the attribute name to the error message using a separator that will be looked up from errors.format (and which defaults to "%{attribute} %{message}").

4.7.3 Active Support Methods
  • Array#to_sentence utiliza la configuración de formato como se indica en el alcance support.array.

5 How to Store your Custom Translations

El backend simple que se envía con Active Support le permite almacenar traducciones tanto en formato Ruby como en YAML. 2

Por ejemplo, un Ruby Hash que proporcione traducciones puede verse así:

{
  pt: {
    foo: {
      bar: "baz"
    }
  }
}

El archivo YAML equivalente se vería así:

pt:
  foo:
    bar: baz

Como puede ver, en ambos casos la clave de nivel superior es la configuración regional. :foo es una clave de espacio de nombres y :bar es la clave para la traducción "baz".

Aquí hay un ejemplo "real" del archivo YAML de traducciones en.yml de Active Support:

en:
  date:
    formats:
      default: "%Y-%m-%d"
      short: "%b %d"
      long: "%B %d, %Y"

Por lo tanto, todas las siguientes búsquedas equivalentes devolverán el formato de fecha :short "%b %d":

I18n.t 'date.formats.short'
I18n.t 'formats.short', scope: :date
I18n.t :short, scope: 'date.formats'
I18n.t :short, scope: [:date, :formats]

Generalmente recomendamos usar YAML como formato para almacenar traducciones. Sin embargo, hay casos en los que desea almacenar ruby ​​lambdas como parte de sus datos de configuración regional, por ejemplo para formatos de fecha especiales.

6 Customize your I18n Setup

6.1 Using Different Backends

Por varias razones, el backend simple enviado con Active Support solo hace "lo más simple que podría funcionar" para Ruby on Rails3 ... lo que significa que solo se garantiza que funcione para inglés y, como efecto secundario, idiomas que son muy similares al inglés. Además, el backend simple solo es capaz de leer traducciones, pero no puede almacenarlas dinámicamente en ningún formato.

Sin embargo, eso no significa que esté atrapado con estas limitaciones. La gem Ruby I18n hace que eso sea fácil de intercambiar la implementación de backend simple con algo más que se adapte mejor a sus necesidades, pasando una instancia de backend al setter I18n.backend=.

Por ejemplo, puede reemplazar el backend simple con el backend de cadena para encadenar varios backend juntos. Esto es útil cuando desea utilizar traducciones estándar con un backend simple pero almacenar traducciones de aplicaciones personalizadas en una base de datos u otros backend.

Con el backend de Chain, puede usar el backend de Active Record y volver al backend simple (predeterminado):

I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)

6.2 Using Different Exception Handlers

La API I18n define las siguientes excepciones que generarán los backends cuando se produzcan las condiciones inesperadas correspondientes:

MissingTranslationData       # no translation was found for the requested key
InvalidLocale                # the locale set to I18n.locale is invalid (e.g. nil)
InvalidPluralizationData     # a count option was passed but the translation data is not suitable for pluralization
MissingInterpolationArgument # the translation expects an interpolation argument that has not been passed
ReservedInterpolationKey     # the translation contains a reserved interpolation variable name (i.e. one of: scope, default)
UnknownFileType              # the backend does not know how to handle a file type that was added to I18n.load_path

El API I18n detectará todas estas excepciones cuando se introduzcan en el backend y las pase al método default_exception_handler. Este método volverá a generar todas las excepciones excepto las excepciones "MissingTranslationData". Cuando se detecta una excepción "MissingTranslationData", devolverá la cadena del mensaje de error de la excepción que contiene la clave / alcance que falta.

El razón de esto es que durante el desarrollo, por lo general, querrá que sus vistas aún se muestren aunque falte una traducción.

Sin embargo, en otros contextos, es posible que desee cambiar este comportamiento. P.ej. el manejo de excepciones predeterminado no permite detectar fácilmente las traducciones que faltan durante las pruebas automatizadas. Para este propósito, se puede especificar un manejador de excepciones diferente. El manejador de excepciones especificado debe ser un método en el módulo I18n o una clase con el método #call:

module I18n
  class JustRaiseExceptionHandler < ExceptionHandler
    def call(exception, locale, key, options)
      if exception.is_a?(MissingTranslation)
        raise exception.to_exception
      else
        super
      end
    end
  end
end

I18n.exception_handler = I18n::JustRaiseExceptionHandler.new

Esto volvería a generar solo la excepción MissingTranslationData, pasando todas las demás entradas al controlador de excepciones predeterminado.

Sin embargo, si está utilizando I18n::Backend::Pluralization, este controlador también generará la excepción I18n::MissingTranslationData: translation missing: en.i18n.plural.rule que normalmente debería ignorarse para volver a la pluralización predeterminada regla para la configuración regional en inglés. Para evitar esto, puede usar una verificación adicional para la clave de traducción:

if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
  raise exception.to_exception
else
  super
end

Otro ejemplo en el que el comportamiento predeterminado es menos deseable es Rails TranslationHelper, que proporciona el método #t (además de #translate). Cuando se produce una excepción "MissingTranslationData" en este contexto, el asistente envuelve el mensaje en un intervalo con la clase CSS translation_missing.

Para hacerlo, el ayudante fuerza a I18n#translate a generar excepciones sin importar qué controlador de excepciones se defina estableciendo la opción :raise:

I18n.t :foo, raise: true # always re-raises exceptions from the backend

7 Translating Model Content

La API I18n descrita en esta guía está diseñada principalmente para traducir cadenas de interfaz. Si está buscando traducir contenido modelo (por ejemplo, publicaciones de blog), necesitará una solución diferente para ayudar con esto.

Varias gemas pueden ayudar con esto:

  • Globalize: Almacene las traducciones en tablas de traducción independientes, una para cada modelo traducido
  • Mobility: Brinda soporte para almacenar traducciones en muchos formatos, incluidas tablas de traducción, columnas json (PostgreSQL), etc.
  • Traco: Columnas traducibles almacenadas en la propia tabla del modelo

8 Conclusion

En este punto, debería tener una buena descripción general de cómo funciona el soporte de I18n en Ruby on Rails y estar listo para comenzar a traducir su proyecto.

9 Contributing to Rails I18n

La compatibilidad con I18n en Ruby on Rails se introdujo en la versión 2.2 y aún está evolucionando. El proyecto sigue la buena tradición de desarrollo de Ruby on Rails de desarrollar soluciones en gems y aplicaciones reales primero, y solo luego seleccionar las mejores de las características más útiles para su inclusión en el núcleo.

Por lo tanto, alentamos a todos a experimentar con nuevas ideas y características en gems u otras bibliotecas y ponerlas a disposición de la comunidad. (¡No olvide anunciar su trabajo en nuestra mailing list!)

Si encuentra que falta su propia configuración regional (idioma) en nuestro example translations data repositorio para Ruby on Rails, por favor fork el repositorio, agregue sus datos y envíe una pull request.

10 Resources

  • Google group: rails-i18n - La lista de correo del proyecto.
  • GitHub: rails-i18n - Repositorio de código y rastreador de problemas para el proyecto rails-i18n. Lo más importante es que puede encontrar muchas example transalations para Rails que deberían funcionar para su aplicación en la mayoría de los casos.
  • GitHub: i18n - Repositorio de código y rastreador de problemas para la gema i18n.

11 Authors

12 Footnotes

1 O, para citar Wikipedia: "Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text."

2 Otros backends pueden permitir o requerir el uso de otros formatos, p. Ej. un backend GetText podría permitir leer archivos GetText.

3 Una de estas razones es que no queremos implicar una carga innecesaria para las aplicaciones que no necesitan ninguna capacidad de I18n, por lo que debemos mantener la biblioteca I18n lo más simple posible para el inglés. Otra razón es que es prácticamente imposible implementar una solución única para todos los problemas relacionados con I18n para todos los idiomas existentes. Por lo tanto, una solución que nos permita intercambiar toda la implementación fácilmente es apropiada de todos modos. Esto también hace que sea mucho más fácil experimentar con funciones y extensiones personalizadas.

Comentarios Sobre el Contenido

Las guías de rieles se administran y publican en latinadeveloper/railsguides.es en GitHub.

Si lee esta guía y encuentra algún texto o código incorrecto que le interese, no dude en enviar una solicitud de extracción en el repositorio anterior. Consulte el archivo README en GitHub para saber cómo enviar una solicitud de extracción. Please contribute if you see any typos or factual errors.