¿Qué aprenderá?

Al finalizar este tutorial el estudiante estará en capacidad de internacionalizar y localizar una aplicación en Angular.

¿Qué construirá?

Una aplicación que se podrá localizar para al menos dos idiomas (inglés y español). Particularmente se localizarán las cadenas de texto de un componente en la aplicación.

¿Qué necesita?

Es necesario que conozca:

  1. La estructura de un proyecto de Angular: módulos, componentes, servicios.
  2. La funcionalidad del Angular-Cli.

Este tutorial lo hacemos como una extensión al presentado en el de rutas.

En este ejemplo se traducirán las cadenas del componente AppComponent.

Para esto, se requiere incluir el atributo i18n en las etiquetas correspondientes. Para nuestro caso, se incluye el atributo en los ítems del menú de navegación tal como lo muestra el código a continuación. Esto se debe realizar para todas las cadenas que necesitan ser traducidas.

<ul class="navbar-nav">
  <li class="nav-item">
    <a
       i18n
       class="nav-link active"
       aria-current="page"
       routerLink="/books/list"
       >Books</a>
  </li>
  <li class="nav-item">
     <a
        i18n
            class="nav-link active"
            routerLink="/authors/list"
            >Authors</a>
        </li>
        <li class="nav-item">
          <a
            i18n
            class="nav-link active"
            routerLink="/editorials/list"
            >Editorials</a
          >
        </li>
    </ul>

Para que la aplicación desarrollada tenga soporte para localización se requiere instalar el paquete localize de Angular con el siguiente comando: ng add @angular/localize

Como la localización afecta la aplicación tanto en desarrollo como en producción, se hace necesario actualizar el archivo src/environments/environment.ts. Incluya en este archivo los mismos valores que están actualmente en el archivo src/environments/environment.development.ts solamente cambiando el atributo de production a true.

El siguiente paso es la creación del archivo de traducción para cada uno de los idiomas que soportará la aplicación. Se ejecuta entonces en una terminal el comando ng extract-i18n --output-path src/locale

Esto crea, dentro de la carpeta src/locale un archivo denominado messages.xlf, el cual tiene la siguiente apariencia:

<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en-US" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="5224604665222373201" datatype="html">
        <source>Books</source>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">30,31</context>
        </context-group>
      </trans-unit>
      <trans-unit id="5505637243815082364" datatype="html">
        <source>Authors</source>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">37,38</context>
        </context-group>
      </trans-unit>
      <trans-unit id="6817816913775897810" datatype="html">
        <source>Editorials</source>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">44,45</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>

Por cada una de las etiquetas a las que le hayamos colocado el atributo i18n, se creará una entrada denominada trans-unit, la cual contiene una etiqueta source, que será el texto a traducir.

Como nuestra aplicación estará diseñada inicialmente para ser desplegada en inglés, vamos a realizar las traducciones para español. Entonces renombramos el archivo a messages.es.xlf y para cada etiqueta sources que aparezca, vamos a incluir una etiqueta target cuyo valor será la traducción, en este caso a español.

Por tanto, el archivo quedará así:

<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en-US" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="5224604665222373201" datatype="html">
        <source>Books</source>
        <target>Libros</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">30,31</context>
        </context-group>
      </trans-unit>
      <trans-unit id="5505637243815082364" datatype="html">
        <source>Authors</source>
        <target>Autores</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">37,38</context>
        </context-group>
      </trans-unit>
      <trans-unit id="6817816913775897810" datatype="html">
        <source>Editorials</source>
        <target>Editoriales</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">44,45</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>

El siguiente paso es hacer una serie de cambios en el archivo angular.json. Lo primero que se debe identificar es el nombre de la aplicación. En este caso el nombre se puede identificar en la línea 6 y es "book-detail".

Ahora en la ruta projects > book-detail se debe agregar un nuevo atributo denominado i18n con el siguiente valor:

"i18n": {
       "sourceLocale": "en-US",
       "locales": {
         "es": {
           "translation": "src/locale/messages.es.xlf",
           "baseHref": ""
         }
       }
     },

Ahora debemos agregar una configuración para el idioma español en la ruta projects > book-detail > architect > build > configurations. Esa configuración se hace en un nuevo atributo denominado "es" con el siguiente valor:

"es": {
    "localize": ["es"],
    "outputPath": "dist/book-detail-es/",
    "i18nMissingTranslation": "error"
 },

También se debe crear una nueva configuración para el idioma español en la ruta projects > book-detail > architect > serve > configurations. La nueva configuración se hace en el atributo "es" con el siguiente valor:

"es": {
             "buildTarget": "book-detail:build:es"
           },

El contenido actualizado de archivo angular.json quedará así:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "book-detail": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "standalone": false
        },
        "@schematics/angular:directive": {
          "standalone": false
        },
        "@schematics/angular:pipe": {
          "standalone": false
        }
      },
      "i18n": {
        "sourceLocale": "en-US",
        "locales": {
          "es": {
            "translation": "src/locale/messages.es.xlf",
            "baseHref": ""
          }
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:application",
          "options": {
            "outputPath": "dist/book-detail",
            "index": "src/index.html",
            "browser": "src/main.ts",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "src/styles.css"
            ],
            "scripts": [],
            "server": "src/main.server.ts",
            "prerender": true,
            "ssr": {
              "entry": "server.ts"
            }
          },
          "configurations": {
            "es": {
              "localize": ["es"],
              "outputPath": "dist/book-detail-es/",
              "i18nMissingTranslation": "error"
            },          
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.development.ts"
                }
              ]
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "es": {
              "buildTarget": "book-detail:build:es"
            },
            "production": {
              "buildTarget": "book-detail:build:production"
            },
            "development": {
              "buildTarget": "book-detail:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "buildTarget": "book-detail:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": [
              "zone.js",
              "zone.js/testing"
            ],
            "tsConfig": "tsconfig.spec.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          }
        }
      }
    }
  },
  "cli": {
    "analytics": "fe86c683-13a1-4b80-8329-485836da2ba3"
  }
}

Hasta ahora la aplicación se ha ejecutado con el comando ng serve (o ng s). Como agregamos una nueva configuración ahora puede ejecutar la versión en español con el comando:

ng s --configuration=es

Si ahora va al navegador y abre la dirección http://localhost:4200 podrá observar que los ítems en el menú de navegación han cambiado de idioma.