We often hear questions around topics of localization and globalization for our DevExtreme widgets. This is not surprising, since we’re talking about a complex topic area — more complex than many developers realize, especially at the beginning of new projects!
In this post, I’m going to describe what our products do out of the box and the available options to extend the functionality for advanced globalization and localization scenarios.
I expect that my scenarios may be incomplete! If you have a problem in this area that remains a mystery to you after reading this post, please comment and let me know!
Areas of interest
Globalization is a term that describes the process of making software aware of the locale it is running in, or even of the fact that there is more than one locale to consider. I hope this is not a common thing these days, but I remember seeing many applications in my life that worked with lots of hard-coded strings, clever date parsing functions specific to the author’s locale and other… well… tricks that make your life difficult when the application is supposed to run in a different country.
Globalizing an application could be called a mindset adjustment, at least if the process starts before, or at least while, code is being written. It is mostly about guidelines like “don’t use string literals without thinking about translation”, “leave enough room in UI design for labels in other languages” or “don’t assume format details for numeric data”.
Typical globalization tasks are these:
- Provide a mechanism for end users to select their locale, and/or auto-detect it
- Ensure that string literals are wrapped in a call to a translation retrieval function, loaded from an external translatable resource, or similar
- Check that automated tests run with different locale settings to find potential issues
- Enable the UI to adapt to changing space requirements
Localization on the other hand involves steps to make an application work according to the preferences or requirements of a certain locale. These are typical tasks for localization:
- Obtain translated content for each piece of text in the application
- Configure display and data entry formats, specifically for numeric and date/time data
- Switch the UI to right-to-left layout
What’s in the box
Non-English languages
The DevExtreme distribution supplies localized strings for all widgets in four languages: English (which is the default), Russian, Japanese and German. The strings are contained in dictionary files, which are JavaScript or JSON files, depending on the packaging you use. There is documentation for this mechanism, but all it amounts to is that you need to include or load one or more of the language-specific dictionaries in your application. For instance, you could use a script
tag to load one of the files like this:
<script type="text/javascript" src="js/localization/dx.messages.de.js"></script>
CDN URLs also work with this loading mechanism:
<script type="text/javascript" src="https://cdn3.devexpress.com/jslib/17.1.5/js/localization/dx.messages.de.js"></script>
When using the npm
packages, for instance, you will need to load a JSON file with the messages and call DevExpress.localization.loadMessages
yourself. The section below titled Globalize/Using npm includes sample code for this process.
Further examples can be found in the docs. It is possible to create custom dictionaries simply by copying one of the existing files, naming it according to a new locale, and changing the content depending on your needs.
To use the localized strings from one of the dictionaries, all you need to do is tell the DevExtreme widgets which locale they should observe:
DevExpress.localization.locale("de")
In order to use the language requested by a user’s browser, we recommend this call:
DevExpress.localization.locale(navigator.language || navigator.browserLanguage);
Again, for a few more details check the documentation linked above.
Right-to-left
Right-to-left (RTL) layouts are supported by the widgets directly. When you set up a widget, you can use the rtlEnabled
property for that widget:
$("editor").dxDateBox({ rtlEnabled: true });
However, usually you want to set RTL for your entire application and this is possible by passing the same flag to the config
function:
DevExpress.config({ rtlEnabled: true });
Here is the documentation page for config
, in case you want to have a closer look.
Value formatting
Widgets support formatting of values via many different configuration options, and here is the general documentation page for format specifications. However, it is important to understand that locale-specific formatting is only supported if you use a separate library – read on for that! I’m mentioning it here because a few of our examples in the docs can be a bit confusing. This has been mentioned to our documentation people and it will be improved.
For instance, this structure definition is shown for a format block:
format: { type: String, // one of the predefined formats precision: Number, // the precision of values currency: String // a specific 3-letter code for the "currency" format }
It is important to understand that the currency
part of this specification will only be interpreted if a localization library is loaded, while type
and precision
work out of the box. The standard currency is usually USD
(unless you change it via config()
) and currency values will be displayed with a preceding $
(dollar sign).
Using globalization/localization libraries
There is support for two different globalization/localization libraries for DevExtreme. The main documentation refers to Globalize, which was supported first and remains the standard catch-all solution. However, I’m going to talk about Intl first, because it is the simpler of the two approaches.
DevExtreme-Intl
Intl is the short name used to refer to a particular object of the ECMAScript Internationalization API. To support this API for DevExtreme, we have created DevExtreme-Intl. If you follow this last link, you will see some documentation for the module in the project README file.
Loading the library is very simple, since there is just one JavaScript file to include via a script
tag or an npm
call (and in the latter case you’d have to add a require
or import
statement to load the library). Once this is done, two things happen automatically:
- Date and number formats in DevExtreme widgets observe the locale configured via
localization.locale()
as described above. - Currencies are interpreted where they are configured (globally or in format definitions) and values defined as currencies are displayed with the correct currency symbols.
In addition, you can take advantage of extended formatting functionality for date and number fields by passing structures compatible with the Intl API. Follow this link for the README section that links to Intl documentation on these capabilities.
To illustrate, here is one of the examples also shown in the README. The format structure uses the properties year
, month
and day
, which are described here.
$("#grid").dxDataGrid({ dataSource: dataSource, columns: [ { dataField: "OrderDate", format: { year: "2-digit", month: "narrow", day: "2-digit" } }, ... ] });
Using DevExtreme-Intl is easy (and you’ll see how complicated it is, in comparison, to set up Globalize), which is its main advantage. Unfortunately there are also drawbacks:
- You need to take extra steps to enable date parsing if your end users require the ability to enter dates into
dxDateBox
editors (or embedded date editors in thedxDataGrid
). This is described here. - The functionality of the Intl API is mostly automatic, but doesn’t easily allow for certain customizations. For instance, I was unable to format a date locale-independently based on a format string like
YYYY-MM-DD
. - You will have to sort out additional mechanisms for the full-scale localization of your entire application, for instance for loading translations of arbitrary strings.
Globalize
The activation of Globalize for your project is much more complicated than that of Intl. Detailed instructions are available as part of our DevExtreme documentation. We recommend loading eight JavaScript files, and eight more JSON files with CLDR data. Globalize and the CLDR repository have even more modules than that, if you need them! Here is a link to the Globalize docs on CLDR, and this link is about distinguishing which modules you may require.
Please note that the instructions on this documentation page assume certain paths to access the Globalize JavaScript files and the CLDR data. These paths are valid if you are using our DevExtreme installation from a zip file or the Windows installer. If you are using a different installation method, your paths will be different. We have other documentation, but it isn’t perfect at this time.
Using a CDN
The page CDN Services shows CDN URLs for the JavaScript files. You might want to check for more current versions of the two main packages cldrjs
and globalize
, and it is also possible to load Globalize from cdnjs instead of aspnetcdn. Further, the page doesn’t show any URLs to load CLDR data from in this scenario. To help you out, here is a version of the CLDR loading logic from the Use Globalize documentation linked above, using unpkg
URLs to access CLDR data:
$.when( $.getJSON('https://unpkg.com/cldr-dates-full/main/en/ca-gregorian.json'), $.getJSON('https://unpkg.com/cldr-numbers-full/main/en/numbers.json'), $.getJSON('https://unpkg.com/cldr-numbers-full/main/en/currencies.json'), $.getJSON('https://unpkg.com/cldr-dates-full/main/de/ca-gregorian.json'), $.getJSON('https://unpkg.com/cldr-numbers-full/main/de/numbers.json'), $.getJSON('https://unpkg.com/cldr-numbers-full/main/de/currencies.json'), $.getJSON('https://unpkg.com/cldr-core/supplemental/likelySubtags.json'), $.getJSON('https://unpkg.com/cldr-core/supplemental/timeData.json'), $.getJSON('https://unpkg.com/cldr-core/supplemental/weekData.json'), $.getJSON('https://unpkg.com/cldr-core/supplemental/currencyData.json'), $.getJSON('https://unpkg.com/cldr-core/supplemental/numberingSystems.json') ).then(function () { // ... continue like the example in the docs
Using npm
The basic use of npm is described in this page of the documentation. However, this page assumes that you will be loading Globalize files via script
tags, accessing them directly from the node_modules
path. In reality I think it is more likely that you’ll be using a packaging approach once you start installing modules via npm
.
The documentation page Modularity describes how to use Webpack and other packagers with your DevExtreme project. The description is pretty good, but also complex. Fortunately we have a set of sample projects, and I recommend you have a look at those in this github repository.
Unfortunately the page on Modularity doesn’t refer to Globalize integration, and only few of the sample projects address this topic. One more thing for us to improve – meanwhile, here are the steps necessary to activate Globalize for your project.
1— Let’s say you start out from a project that has the simple structure shown in the Webpack/jQuery sample.
2— Install a few extra packages:
npm install --save-dev cldr-data globalize
3— Edit index.js
and add these lines at the top:
require('devextreme/localization/globalize/message'); require('devextreme/localization/globalize/number'); require('devextreme/localization/globalize/currency'); require('devextreme/localization/globalize/date'); const Globalize = require('globalize'); Globalize.load( // language specific files, loading English and German here require('cldr-data/main/en/ca-gregorian.json'), require('cldr-data/main/en/numbers.json'), require('cldr-data/main/en/currencies.json'), require('cldr-data/main/de/ca-gregorian.json'), require('cldr-data/main/de/numbers.json'), require('cldr-data/main/de/currencies.json'), require('cldr-data/supplemental/likelySubtags.json'), require('cldr-data/supplemental/timeData.json'), require('cldr-data/supplemental/weekData.json'), require('cldr-data/supplemental/currencyData.json'), require('cldr-data/supplemental/numberingSystems.json') ); // German messages - English ones are included by default const deMessages = require('devextreme/localization/messages/de.json'); const localization = require('devextreme/localization'); // This loading instruction is not required when using CDN paths localization.loadMessages(deMessages); localization.locale('de');
The first block loads the Globalize integration modules that come with the DevExtreme npm
distribution. You might wonder why we don’t appear to be loading any of the cldrjs
and globalize
package files that were included in the CDN based approach. The reason is that when using this modularized approach, the required references are pulled in automatically by Webpack (once you’ve added something to the Webpack config, see below).
Note that in some of our samples, you will find code that uses Globalize functions directly to load the messages and set the locale:
Globalize.loadMessages(deMessages); Globalize.locale('de');
There is no practical difference between these approaches for the task at hand. You may prefer using the DevExtreme “versions” of the functions for consistency (since that will work with or without Globalize loaded, or even with or without DevExtreme-Intl loaded). On the other hand, in a larger application you may be using Globalize independently to localize other parts of your application than just the DevExtreme widgets, and in that case it may seem more familiar to use Globalize API calls directly.
4— Edit webpack.config.js
and add this block to the module.exports
object:
module.exports = { entry: ... ... resolve: { lias: { globalize$: path.resolve( __dirname,'node_modules/globalize/dist/globalize.js' ), globalize: path.resolve( __dirname,'node_modules/globalize/dist/globalize' ), cldr$: path.resolve(__dirname, 'node_modules/cldrjs/dist/cldr.js'), cldr: path.resolve(__dirname, 'node_modules/cldrjs/dist/cldr') } } ... }
This configuration ensures that references to globalize
and cldr
inside some of the packages can be resolved correctly.
5— Build the bundle by running webpack
(or node_modules/.bin/webpack
if you don’t have Webpack installed globally). Open index.html
in a browser and everything should come up correctly and without errors.
6— To see localization in action, I recommend adding a dxDateBox
to your project. Add this to index.html
, right after the myButton
div that’s already in there:
<div id="datebox"></div>
Then add this code to your index.js
:
require('devextreme/ui/date_box'); $('#datebox').dxDateBox();
Don’t forget to run webpack
again, and test the German localization by entering invalid text in the datebox. You should see a German language error message coming up. When you select a date, you should also see the German style format (dd.mm.yyyy
).
What Globalize does
Once Globalize has been loaded, whichever mechanism you use, it performs the same tasks as Intl did in the section above. It enables localized date and number formats and the use of currency symbols.
In contrast to Intl, date parsing works automatically with Globalize. You will be able to enter a date string manually, in a datebox or an embedded data grid editor.
Using custom format strings is also possible with Globalize. For instance, you can use YYYY-MM-DD
as a locale-independent date format (I mentioned above that this is not possible with Intl, to my knowledge.)
The infrastructure provided by Globalize is very complex, but also very powerful, and you’ll find it a versatile solution for application-wide localization tasks. An API overview for Globalize can be found here, and of course lots of third-party content is available about it.
Final thoughts
I hope this post was able to shed some light on the options for localization with the DevExtreme widgets, and the technical details of working with Globalize and Intl. As I said in my introduction, there are many different use cases and scenarios and I’m sure I’m not covering them all. Please let me know if you have questions about your own situation and I’ll help you answer them, and extend this post accordingly!