# Building a Multi-language website
# Server-side vs Client-side localization
The standarized way to translate a web application is building the translatin mechanism on the server-side, and retrieve the translation files via AJAX calls. Altough, in some applications there are dynamically created messages which require a client-side mechanism for translation. The best way is to keep a mixed approach.
The next example has been built with the i18n
Vue plugin for the client-side and a custom approach for the server-side translation, in order to make it easy to understand. The server-side approach should be changed depending on the project scope and properties.
# Multi-language client-side website with Vue.js
The i18n internationalization plugin is the most extended Vue plugin to add support for different languages to a website.
# Installation and usage
The installation is a simple process like other NPM modules
npm install vue-i18n
After installing, add the following code in the main.js
file of the Vue App
import VueI18n from 'vue-i18n'
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: '<default-locale-ISO2-format>',
messages: {
'en': {
// ...
},
'es':{
//...
},
// ...
}
});
new Vue({
//...
i18n
}).$mount('#app')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Features
# Supports different message formats (names, lists, HTML, custom...)
const messages = {
en: {
message: {
hello: '{msg} world'
}
}
}
2
3
4
5
6
7
<p>{{ $t('message.hello', { msg: 'hello' }) }}</p>
# Allows pluralization
const messages = {
en: {
apple: 'no apples | one apple | {count} apples'
}
}
2
3
4
5
<p>{{ $tc('apple', 0) }}</p>
<p>{{ $tc('apple', 1) }}</p>
<p>{{ $tc('apple', 10, { count: 10 }) }}</p>
2
3
Note that you will need to use $tc() instead of $t()
# Language fallback
const i18n = new VueI18n({
// ...
fallbackLocale: 'en',
silentTranslationWarn: true
});
2
3
4
5
The
silentTranslationWarn
property set totrue
is necessary to avoid console warnings when fallback
# Other features
- DateTime formats
- Locale message syntax (i.e. error list)
- Vue
v-t
syntax for better performance (Instead of$t()
) - Hot reloading to increase performance
# Image Uploader App Features
- Separated file for defining languages and messages
- English fallback
- Removed fallback warnings
- Four language support (English, Spanish, French, Dutch)
$t()
syntax (Easy to change tov-t
)
# Multi-language server-side website with Node.js + Express.js
In order to show a solid example of how multi-language web apps can work, an example of server multi-language communication has been created.
# Preparing the client for receiving the data
It is mandatory to make a modification in the client-side for the routes that will have to request content to the server, as it is necessary to request the content if the user changes the language while browsing a page that needs to be translated. This breaks the concept of the Single Page Application, but there is the only way to do it if the pages are served one by one for each language. It is not a big problem since the page reload only has to be done when the user changes the language, and even can be restricted to only the ones that can receive translated content.
To get server-side data translated, it is also necessary that the request from the client-side includes information about in which language is preferred to get the requested content. There are two main ways to do it, and can be applied separately or mixed, and even adding some other properties and features to, for example, guess the best language to serve in first place instead of using one by default (fallback).
# 1. Serving content using different routes for each supported language: https://mysite.com/en/about
This is a common way to serve different content and with tools like Node.js
and Express.js
, it can be applied easily. The problem with this method arises when a user tries to share a link with other user who speaks a different language. In this case the receiver will have to change the language manually using the toggler or via URL. Other mechanisms can be applied in this situations to automatically change the route if the default receiver's browser language is different than the one of the URL.
# 2. Serving content using Headers
This is the easiest way to serve content in a proper way in different languages, since it does not require any plugin or third party module to build it, and also allows to maintain the existing routes of the application API. The problem of this method is that opens a door to XSS
or CSRF
attacks, but if the security of the application is well constructed, it should not be a problem. This method is usually applied together with the mentioned above to serve the first content in the most appropiated language for the requester.
# Other possibilities: Serving all the content all time and let the client-side manage it
This method is even easier than the previous one, but the problem is that if the server receives lots of requests and have to send large amounts of information for each one, this method can cause slowdowns in response times.
# Preparing the server-side
For this example, the client has been modified to send preferred the language in the header of each request. In order to prepare the server to send the content in the correct language, a localization
folder has been created and different JSON
files inside it, one for each supported language.
The language files has been loaded to the server and then used to serve its content based on the request headers. There are also different plugins (like i18n-node (opens new window)) to create, store and manage language files.
The code to charge and manage the files in the server is the following:
const defaultLanguage = 'en';
const en = require('./languages/en.json');
const es = require('./languages/es.json');
const nl = require('./languages/nl.json');
const fr = require('./languages/fr.json');
const locales = { "en": en, "es": es, "nl": nl, "fr": fr };
2
3
4
5
6
7
8
Then this locales
variable is used in each request to serve the content
app.get('/posts', function(req, res) {
// Set the language to English if the header is not defined
var lang = req.headers["accept-language"] || 'en';
// If the header is defined but the language is
// not supported, return also the English content
var responseData = locales[lang] || locales[defaultLanguage];
res.json(responseData);
});
2
3
4
5
6
7
8
# General result applying mixed techniques
# Multi-language database design
# Column approach
Simplest and easiest way to proceed. This method consists of adding a new column to the table for each column that has to be translated.
Pros | Cons |
---|---|
Simple | Hard to maintain, works well for 2 or 3 languages |
Easy queries | Hard to add new languages, since new columns have to be created for each table that requires it |
No duplicates | Store empty space, if there are elements that don't need to be translated |
# Multi-row approach
Similar to the column approach, but adding a new row for each element that has to be translated. Better than the previous one in case the same table has more than 1-2 columns to translate.
Pros | Cons |
---|---|
Simple | Hard to maintain, since every change to columns, even if the column changed does not require translation, has to be performed in all rows for each language |
Easy queries | Hard to add new languages |
Duplicated content |
# Single translation table approach
This solution is the cleanest one from database structure perspective. All texts that need to be translated are stored in a Single translation table.
Pros | Cons |
---|---|
Clean | Complex querying, multiple JOINS required |
Easy to add new languages | Hard to maintain from the querying perspective. The more fields to translate, the more complex the query |
Maintainable (all translations in one place) | All translations in one place, if one table is missing, this will cause global problems for the application |
# Additional translation table approach
Variation of the Single translation table approach, in order to make it easier to maintain and work with. For each table that stores information that may need to be translated, an additional table is created. The original table stores only language insensitive data and the new one all translated info
Pros | Cons |
---|---|
Clean | May double the amount of tables |
Easy to add new languages | |
Columns keep their names | |
Easy queries, one JOIN required |
# Problems
- The app title (browser tab) cannot be translated since the Vue App is inside the
HTML
file that contains the Title - Layout - Some languages like Arabic are right-to-left.
# Possible Solutions
(?)
Workaround to switch the title when app changes (Need more research)- It would be necessary to have separate style files to properly handle the site appearance so that the text read and displayed correctly