martinroob / ngx-i18nsupport

Some tooling to be used for Angular i18n workflows
MIT License
283 stars 69 forks source link

Angular 10 not supporting xliffmerge #180

Open coder123er opened 3 years ago

coder123er commented 3 years ago

Earlier we were using Angular 7 for our project and the following command worked for us for Localization

"extract-i18n": "ng xi18n projectName --i18n-format xlf --output-path src/locale --i18n-locale en && ng run projectName:xliffmerge"

The above command generated xlf file and also merged it with other localized files.

But now We have upgraded to Angular version 10 and on executing above command the terminal gives us error.

What is the new approach in Angular 10 to generate and merge localized files.

melcor76 commented 3 years ago

I'm running this command in Angular 11: "ng extract-i18n projectName --ivy --format xlf --outputPath outputFolder && ng run projectName:xliffmerge"

coder123er commented 3 years ago

@melcor76

"ng extract-i18n projectName --ivy --format xlf --outputPath outputFolder && ng run projectName:xliffmerge" Is the above command working for you??

In my case the first part of the command ng extract-i18n projectName --ivy --format xlf --outputPath outputFolder works fine and it generates message.xlf file but the second part of the command which is "ng run projectName:xliffmerge" gives an error as below

An unhandled exception occurred: Could not find the implementation for builder @ngx-i18nsupport/tooling:xliffmerge See "C:\Users\CENTRA~1\AppData\Local\Temp\ng-r6zg82\angular-errors.log" for further details.

I am stucked at merging the message.xlf file with other locale files.

melcor76 commented 3 years ago

@coder123er Yes, its working for me. It sounds from the error that its your configuration that might be missing something. I'll copy what I wrote about this in our docs so you can see if you are missing something.

Add Language

Use the i18n project option in your app's build configuration file (angular.json) to define locales for a project. The following sub-options identify the source language and tell the compiler where to find supported translations for the project:

For example, the following excerpt of an angular.json file sets the source locale to en-US and provides the path to the nb (Norwegian) locale translation file:

"projects": {
  "portal": {
    "i18n": {
      "sourceLocale": "en-US",
      "locales": {
        "nb": "i18n/messages.nb.xlf"
      }
    }
}

With this change you will be able to start the Norwegian version of the app and make sure the translations are working:

ng serve --configuration=nb --open

Xliffmerge

We use a tool called Xliffmerge to create the localized translation files. Add the new language to the configurations in angular.json under projects -> portal -> architect -> xliffmerge.

"xliffmerge": {
  "options": {
    "xliffmergeOptions": {
      "defaultLanguage": "en-US",
      "languages": ["nb"]
    }
  }
}

Apply specific build options for just one locale

To apply specific build options to only one locale, you can create a custom locale-specific configuration in angular.json by specifying a single locale as shown in the following example for Norwegian:

"build": {
  "configurations": {
    "nb": {
      "localize": ["nb"]
    }
  }
},
"serve": {
  "configurations": {
    "nb": {
      "browserTarget": "portal:build:nb"
    }
  }
}

You can then pass this configuration to the ng serve or ng build commands with --configuration=nb.

Add a new line to the language specific start commands (start:xx) in the scripts section in package.json. When done you can run the application with the new language with:

npm run start:nb

Language Files

To create translation files for each language we copy the source language file. For example, to create a Norwegian translation file make a copy of the messages.xlf file and rename it to messages.nb.xlf.

Doing this for all files every time there is a new translation is a bit tedious so we can use tooling for this. With the help of xliffmerge we can run a script that extracts the translations and merges them into the language files with the command:

npm run translations

We send all the translations to the i18n folder by providing the --output-path in the script.

parvdhunna commented 3 years ago

@melcor76 I tried with the above mentioned configurations, but faced the following error: image

could you please help me with this.

parvdhunna commented 3 years ago

Hi Martin,

When I use single locale as per the steps you have mentioned, I face the error as I shown in screenshot above.

My requirement is to translate multiple locale files at the same time so I need to use Xliffmerge tool. Could you please guide me , how can we do that on Angular 10. I encounter following error for using builder as Xliffmerge: image

I am providing my angular.json and package.json file here, so that you can take a look and pin point any configuration issue, if any. I am really stuck on this, any help would be appreciated.

angular.json

{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular": { "root": "", "sourceRoot": "src", "projectType": "application", "prefix": "app", "schematics": {}, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/angular", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json", "styles": [ { "input": "node_modules/@progress/kendo-theme-default/dist/all.css" }, "./node_modules/@angular/material/prebuilt-themes/purple-green.css", "node_modules/bootstrap/dist/css/bootstrap.min.css", "src/assets/css/scheduler.scss", "src/assets/css/styles.css", "src/assets/css/dev.css", "src/assets/css/responsive.css", "src/assets/css/mobile-chrome-fix.css", "src/assets/css/desktop.scss", "src/assets/css/mobile.scss" ], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "assets": [ "src/favicon.ico", { "glob": "*/", "input": "src/assets", "output": "./../assets" } ] }, "dev": { "assets": [ "src/favicon.ico", "src/assets" ] }, "es": { "aot": true, "outputPath": "dist/angular-es", "i18nFile": "src/i18n/messages.es.xlf", "i18nFormat": "xlf", "i18nLocale": "es" }, "fr": { "aot": true, "outputPath": "dist/angular-fr", "i18nFile": "src/i18n/messages.fr.xlf", "i18nFormat": "xlf", "i18nLocale": "fr" }, "de": { "aot": true, "outputPath": "dist/angular-de", "i18nFile": "src/i18n/messages.de.xlf", "i18nFormat": "xlf", "i18nLocale": "de" }, "en": { "aot": true, "i18nFile": "src/assets/locale/messages.en.xlf", "i18nFormat": "xlf", "i18nLocale": "en", "i18nMissingTranslation": "error" } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "angular:build" }, "configurations": { "production": { "browserTarget": "angular:build:production" }, "dev": { "browserTarget": "angular:build:dev" }, "de": { "browserTarget": "angular:build:de" }, "fr": { "browserTarget": "angular:build:fr" }, "es": { "browserTarget": "angular:build:es" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "angular:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", "karmaConfig": "src/karma.conf.js", "styles": [ "./node_modules/@angular/material/prebuilt-themes/purple-green.css", { "input": "node_modules/@progress/kendo-theme-default/dist/all.css" }, "src/styles.css" ], "scripts": [ "./node_modules/crypto-js/crypto-js.js" ], "assets": [ "src/favicon.ico", "src/assets" ] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "src/tsconfig.app.json", "src/tsconfig.spec.json" ], "exclude": [ "/node_modules/" ] } }, "xliffmerge": { "builder": "@ngx-i18nsupport/tooling:xliffmerge", "options": { "xliffmergeOptions": { "i18nFormat": "xlf", "srcDir": "src/assets/locale", "genDir": "src/assets/locale", "defaultLanguage": "en", "languages": [ "en", "de", "fr", "es" ] } } } } }, "angular-e2e": { "root": "e2e/", "projectType": "application", "architect": { "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular:serve" }, "configurations": { "production": { "devServerTarget": "angular:serve:production" } } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": "e2e/tsconfig.e2e.json", "exclude": [ "/node_modules/" ] } } } } }, "defaultProject": "angular" }


package.json

{ "name": "angular", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "buildci": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --prod --build-optimizer --output-path=\"dist/portal\"", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "post-build": "node ./build/post-build.js", "build-i18n:en_test": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --prod --sourceMap --build-optimizer --output-path=\"dist/portal/en\" --i18n-locale en --i18n-file=src/assets/locale/messages.en.xlf --i18n-format=xlf --baseHref /en/", "build-i18n:en": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --prod --build-optimizer --output-path=\"dist/portal/en\" --i18n-locale en --i18n-file=src/assets/locale/messages.en.xlf --i18n-format=xlf --baseHref /en/", "build-i18n:fr": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --prod --build-optimizer --output-path=\"dist/portal/fr\" --i18n-locale fr --i18n-file=src/assets/locale/messages.fr.xlf --i18n-format=xlf --baseHref /fr/", "build-i18n:es": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --prod --build-optimizer --output-path=\"dist/portal/es\" --i18n-locale es --i18n-file=src/assets/locale/messages.es.xlf --i18n-format=xlf --baseHref /es/", "build-i18n:de": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --prod --build-optimizer --output-path=\"dist/portal/de\" --i18n-locale de --i18n-file=src/assets/locale/messages.de.xlf --i18n-format=xlf --baseHref /de/", "build-i18n": "npm run build-i18n:en && npm run build-i18n:fr && npm run build-i18n:es && npm run build-i18n:de && npm run post-build", "build-i18n-dev": "npm run build-i18n:en", "build-i18n-dev-es": "npm run build-i18n:es", "build-i18n-dev-fr": "npm run build-i18n:fr", "build-i18n-dev-de": "npm run build-i18n:de", "extract-i18n": "ng xi18n angular --i18n-format xlf --output-path assets/locale --i18n-locale en && ng run angular:xliffmerge", "start-de": "ng serve --configuration=de", "start-fr": "ng serve --configuration=fr", "start-es": "ng serve --configuration=es", "dev": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng serve --configuration=dev" }, "private": true, "dependencies": { "@angular-devkit/architect": "^0.13.10", "@angular-devkit/build-angular": "^0.1001.1", "@angular-devkit/build-optimizer": "^0.13.10", "@angular-devkit/build-webpack": "^0.1001.1", "@angular-devkit/core": "^10.1.1", "@angular/animations": "^10.1.1", "@angular/cdk": "^10.1.1", "@angular/common": "~10.1.1", "@angular/compiler": "~10.1.1", "@angular/core": "~10.1.1", "@angular/forms": "~10.1.1", "@angular/http": "^7.2.16", "@angular/material": "^7.3.7", "@angular/platform-browser": "~10.1.1", "@angular/platform-browser-dynamic": "~10.1.1", "@angular/router": "~10.1.1", "@ngtools/webpack": "^10.1.1", "@ngx-i18nsupport/tooling": "^1.1.3", "@progress/kendo-angular-buttons": "^5.4.1", "@progress/kendo-angular-charts": "^4.1.3", "@progress/kendo-angular-common": "^1.2.2", "@progress/kendo-angular-dateinputs": "^4.2.2", "@progress/kendo-angular-dialog": "^4.1.3", "@progress/kendo-angular-dropdowns": "^4.2.6", "@progress/kendo-angular-editor": "^0.9.0", "@progress/kendo-angular-excel-export": "^3.1.3", "@progress/kendo-angular-grid": "^4.7.0", "@progress/kendo-angular-inputs": "^6.5.1", "@progress/kendo-angular-intl": "^2.0.1", "@progress/kendo-angular-l10n": "^2.0.1", "@progress/kendo-angular-layout": "^4.2.3", "@progress/kendo-angular-listview": "^0.2.1", "@progress/kendo-angular-notification": "^2.1.2", "@progress/kendo-angular-pdf-export": "^2.0.3", "@progress/kendo-angular-popup": "^3.0.5", "@progress/kendo-angular-resize-sensor": "3.2.0", "@progress/kendo-angular-scheduler": "^1.1.4", "@progress/kendo-angular-scrollview": "^3.0.1", "@progress/kendo-angular-toolbar": "^2.2.1", "@progress/kendo-angular-tooltip": "^2.1.2", "@progress/kendo-angular-treeview": "^4.1.2", "@progress/kendo-angular-upload": "^5.2.1", "@progress/kendo-data-query": "^1.5.3", "@progress/kendo-date-math": "^1.5.1", "@progress/kendo-drawing": "^1.6.0", "@progress/kendo-recurrence": "^1.0.1", "@progress/kendo-theme-default": "4.9.0", "@progress/kendo-angular-sortable": "^3.0.6", "@telerik/kendo-intl": "^1.5.2", "adal-angular4": "^4.0.9", "ajv": "^6.10.0", "angular-dual-listbox": "^5.0.1", "angular-gridster2": "^7.2.0", "angular7-csv": "^0.2.12", "bootstrap": "^4.5.2", "classlist.js": "^1.1.20150312", "core-js": "^2.5.4", "guid-typescript": "^1.0.9", "hammerjs": "^2.0.8", "jquery": "^3.5.1", "jwt-decode": "^2.2.0", "moment": "^2.24.0", "moment-timezone": "^0.5.28", "ngx-bootstrap": "^6.2.0", "ngx-bootstrap-modal": "^2.0.1", "ngx-chips": "2.0.2", "ngx-clipboard": "^14.0.1", "ngx-color-picker": "^7.5.0", "ngx-device-detector": "^1.3.20", "ngx-json-viewer": "^2.4.0", "ngx-perfect-scrollbar": "^7.2.0", "ngx-text-overflow-clamp": "0.0.1", "popper.js": "^1.15.0", "rxjs": "^6.6.3", "rxjs-compat": "^6.6.3", "stream": "0.0.2", "timers": "^0.1.1", "tslib": "^1.10.0", "web-animations-js": "^2.3.1", "xml-writer": "^1.7.0", "zone.js": "~0.10.3", "@progress/kendo-angular-pager": "^1.0.0", "@angular/localize": "~10.1.1" }, "devDependencies": { "@angular/cli": "^10.1.1", "@angular/compiler-cli": "~10.1.1", "@angular/language-service": "~10.1.1", "@angular/localize": "^10.1.1", "@ngx-i18nsupport/ngx-i18nsupport": "^1.1.3", "@types/jasmine": "~2.8.8", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", "codelyzer": "~4.5.0", "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", "karma": "~3.1.1", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~1.1.2", "karma-jasmine-html-reporter": "^0.2.2", "ngx-bootstrap": "^6.2.0", "node-sass": "^4.14.1", "protractor": "~5.4.0", "ts-node": "~7.0.1", "tslint": "~5.11.0", "typescript": "~4.0.2" } }

If you can spare some time, we can discuss the issue over a skype call so that you can take a look into my environment.

Thanks for you help, -Parv

melcor76 commented 3 years ago

@melcor76 I tried with the above mentioned configurations, but faced the following error: image

could you please help me with this.

@parvdhunna Looking at your configurations it looks you are doing it the old way. The error message is also complaining about your build configuration.

The Angular docs have excellent explanations how you should do it now: https://angular.io/guide/i18n#define-locales-in-the-build-configuration

Basically when quickly looking at it you should change this:

"fr": {
  "aot": true,
  "outputPath": "dist/angular-fr",
   "i18nFile": "src/i18n/messages.fr.xlf",
   "i18nFormat": "xlf",
    "i18nLocale": "fr"
},

To this:

"fr": {
  "localize": ["fr"]
}

You also need to add this part:

"i18n": {
        "sourceLocale": "en-US",
        "locales": {
          "da": "i18n/messages.da.xlf",
          "fi": "i18n/messages.fi.xlf",
          "sv": "i18n/messages.sv.xlf",
          "nb": "i18n/messages.nb.xlf"
        }
 }
coder123er commented 3 years ago

@melcor76 I have done changes in my angular.json file as mentioned by you and provided in the article.

This is my command which I execute

"extract-i18n": "ng xi18n angular --ivy --format xlf --outputPath assets/locale && ng run angular:xliffmerge"

And the error i get on executing it An unhandled exception occurred: Could not find the implementation for builder @ngx-i18nsupport/tooling:xliffmerge See "C:\Users\CENTRA~1\AppData\Local\Temp\ng-r6zg82\angular-errors.log" for further details.

If you can check there are any other changes to be done in angular.json Please find below my angular.json

{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular": { "root": "", "sourceRoot": "src", "projectType": "application", "prefix": "app", "schematics": {}, "i18n": { "sourceLocale": "en", "locales": { "de": "src/assets/locale/messages.de.xlf", "fr": "src/assets/locale/messages.fr.xlf", "es": "src/assets/locale/messages.es.xlf" } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "localize": true, "aot": true, "outputPath": "dist/angular", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json", "styles": [ { "input": "node_modules/@progress/kendo-theme-default/dist/all.css" }, "./node_modules/@angular/material/prebuilt-themes/purple-green.css", "node_modules/bootstrap/dist/css/bootstrap.min.css", "src/assets/css/scheduler.scss", "src/assets/css/styles.css", "src/assets/css/dev.css", "src/assets/css/responsive.css", "src/assets/css/mobile-chrome-fix.css", "src/assets/css/desktop.scss", "src/assets/css/mobile.scss" ], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "assets": [ "src/favicon.ico", { "glob": "*/", "input": "src/assets", "output": "./../assets" } ] }, "dev": { "assets": [ "src/favicon.ico", "src/assets" ] }, "es": { "localize": ["es"] }, "fr": { "localize": ["fr"] }, "de": { "localize": ["de"] }, "en": { "localize": ["en"] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "angular:build" }, "configurations": { "production": { "browserTarget": "angular:build:production" }, "dev": { "browserTarget": "angular:build:dev" }, "de": { "browserTarget": "angular:build:de" }, "fr": { "browserTarget": "angular:build:fr" }, "es": { "browserTarget": "angular:build:es" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "angular:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", "karmaConfig": "src/karma.conf.js", "styles": [ "./node_modules/@angular/material/prebuilt-themes/purple-green.css", { "input": "node_modules/@progress/kendo-theme-default/dist/all.css" }, "src/styles.css" ], "scripts": [ "./node_modules/crypto-js/crypto-js.js" ], "assets": [ "src/favicon.ico", "src/assets" ] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "src/tsconfig.app.json", "src/tsconfig.spec.json" ], "exclude": [ "/node_modules/" ] } }, "xliffmerge": { "builder": "@ngx-i18nsupport/tooling:xliffmerge", "options": { "xliffmergeOptions": { "i18nFormat": "xlf", "srcDir": "src/assets/locale", "genDir": "src/assets/locale", "defaultLanguage": "en", "languages": [ "en", "de", "fr", "es" ] } } } } }, "angular-e2e": { "root": "e2e/", "projectType": "application", "architect": { "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular:serve" }, "configurations": { "production": { "devServerTarget": "angular:serve:production" } } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": "e2e/tsconfig.e2e.json", "exclude": [ "/node_modules/" ] } } } } }, "defaultProject": "angular", "cli": { "analytics": false } }

melcor76 commented 3 years ago

@coder123er Can't see anything wrong. Reading the error message I would try reinstalling xliffmerge to the latest version.

laurantines commented 3 years ago

Check this tool https://www.codeandweb.com/babeledit/tutorials/how-to-translate-your-angular9-app-with-xlf-files

I have the same problem as you when updating to Angular 11, it seems that the xliffmerge tool is no longer supported and there are no updates in the Github repository. Babel's translation program merges the xlf files that you enter, establishing the primary language you can translate in as many languages ​​as you need and the string extraction is always performed on the primary language.

melcor76 commented 3 years ago

I have been working on an Angular i18n article and while doing it I did some experimentation to get Xliffmerge working on a new project. This is the important part of what I write in the article:

The documentation for Xliffmerge is targeting older versions of Angular but after some experimentation I found that it’s enough to install the @ngx-i18nsupport/tooling package:

npm install -D @ngx-i18nsupport/tooling --legacy-peer-deps

Then we can add new languages to the configurations in angular.json under projects -> projectName -> architect -> xliffmerge.

"xliffmerge": {
  "builder": "@ngx-i18nsupport/tooling:xliffmerge",
  "options": {
    "xliffmergeOptions": {
      "defaultLanguage": "en-US",
      "languages": ["nb"]
    }
  }
}

After adding new translations we can extract them and migrate them to our translation files by running this script:

ng extract-i18n && ng run projectName:xliffmerge

We get a couple of warnings running the script which tells us its working!

WARNING: merged 1 trans-units from master to "nb"
WARNING: please translate file "messages.nb.xlf" to target-language="nb"