Localización en Go con paquetes básicos

Crear una buena aplicación no es fácil. Cualquier aplicación única y útil que escriba, si al usuario no le gusta, entonces, como dicen, tiene un gran problema. A la mayoría de las personas no les gusta y ahuyentan todo lo que no entienden. A menudo, la interfaz de usuario y las letras son la punta visible del iceberg de su aplicación, según la cual el usuario la evalúa. Por lo tanto, la localización de todo lo que ve el usuario es extremadamente importante.


Recuerde que hace diez años, cuando Internet apenas comenzaba a entrar en la vida de las masas, y muchos de los gigantes de TI de la actualidad se encontraban en la etapa de inicio de enanos con un par de docenas de empleados, estaba en el orden de las cosas enviarle una carta al usuario en inglés. Y los usuarios simpatizaban con esto. Hoy, cuando todo está presente en Internet y no necesita tener siete tramos en la frente, tener una educación superior o saber inglés para usarlo, se considera una mala forma no admitir la localización en su aplicación. Por cierto, en nuestra empresa, la localización de todos los textos de IU ya se lleva a cabo en 20 idiomas y la lista de idiomas admitidos está en constante crecimiento.


En Go, como en un lenguaje bastante joven, todas las tendencias modernas de desarrollo web se implementan a nivel de paquetes básicos y no requieren "bailar con una pandereta" adicional. (Comencé a aprender Go hace unos años, pero aún recuerdo la sensación de "superpoderes descubiertos" que experimenté los primeros días después de aprender este idioma. Parecía que ahora puedo realizar cualquier tarea escribiendo solo un par de líneas).


Por supuesto, la localización tampoco se salvó en Go. La localización en él está disponible prácticamente "fuera de la caja" usando los paquetes básicos: golang.org/x/text/language , golang.org/x/text/message y golang.org/x/text/feature/plural . Veamos lo fácil que es en Go en solo media hora, con estos paquetes, puede implementar una tarea no trivial como la localización de mensajes.


Mirando hacia el futuro, diré que el propósito de este artículo es principalmente mostrar el poder y la belleza de Go y resaltar las características básicas de los paquetes de mensajes para trabajar con localizaciones. Si está buscando una solución para una aplicación de producción, es posible que desee una mejor biblioteca estándar . Las ventajas de go-i18n son muchas estrellas en github (entre ellas está la mía) y una gran flexibilidad. Sin embargo, existen argumentos en contra de su uso: es posible que no necesite toda esa flexibilidad y funcionalidad; por qué usar una biblioteca externa cuando todo ya está implementado en el lenguaje mismo; si ya tiene su propio sistema de traducción con sus propios formatos, esta biblioteca "tal cual" probablemente no funcionará y tendrá que modificarla de todos modos; y, al final, usar una biblioteca de terceros no es tan interesante e informativo como hacer algo usted mismo.


Formulamos los requisitos básicos para la tarea que se implementa. Hay: a) etiquetas en formato yaml: "nombre_etiqueta: texto de traducción" ; el idioma de traducción se especifica en el nombre del archivo, por ejemplo ru.yml; b) plantillas de correo electrónico en html. Es necesario, en función de los parámetros de entrada: configuración regional y matriz de datos, generar texto localizado del mensaje.


Y empecemos ... Pero primero, algunas palabras más sobre el paquete de mensajes (golang.org/x/text/message). Está diseñado para formatear la salida de cadenas localizadas. Message implementa la interfaz del paquete fmt estándar y puede reemplazarlo. Ejemplo de uso:

message.SetString(language.Russian, "toxic", "") message.SetString(language.Japanese, "toxic", "毒性") message.NewPrinter(language.Russian).Println(“toxic”) message.NewPrinter(language.Japanese).Println(“toxic”) //: // //毒性 

Para que el paquete "vea" la etiqueta, primero debe declararse. En el ejemplo, la función SetString se usa para esto. A continuación, se crea una impresora para el idioma seleccionado y se muestra directamente una cadena localizada.

Para resolver nuestro problema, podríamos generar un archivo go con todas las etiquetas, pero esto no es muy conveniente, ya que cuando agregue nuevas etiquetas, tendrá que volver a generar este archivo cada vez y volver a compilar la aplicación. Otra forma de comunicar mensajes sobre nuestras etiquetas es usar diccionarios. Un diccionario es una estructura que implementa la búsqueda de interfaz de búsqueda de etiquetas (cadena de clave) (cadena de datos, ok bool) .

La opción de diccionarios nos conviene. Primero, definimos la estructura del diccionario e implementamos la interfaz de búsqueda:

 type dictionary struct { Data map[string]string } func (d *dictionary) Lookup(key string) (data string, ok bool) { if value, ok := d.Data[key]; ok { return "\x02" + value, true } return "", false } 

Esparza todas las etiquetas de los archivos yaml en una colección de diccionarios, que es un mapa de formato de diccionario [lang] * , donde lang es una etiqueta de idioma en formato BCP47 .

 func parseYAMLDict() (map[string]catalog.Dictionary, error) { dir := "./translations" files, err := ioutil.ReadDir(dir) if err != nil { return nil, err } translations := map[string]catalog.Dictionary{} for _, f := range files { yamlFile, err := ioutil.ReadFile(dir + "/" + f.Name()) if err != nil { return nil, err } data := map[string]string{} err = yaml.Unmarshal(yamlFile, &data) if err != nil { return nil, err } lang := strings.Split(f.Name(), ".")[0] translations[lang] = &dictionary{Data: data} } return translations, nil } 

Instalamos la colección de diccionarios en la función init para que el paquete de mensajes pueda usar los diccionarios cuando se inicie la aplicación.

 func init() { dict, err := parseYAMLDict() if err != nil { panic(err) } cat, err := catalog.NewFromMap(dict) if err != nil { panic(err) } message.DefaultCatalog = cat } 

Entonces, en este momento, hemos logrado la disponibilidad de localización de etiquetas de nuestros archivos en cualquier parte del programa:

 message.NewPrinter(language.Russian).Println(“label_name”) 

Es hora de pasar a la segunda parte de la tarea y sustituir nuestras etiquetas localizadas en las plantillas de correo electrónico. Por ejemplo, considere un mensaje simple: una carta de bienvenida al registrar un usuario:
Hola Bill Smith!


Para el análisis, utilizamos otro paquete estándar: html / template . Al analizar plantillas en plantilla, puede configurar sus funciones a través de .Funcs () :

 template.New(tplName).Funcs(fmap).ParseFiles(tplName) 

Agregue una función a la plantilla que traducirá etiquetas y sustituirá variables en ellas, y llámela traducir . Código de análisis de plantilla:

 //  lang:=language.Russian //  tplName:=”./templates/hello.html” //   data := &struct { Name string LastName string }{Name: "Bill", LastName: "Smith"} fmap := template.FuncMap{ //   "translate": message.NewPrinter(lang).Sprintf, } t, err := template.New(tplName).Funcs(fmap).ParseFiles(tplName) if err != nil { panic(err) } buf := bytes.NewBuffer([]byte{}) if err := t.Execute(buf, data); err != nil { panic(err) } fmt.Println(buf.String()) 

La plantilla de carta resultante ./templates/hello.html:

 <!DOCTYPE html> <head> <title>{{translate "hello_subject"}}</title> </head> <body> {{translate "hello_msg" .Name .LastName}} </body> </html> 

Dado que en la traducción usamos la función Sprintf para la localización, las variables en el texto de la etiqueta se coserán usando la sintaxis de esta función. Por ejemplo, % s es una cadena, % d es un número entero.
Archivos etiquetados
en.yml

 hello_subject: Greeting mail hello_msg: Hello, %s %s! 

ru.yml

 hello_subject:   hello_msg: , %s %s! 

Sobre esto, en principio, eso es todo, ¡la localización de las letras está lista! Habiendo escrito solo unas pocas docenas de líneas de código, obtuvimos una funcionalidad poderosa que hace posible localizar letras de cualquier complejidad en docenas de idiomas.


Si le gustó este ejemplo, puede ir más allá e implementar independientemente la pluralización, utilizando nombres de variables para las variables en las etiquetas en lugar de % sy utilizando funciones en las etiquetas. Deliberadamente, no hice esto para dejar espacio a tu imaginación.


El código en los ejemplos está escrito específicamente para demostrar las capacidades del paquete de mensajes y no pretende ser ideal; una lista completa del código está disponible en github .

Source: https://habr.com/ru/post/es431604/


All Articles