Environment Setup
In the example I’m going to show you, I’ll use Bower and Gulp to make our development environment as more automated and flexible as possible. If they are not installed on your system yet or if you have never used them in your development workflow, I highly recommend to install and start learning more about them. Here is a list of articles that could be useful for this purpose:- Package Management for the Browser with Bower
- How to Grunt and Gulp Your Way to Workflow Automation
- Kickstart Your AngularJS Development with Yeoman, Grunt and Bower
bower init
in the command line inside a project directory that we’ll call multilingualwithangular
. bower init
will interactively create a manifest file called bower.json
which will include some information about the project as well as a list of the previously installed front-end dependencies.
The next step is to install the initial required packages.
bower install angular angular-translate --save
Let’s set up Gulp and install these basic packages. First we need to run the command npm init
and follow few simple steps to create a package.json
file which will include some information about the project and how to manage Node.js modules.
Next, we’ll install Gulp within the project:
npm install gulp --save-dev
We’ll also need some Gulp dependencies for JavaScript and Sass and other automation tools.
npm install gulp-sass gulp-uglify gulp-concat run-sequence browser-sync --save-dev
At this point, we have to create an empty gulpfile.js
configuration file within the project directory. It’ll be used to define our Gulp tasks such as JavaScript and Sass. You can take a look at the complete configuration file in my GitHub repository.
In the JavaScript task we’ll add two files, angular
and angular-translate
, plus the main JavaScript file inside a /js
directory. Then, we’ll concatenate them together and use a library for Node.js called Uglify to compress and reduce the size of our file.
'use strict';
var gulp = require('gulp');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var runSequence = require('run-sequence');
var browserSync = require('browser-sync');
gulp.task('js', function(){
return gulp.src([
'./bower_components/angular/angular.js',
'./bower_components/angular-translate/angular-translate.js',
'./js/app.js'])
.pipe(concat('app.min.js'))
.pipe(uglify())
.pipe(gulp.dest('./js'))
});
gulp.task('serve', function() {
browserSync({
server: {
baseDir: "./"
}
});
});
gulp.task('build', [], function() {
runSequence('js');
});
gulp.task('default', ['build'], function() {});
Once done, we can run the gulp build
task we’ve previously created. It’ll run the js
task and then generate a /js/app.min.js
file that will be included in a simple HTML file.
<!DOCTYPE HTML>
<html>
<head>
<title>Multilingual AngularJS</title>
<meta charset="utf-8">
</head>
<body>
<script src="js/app.min.js"></script>
</body>
</html>
To open the project in a localhost environment, run gulp serve
and then this will automatically open a browser tab directed to localhost:3000.
Adding Translation Using Angular-Translate
With these first configuration tasks in place, it’s time to take a step forward and add the translation support for the application text. We’ll work with Arabic and English as our main languages. They are completely different languages in regard of grammar, syntax, and in their writing directions (Right-to-Left Arabic and Left-to-Right English). angular-translate is an AngularJS module that we can use to translate the text. It provides many interesting features like filters, directives, and asynchronous loading of i18n data. First of all, let’s set up AngularJS and configure it withangular-translate
// js/app.js
var app = angular.module('Multilingual', ['pascalprecht.translate']);
app.config(['$translateProvider', function($translateProvider) {
$translateProvider
.translations('ar', {
'HELLO': 'مرحبا'
})
.translations('en', {
'HELLO': 'Hello'
})
.preferredLanguage('ar');
}]);
Then, let’s slightly modify the HMTL:
<html ng-app="Multilingual">
Then run gulp build
from the command line to build the new changes in the JavaScript file. In the previous code snippet we have:
- Created an Angular module called
Multilingual
. - Injected the
angular-translate
module as a dependency into our App aspascalprecht.translate
. - Injected
$translateProvider
in the.config()
method. - Registered the translation tables in different languages using the
.translations()
method and setting the language key such asen
orar
as the first parameter. - Set the preferred language using
.preferredLanguage()
method, (this is important as we use more than one language, so we can teachangular-translate
which one to use on first load).
angular-translate
using the translate
filter
<h2>{{ 'HELLO' | translate }}</h2>
Having too many filters in a view sets up too many watch expressions as described in the translate-directive documentation. A better and faster way to implement it is using the translate
directive. Another reason to go with the directive is that there is a chance that the user will see the raw {{ 'HELLO' | translate }}
before our template rendered by AngularJS while it’s being loading.
The way we can use the directive is to pass the translation ID as an attribute value of the translate
directive.
<h2 translate="HELLO"></h2>
Sometimes we may need to know if we have missed some translation IDs. angular-translate-handler-log
helps us solving this problem providing a very good method called useMissingTranslationHandlerLog()
which logs warnings into the console for any missing translation ID. To use it we have to first install it. You can do it with Bower:
bower install angular-translate-handler-log --save
Then, update the JavaScript Gulp task:
gulp.task('js', function(){
return gulp.src([
'./bower_components/angular/angular.js',
'./bower_components/angular-translate/angular-translate.js',
// New file
'./bower_components/angular-translate-handler-log/angular-translate-handler-log.js',
'./js/app.js'])
.pipe(concat('app.min.js'))
.pipe(uglify())
.pipe(gulp.dest('./js'));
});
Finally, run gulp build
using this method directly on $translateProvider
as:
$translateProvider
.translations('ar', {
'HELLO': 'مرحبا'
})
.translations('en', {
'HELLO': 'Hello'
})
.preferredLanguage('ar')
.useMissingTranslationHandlerLog();
If we missed up the translation for HELLO
, thanks to this method we’ll get a warning message that says “Translation for HELLO doesn’t exist”.
Load Translation Files Asynchronously
Instead of adding translation data for different languages directly in the.config()
method, there is another way to load them in an asynchronous and lazy loading. Actually, there are several ways to achieve this task, but in this tutorial we’ll use just the angular-translate-loader-static-files
extension.
First we need to install the extension with Bower:
bower install angular-translate-loader-static-files --save
Once installed, we need to update the Gulp task with the extension file path and then run gulp build
.
gulp.task('js', function(){
return gulp.src([
'./bower_components/angular/angular.js',
'./bower_components/angular-translate/angular-translate.js',
'./bower_components/angular-translate-handler-log/angular-translate-handler-log.js',
// New file
'bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
'./js/app.js'])
.pipe(concat('app.min.js'))
.pipe(uglify())
.pipe(gulp.dest('./js'));
});
At this point, we need to create a /translations
directory and add the languages translation files. The structure will look like the following:
translations
├── ar.json
└── en.json
Inside the ar.json
file, write the content reported below:
{
"HELLO": "مرحبا",
"BUTTON_LANG_AR": "العربية",
"BUTTON_LANG_EN": "الإنجليزية",
"WELCOME_MESSAGE": "مرحباً في موقع AngularJS المتعدد اللغات"
}
On the contrary, in the en.json
file save the following content:
{
"HELLO": "Hello",
"BUTTON_LANG_AR": "Arabic",
"BUTTON_LANG_EN": "English",
"WELCOME_MESSAGE": "Welcome to the AngularJS multilingual site"
}
Now, we can use the useStaticFilesLoader
method to tell angular-translate
which language files to load using a specific pattern by using the following approach:
prefix - specifies file prefix
suffix - specifies file suffix
And here’s how the JavaScript file changes:
// js/app.js
app.config(['$translateProvider', function($translateProvider) {
$translateProvider
.useStaticFilesLoader({
prefix: '/translations/',
suffix: '.json'
})
.preferredLanguage('ar')
.useMissingTranslationHandlerLog();
}]);
If we want to add a prefix to the files, we can rename each of them using a prefix (in this case, locale-
):
translations
├── locale-ar.json
└── locale-en.json
By applying this change, we have to update the app.js
file as follows:
// js/app.js
app.config(['$translateProvider', function($translateProvider) {
$translateProvider
.useStaticFilesLoader({
prefix: '/translations/locale-',
suffix: '.json'
})
.preferredLanguage('ar')
.useMissingTranslationHandlerLog()
}]);
Here angular-translate
will concatenate our code as {{prefix}}{{langKey}}{{suffix}}
, and then load /translations/locale-en.json
file for example.
Switching between Different Languages
So far we’ve seen how to work with text translations for two languages. Nevertheless, we can’t still switch to the other language from the browser at runtime. To do this, we need to add a button for every language to switch from it.<div ng-controller="LanguageSwitchController">
<button ng-show="lang == 'en'" ng-click="changeLanguage('ar')" translate="BUTTON_LANG_AR"></button>
<button ng-show="lang == 'ar'" ng-click="changeLanguage('en')" translate="BUTTON_LANG_EN"></button>
</div>
We can also create some $rootScope
properties and use them on our HTML code to set up the initial layout direction and the lang
attribute in the first load, binding them later whenever the language changes.
// js/app.js
app.run(['$rootScope', function($rootScope) {
$rootScope.lang = 'en';
$rootScope.default_float = 'left';
$rootScope.opposite_float = 'right';
$rootScope.default_direction = 'ltr';
$rootScope.opposite_direction = 'rtl';
}])
angular-translate
provides a handy method called use
that takes a parameter and sets the language for us based on the passed parameter. Moreover, we’ll listen to the $translateChangeSuccess
event, which is fired once a translation change is successful, to ensure the language has changed. Then, we can modify the $rootScope
properties based on the selected language:
// js/app.js
app.controller('LanguageSwitchController', ['$scope', '$rootScope', '$translate',
function($scope, $rootScope, $translate) {
$scope.changeLanguage = function(langKey) {
$translate.use(langKey);
};
$rootScope.$on('$translateChangeSuccess', function(event, data) {
var language = data.language;
$rootScope.lang = language;
$rootScope.default_direction = language === 'ar' ? 'rtl' : 'ltr';
$rootScope.opposite_direction = language === 'ar' ? 'ltr' : 'rtl';
$rootScope.default_float = language === 'ar' ? 'right' : 'left';
$rootScope.opposite_float = language === 'ar' ? 'left' : 'right';
});
}]);
And also apply the following change to the markup:
<html lang="{{ lang }}" ng-app="Multilingual">
In my article titled Using Helper Classes to DRY and Scale CSS, you can see another example of using these directional properties in HTML as helper classes:
<div class="text-{{ default_float }}"></div>
Remember the Language
Up to this point, we’ve built the switching language feature and we’re able to change the language to use our favourite one. The next step is to let the application remember the language we choose, so the next time we launch it we don’t have to switch to that language again. We’ll teach our application to remember the language using the browser localStorage to store the selected language and we’ll use angular-translate-storage-local extension for this purpose. As you can imagine, the next step is to install it. We’ll do it with Bower:bower install angular-translate-storage-local --save
Running this command, we’ll also install angular-cookies
and angular-translate-storage-cookie
as dependencies. Once installed, we need to update the Gulp task with the new files running gulp build
:
gulp.task('js', function(){
return gulp.src([
'./bower_components/angular/angular.js',
'./bower_components/angular-translate/angular-translate.js',
'./bower_components/angular-translate-handler-log/angular-translate-handler-log.js',
'bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
// New files
'./bower_components/angular-cookies/angular-cookies.js',
'./bower_components/angular-translate-storage-cookie/angular-translate-storage-cookie.js',
'./bower_components/angular-translate-storage-local/angular-translate-storage-local.js',
'./js/app.js'])
.pipe(concat('app.min.js'))
.pipe(uglify())
.pipe(gulp.dest('./js'));
});
With this code in place, the next steps are:
- Adding
ngCookies
as a dependency. - Telling
$translateProvider
to use localStorage viauseLocalStorage()
var app = angular.module('Multilingual', [
'pascalprecht.translate',
'ngCookies'
]);
app.config(['$translateProvider', function($translateProvider) {
$translateProvider
.useStaticFilesLoader({
prefix: '/translations/',
suffix: '.json'
})
.preferredLanguage('ar')
.useLocalStorage()
.useMissingTranslationHandlerLog()
}]);
angular-translate
will store the initial language as we set by preferredLanguage()
with the key NG_TRANSLATE_LANG_KEY
. It’ll assign the language as its value in the browser localStorage and then update it every time the user switches the language. When the user opens the application, angular-translate
will retrieve it from localStorage.
Working with Layout Direction
We have reached the presentation part. If you’re working with two languages with the same writing directions (for instance English and French), the configuration is complete. If one of the language direction is RTL and the other is LTR instead, we need do some extra work to adjust some layout scenarios. Let’s say this is the CSS code for the LTR language (English):.media-image { padding-right: 1rem; }
When it comes to the RTL language, the above code should be mirrored to be padding-left
instead of padding-right
:
.media-image { padding-left: 1rem; }
However, this is not a good practice at all since it’s time consuming and involves code repetitions:
[lang='ar'] .media-image {
padding-right: 0;
padding-left: 1rem;
}
To solve this issue we need to write some CSS code and allow the support for both the RTL language and the LTR one in an effective, automated, and dynamic way. With such approach, we won’t have to repeat or override CSS rules. I encourage you to read my article titled Manage RTL CSS with Sass and Grunt to learn more about this technique and how to use it in your projects.
We’ll implement it in this tutorial using Gulp and adding a Sass task that takes ltr-app.scss
and rtl-app.scss
. We’ll import the main Sass file in addition to direction specific variables inside them:
gulp.task('sass', function () {
return gulp.src(['./sass/ltr-app.scss', './sass/rtl-app.scss'])
.pipe(sass())
.pipe(gulp.dest('./css'));
});
// Update the build task with sass
gulp.task('build', [], function() {
runSequence('js', 'sass');
});
The sass/ltr-app.scss
file should be as follows:
// LTR language directions
$default-float: left;
$opposite-float: right;
$default-direction: ltr;
$opposite-direction: rtl;
@import 'style';
And this is the code of sass/rtl-app.scss
:
// RTL language directions
$default-float: right;
$opposite-float: left;
$default-direction: rtl;
$opposite-direction: ltr;
@import 'style';
Finally, this is an example of what sass/style.scss
looks like:
body { direction: $default-direction; }
.column { float: $default-float; }
.media-image { padding-#{$opposite-float}: 1rem; }
With all this code in place, you can run gulp build
and the Sass task will generate two files. css/rtl-app.css
will have the code listed below:
/* css/rtl-app.css */
body { direction: rtl; }
.column { float: right; }
.media-image { padding-left: 1rem; }
The css/ltr-app.css
file will have the content reported below:
/* css/ltr-app.css */
body { direction: ltr; }
.column { float: left; }
.media-image { padding-right: 1rem; }
The next and final step is to use these generated files dynamically, based on the current language. We’ll use the $rootScope
‘s default_direction
property to set the direction during the first load and then bind it when we change the language.
<link ng-href="css/{{ default_direction }}-app.css" rel="stylesheet">
Conclusions
As we’ve seen, using angular-translate is the way to go when it comes to AngularJS translation. It offers a lot of handy filters, directives and interesting tools to use. We have covered the translation process in many different ways, exploring how to switch between two languages. We’ve also discussed how to store a selected language in user browser storage and how to work with CSS to make the presentation layer more responsive with language directions. I hope you enjoyed this tutorial. I’ve created a GitHub repo for this article and you can check out the code here. Feel free to share your comments in the section below.Frequently Asked Questions on Multilingual Support for AngularJS
How can I implement multilingual support in AngularJS?
Implementing multilingual support in AngularJS involves using the angular-translate module. This module provides filters and directives that allow you to translate your application into different languages. You can install it using npm or bower, and then include it in your application module. After that, you can define translations for different languages using the $translateProvider. You can then use the translate filter or directive in your HTML to display the translated text.
What are the benefits of using AngularJS for multilingual support?
AngularJS provides a robust and flexible framework for building web applications, and its support for multilingual applications is no exception. With AngularJS, you can easily define translations for different languages and switch between them dynamically. This makes it easy to create a user-friendly interface that can be customized to the user’s preferred language.
Can I use external translation files with AngularJS?
Yes, AngularJS allows you to use external translation files. This can be useful if you have a large number of translations or if you want to manage your translations separately from your application code. You can load these files using the $translateProvider’s useStaticFilesLoader method.
How can I switch between languages in AngularJS?
Switching between languages in AngularJS is done using the $translate service’s use method. This method takes a language key as a parameter and switches the current language to the one specified. You can use this method in conjunction with a select element or a set of buttons to allow the user to switch languages.
How can I handle pluralization in AngularJS?
AngularJS provides a translate directive that supports pluralization. This directive takes a translate-values attribute that can be used to specify the number of items. The translations can then be defined with a special syntax that handles different cases based on this number.
Can I use AngularJS for right-to-left languages?
Yes, AngularJS supports right-to-left languages. This can be achieved by setting the dir attribute on the html element to “rtl”. You can also use the $locale service to get information about the current locale and adjust your layout accordingly.
How can I handle fallback languages in AngularJS?
AngularJS allows you to specify a fallback language that will be used if a translation is not available in the current language. This can be done using the $translateProvider’s fallbackLanguage method.
Can I use interpolation in my translations in AngularJS?
Yes, AngularJS supports interpolation in translations. This allows you to include dynamic content in your translations. You can use the translate directive or filter with an expression that includes the dynamic content.
How can I test my multilingual AngularJS application?
Testing a multilingual AngularJS application involves checking that the correct translations are displayed for each language. This can be done using unit tests with the $translate service, or using end-to-end tests with tools like Protractor.
Can I use AngularJS for localization?
Yes, AngularJS provides support for localization in addition to translation. This includes date, number, and currency formatting based on the user’s locale. This can be done using the $locale service and the various filters provided by AngularJS.
Ahmad Ajmi is a self-taught front-end developer passionate about the Web, open source, and programming.