📜 ⬆️ ⬇️

Accordion about architecture and localization

JavaScript Localization Over the past six months, there have been a lot of articles on how to write a cool framework with a volume of ~ 100 lines. A tackle is a story about how to write ~ 2000 lines and no framework.

Background


Before turning to the technical part, I would like to get acquainted with the history of the issue. To the question “why?”, This article gives a good answer. Anton Nemtsev spoke on DUMP in Yekaterinburg on what localization and internalization are . Alexander Gerasimov gave a talk on typography in Minsk at Frontend Dev Conf. Alexander Tevosyan from Badoo spoke about the layout of multilingual sites on MoscowJS, and his colleague, Gleb Deikalo, wrote about multi-step translation . How to write the texts themselves so that they were easy to translate was already discussed in the report of Christina Yaroshevich on Hyperbaton at the conference of the Translation Forum Russia, moreover, Pavel Doronin wrote on a similar topic.

What is Hyperbaton
Hyperbaton is a figure of speech . At first, I wondered for a long time why the conference was called a loaf . In general, I recommend watching the reports that were published. Many interesting and for non-linguists. For example, Maxim Ilyakhov

On Habré you can find a lot of articles on various topics related to localization. Large companies like Alconost or ABBYY write them in batches.


The articles and reports described above answer many questions. But the question on the organization of the code is described only partially. And yet, yes, in my book Surrealism on JavaScript, I partially described the localization logic and its work with the DOM. But it is time to write a few more words about the code and its general structure.
')

Introduction


Why on the client?

It all depends on the task that we solve. If we are talking about a site, then the server is better. If this application, it is better to deploy the infrastructure on the client. On the one hand, the client can work offline. On the other hand, we will reduce server requests if clients use local files. In addition, sometimes we do not have a server, or the operating time of a server developer is so expensive that it does not make sense to spend it for this task.

Why on javascript?

As frontend developers, we can still choose HTML or CSS. How to use HTML, I personally can not imagine. How to make CSS work as part of the localization task was written by Anton Lunev ( http://habrahabr.ru/post/121075/ ). But both in the first and in the second case, we lose the flexibility and opportunities that JavaScript provides us.

What to eat from the finished?

Apart from the various functions in 30 lines with stackoverflow, and the internal tools of the guys from Badoo, Yandex, Alconost, etc., at the moment there are several quite tolerable solutions:


Accordion about storing translations


We have variables in which translation of phrases for a specific language is recorded. Further, throughout the application, instead of using the phrases themselves, the corresponding variables are substituted. Thus, by changing the value of variables, we can change the language of the entire application.

JavaScript Localization

For example, in the game “Juices” guys took and recorded translations of all phrases into separate variables ( VARIABLES KARL! It's good that at least not global! ). Working with variables can justify itself well in two cases:


In the first case, our resources are cut, and we have to save on everything in order to meet the memory limitations. It is not always possible to develop a good logical architecture in such conditions.

In the second case, we are trying to eliminate even the possibility of accidentally switching the parameters. If you look at the same slot machines, then all the probabilities of losing are strictly regulated by the legislation of a particular country. Therefore, the case when in Russia there are automata for the United States, and in Bulgaria - automata for Hungary, is excluded.

The game "Squishy" works in VKontakte and does not fall under any of the restrictions specified above. By her example, she clearly shows that the quality of the code and architecture of the localization system plays no role whatsoever. Yes, perhaps, it will be inconvenient for someone to refactor code or add new features. Yes, perhaps this approach will lead to more bugs. But the fact is a fact, a product: in production, it brings money, it suits everyone.

It’s not very convenient to store each phrase in a new variable, and the translations themselves are usually transferred to a separate configuration file. Therefore, the developers of most localization systems prefer to use popular storage formats for information, such as JSON and XML. Most Java and Python developers usually prefer to work with XML, and front-end vendors with JSON. Here, who is used to anything. Standard formats make it easy to use the same translations for completely different localization systems, both in the context of a single programming language and in the context of completely different languages.

There are quite a large number of people who went the other way. So Ruby`ists like to use YML, Flashers prefer XLIFF, C ʻishniki - PO format, for example, guys from l20n.org have come up with their own format, with tags and attributes. Let's take a quick look at them.

YML

YML is like XML, only human-readable. You can also store RegExp in it explicitly.

It was:

<translation> <hello>, !</hello> </translation> 

It became:

 translation: hello: ", !" 

PO

It is used mainly for the localization of free software, since it's simple. The developer wraps all the lines in some function, for example gettext (), and then these lines are the key for translation. If there is no translation, the source string is substituted.

 msgid "Hello, World!" msgstr ", !" 

Since None of the above formats is native and convenient for front-tenders, we go further. An example of a file with translations in the game "Valley of sweets":

JavaScript Localization

When using JSON in JavaScript, some localization systems do not support nested properties. An example of such a file you can see above. This entails an unobvious translation structure. Most localization systems do not have such problems, and the above code can be easily converted.

It was:

 { ... "inviteFriend": " ", "inviteFriend2Lines": "<br> ", "InviteFriend2Lines": "<br> ", ... } 

It became:

 { ... "invite": { "friends": { "line1": " ", "line2": "<br> " }, "friend": "<br> ", ... } 

With this approach, the structure of the translation becomes more apparent. Phrase identifiers take the form of “invite.friends” and are pulled out of the JSON object by a function like this:

 function getProperty (json, key) { key = key.split(/[\.]+/gim); for (var i = 0, l = key.length; i < l; i++) { var propertyName = key[i]; if (!json[propertyName]) { return null; } json = json[propertyName]; } return json; } 

The next step is to try to reflect cases in this structure, since sometimes there is a need to withdraw the numerals. The classic example is “2 apples ka ”, “1 apple ko ”, “no apple k ”. The l20n guys already mentioned above solved the problem like this:

 <brandShortName { *nominative: "Aurora", genitive: "Aurore", dative: "Aurori", accusative: "Auroro", locative: "Aurori", instrumental: "Auroro" }> 

And those who use Angular, write this:

 {{numMessages, plural, one {   } two {   } other {   } }} 

I, for example, refused at all from the idea of ​​numerals and cases, because this:


The classic problem with apples can be solved by pictograms. Here are some examples:

JavaScript Localization
Creating units in Red Alert

JavaScript Localization
MineCraft inventory

Perhaps this is the specifics of the game, but in conventional SPA applications (admin panels, utilities, etc.), it was also always possible to display such counters correctly and without cases.

JavaScript Localization
Fast display of statistics in the banner system

JavaScript Localization
In the game "CosmoSim" parameters are shown by numbers and pictograms.

Of course, there are many cases where, whether we like it or not, the problem must be solved. If you have just such a case, then perhaps an article by Pavel Doronin or a crutch from "...". Replace (/.../ gim, "...") will help you;

What is the problem with the templates?

The problem is that any option in which we come up with a template immediately becomes not universal, because there are no universal templates. If we automate this option, then we have all the lines, regardless of the content, will be processed by regular search for variables. This is a lot of extra work. If we bypass this and initially mark lines with a pattern in the data, then we will increase the amount of data that we need to store. Thus, working with templates is always a mystery with two chairs: “correctness of the translation” vs. "Simplicity and versatility of the system."

Accordion about HTML


We have already discussed the storage of translations, it's time to go to HTML. Almost everything that is written below was already in the book “Surrealism on JavaScript”, and what was not there, was in Nemtsv and Tevosyan.

If we talk about the layout of the HTML-page, then we need to somehow mark the DOM-elements, the text within which needs translation. As a rule, developers assign various attributes to elements, and write the translation id in this attribute. For example:

 <!-- angular-translate --> <p translate="hello">, !</p> <!-- i18next (http://i18next.com/) --> <p data-i18n="hello">, !</p> <!-- l20n (http://l20n.org/) --> <p data-l10n-id="hello">, !</p> <!-- jquery.localize.js (https://github.com/coderifous/jquery-localize) --> <p data-localize="hello">, !</p> 

But! Few people analyze the translation object received by the identifier and the tag to which it will be applied. If this is not done, then tasks like:


etc. they will be solved with crutches, or they will not be solved at all. An example of a situation in which you may feel discomfort by choosing an ill-conceived system may be the following code:

 <img src="scheme_ru.png" alt=" " title="    2114" translate="id__"/> <input type="text" placeholder="  " value=" " translate="id__"/> <link rel="stylesheet" type="text/css" href="css/ru.css" translate="id__"/> 

In our localization system, we distinguish between the type of translation received and the type of HTML element. Thus, having received the translation of the type string “scheme_en.png” and having the HTML IMG element, we can assume that, most likely, the src attribute needs to be changed. If we work with the INPUT element, then, most likely, we need to translate the placeholder attribute, and the LINK type, respectively, does the href attribute change, if no other parameters are specified. In addition, in situations where the rules for working with an element are not specified or ambiguous, we use translations of the type “object” and, by iterating through its properties, assign a value to the same-name attribute of the tag we work with. Consider a translation for the examples given above:

 { "id__": { "src": "scheme_uk.png", "alt": " ", "title": "  і  2114", }, "id__": { "placeholder": "і  ", "value": " " }, "id__": "css/ua.css" } 

Having the opportunity to change the attributes of tags, we smoothly proceed to solving the problem of changing the interface for different national groups. As has already been said more than once: “- Localization is not only the translation of texts, but also the adaptation of the product, to the cultural characteristics of a particular country”. Therefore, we need the ability to automatically switch styles and change images.

A little pain

Repeatedly in the comments, and articles, I heard the opinion that the flags of countries should not be displayed, and icons. If a person does not know something, he will always turn on the English and figure it out. Strongly disagree with this point of view. The fact is that I know the bad English. A bad translation is much better for me than its absence. And like me very much.

Suppose there is exactly the same guy in Georgia or Israel. This is the usual average guy of this country. He will look at the English language in the same way as you speak Georgian or Yiddish. Below is a screenshot of a small HTML game. If it were not for the icons on the buttons, from what attempt would you get into the menu of switching languages? If languages ​​were not accompanied by flags, how long and painful would you be wandering around a skisk of 50 languages?

JavaScript Localization

Mirror do not forget

This has already been said a lot, so nothing to add. Have you made an element? Do not forget to attach rtl.css to it (this will help rtl-css ). List all rtl in the localization system. She needs to know in which case you need to connect them.

JavaScript Localization

For example:

 LanguageApplication({ rightRules: [ "module/button/button__right.css", "module/popup_text/popup_text__right.css", "module/progress_bar/progress_bar_right.css", "module/popup_window/popup_window__right.css", "module/popup_window/popup_window__animation_elements_right.css", "js/applications/language/language__right.css", "css/right.css" ] }); 

Accordion Pro Architecture


Now that we have briefly described the possible formats for storing translations and poked a bit in HTML, we can add a little pain :) Why write your localization system when there are at least five ready-made and widely used solutions on the market? Good question, let's see how localization works in general.

We have two files: Russian and English. By User-Agent, we determine the user's language and connect either the first or the second.

JavaScript Localization

Simple task, everything is beautiful and clear. Any localization system shows you a demo that works on this principle. But we had another. Several mobile portals were assembled from the same modules. The portals were similar, but some sections were different. We sold games on one, books on the other, and you could buy a tune on both. There were various online tournaments and championships. Some were held only in Russia, others throughout the CIS. In order not to produce resources, the translation was divided into several files. For each specific mobile portal, the combination of these translations was different. Since the number of sections in the Russian and English versions could not coincide (and in half of the cases did not coincide), the number of translations in one combination also varied from language to language within even one portal.

JavaScript Localization

What do we see in the diagram? A bundle of files. And for each pack of files there should be a configuration file that describes the number, order and version of the necessary translations in the assembly.

JavaScript Localization

"Fine! Now everything is as it should be! ”- I thought at that moment. But, as you may have guessed, this was only the beginning of the journey. After half a year, I began work on the application of psychological tests. Everything is pretty trite: choose a test, answer questions, publish your psychological breast size on the wall. All is good, but in Russia such tests are already complete. But in some Czech Republic or Croatia is not. They have less population and they also have less programmers. Small market is not attractive for large players. Therefore, you can write “the best test” for Russia and try to win ~ 146 million people, or the same for Croatia, where ~ 4 million people live. Looking ahead to say that the project failed, but at the development stage, it was not yet obvious.

JavaScript Localization

What do we have? Approximately 30 tests at the time of publication of the application. Each test is an introduction, then about 10 questions and at least three conclusions. All this is multiplied into three languages. Total: 30 * (1 + 10 + 3) * 3 = 90 translations with a complex structure, no less than 2000 characters. If the application “shoots”, then the number of tests will increase by two, three times, and the number of translations will be multiplied by at least five. Even if the user will not pass all the tests in all languages, we must remember that we have a one-page application. And this application slowly and consistently eats memory, and with each new test it disappears.

What flies with the dough? A lot of different files arrive with the dough. One of them is the translation configuration. It describes all the languages ​​in which this test is available. In the same place, addresses are written from where the relevant translations should be shipped.

Since our application consists of modules, it is quite obvious that the interface translation is separate, and the translation of each test is separate. As mentioned above, this gives us the opportunity to reuse resources.

JavaScript Localization

Look, in the picture you can see how the “test translation” package arrived to the “interface translation” package. What does the localization system do? That's right, it combines two configurations, and gets a third. And then, taking into account the final config, begins to think about what transfers she now needs. When she thinks out everything and loads it, she will again combine the result, receiving a single JSON at the output. That is why JSONʻu will form the final translation of phrases.

JavaScript Localization

It's time to smoke and “say hello” to Gleb Deikalo, who, as mentioned above, wrote about “multi-level translation”. By combining configurations and translations, we can get the same multistage described in his article. Only for this we need to merge translations within the framework of different languages ​​in a predetermined order.

Let's return to our memory. Now that we have smeared configuration files up and down and know the order of their merging, we can try to clear the memory after the test is completed. To do this, we need to remove the downloaded package and the translations it added. Next, combine the balance in the final translation.

JavaScript Localization

Good thing turned out. Now we can load and unload whole blocks of translations from memory. But my colleague from the neighboring department offered a better and easier solution - to write translations of tests in one variable. Thus, the translation of each of the next test would overwrite the previous translations. It is a pity that by that moment, everything that was described above was already implemented with that two times.

Why two? Because I was wrong when I described the structure of the configuration file. Above, I already wrote that the system should be universal for online and offline work. If we work offline, we can upload translations only by directly connecting the JS file. This file will give us an object with the data during initialization. Since in the future we are going to delete it, we need to sign the object with some id, moreover, we need to keep the same id in the configuration file. When we will delete the configuration, having received the list of translations id, we will also delete all the translations that were loaded at her request. I described the structure of the configuration file as follows:

 { id: "id ", translations: { ru: { b: "/lang/b.js", a: "/lang/a.js" } } } 

Who will roll updates on whom: “transfer A to B” or “B to A”? If you are lucky, then “A to B”, but he is not obliged to do this, because the structure is not an array, hence the order should not matter. In addition, another error was that the order of loading files affected the order of assembly. Therefore, I had to redo it:

 { id: "id ", translations: { ru: [ { id: "b", url: "/lang/b.js" }, { id: "a", url: "/lang/a.js" } ] } } 

Is this a localization system? No, this is a system load configuration. Another mistake I made was that in the first version I did not allocate it to the hotel class. In general, there are quite a few separate classes and modules:



In most “ready-made” localization modules, this is not the case. Why? Perhaps because those who write them rarely use them.

Interesting fact:
None of the sites of localization systems on JS is localized.

Charles! When we wrote localization, at least we ourselves used it! Carl himself used!

We update the DOM only on demand

Not every localization system methods "switch language" and "update DOM tree" are separated. This can create difficulties if you need to complete the transaction immediately after all translations are ready. Also, in many implementations, the DOM tree is updated after the download of each translation file is completed. For example, we switched the language to "Armenian". The system begins to load three translation files: the main interface, settings, translation of a plugin. In such a situation, it is logical to update the DOM only once - after the end of loading the last file in the list.

English, motherf ** ker! Do you speak it?


Now, are we ready for localization? Not! Now we are ready to decide on the infrastructure that will be around this system. The format and structure of the translation file are determined by the programmer, and its content is the translator or project manager. These people may not only be unfamiliar with the chosen structure of information storage, but also refuse to attempt to accept and understand it. They can easily remove quotes in a JSON object or accidentally erase any tag in XML, and moreover, many of them generally prefer to work with files in Microsoft Word (DOCX) format.

The task of the infrastructure is not only to provide managers with a tool for localization, but also to ensure the stability and ease of assembling and updating translations. Localization systems often suffer from infrastructure errors and underdevelopment. The greater complexity of updating files with translations and keeping them up to date is often the cause of disabling one or more languages.

The managers of the Mushroom War game use the Excel spreadsheet to work with translations, which is then processed by the parser and turned into an XML document that is used by the localization system when building the project.

JavaScript Localization

The guys from “Evilibrium” decided not to work with the files directly and made the exact same sign in Google Docs. The logic is simple - sending a link to translators is easier than sending them a file. Therefore, everyone works with translation online, and when assembling, the tablet is downloaded in the same way and parsed into the necessary XML.

JavaScript Localization

At the moment there are many online services for working with translations. Someone is better, someone is worse, but almost everyone copes with the task of interacting with a translator or a group of translators. Also, most services provide the ability to automatically translate your application. On the Internet there are many articles on this topic, but because This article is about bayans, it’s a sin not to write your own program for translations.

Before continuing, I want to say a few words about why, in my opinion, auto translation is normal. First, if you write a text observing a number of rules (simply, briefly, with direct word order, etc.), then an auto translation can reach an accuracy of 90% (see the report of Kristina Yaroshevich).Secondly, in most applications a fairly standard and poor set of phrases are used: open, close, save, load, new game, exit, further, cancel, etc. They are very difficult to translate wrong. Thirdly, we, as carriers of Russian, are happy bolsh-Mensh rozumiti bagato movi slov'yanskoi Movno group. Therefore, we can check their translation for gross errors and without a translator. But this is my personal opinion, for example, Pavel Doronin, in his article, held the opposite opinion: “A poorly localized product is much worse than non-localized in general.”

To localize the next application, I needed to cut the files with the translation. The freelancers who sent the translation constantly broke JSON. Soon, instead of JSON, I began sending them plain text. In the first approximation, there was a problem from the text of freelancers to get valid objects. For this task, an application was made on the knee in two windows (text on input, JSON on output). How to break the text into phrases? Let's look at an example:

Text in TEXTAREA:

  1  2 

As an ordinary person sees him:

  1  2 

As the programmer sees it:

  1\n  2 

Fine! We have a symbol by which we can break the text. In addition, because this is a hidden symbol, the translator will not be able to accidentally break or wipe it (at least in most cases). We convert the text into an array through split (), and then we substitute it into an object that we drive into JSON.stringify () and get the necessary valid file.

JavaScript Localization

SinceJSON`s template can be different, it is better to put it in a separate file. And since we brought it to a separate file, can teach him to generate not only JSON `s? With these thoughts, I added another window to the program in which you can edit the “template generation” function. At the input, it receives an array of translated phrases, and at the output it returns JSON, XML, or something else.

JavaScript Localization

When it was necessary to translate the next application, I thought that you can save a lot and increase the volume of translations by screwing in some auto translator. No sooner said than done. Now the program could take the text, translate it into a bunch of languages, and give the final valid JSON `s.

JavaScript Localization

After that, I began to write a script that would cut the wrapper of JSON in the JavaScript files. Then I thought, and instead wrote a small local server on NodeJS. Now, after receiving the next translation from Yandex.Translate, the program began to send data to the node that cut the necessary files on the fly.

The result is very pleased. Now small games began to go with a standard translation of about 45-50 languages. So several small products were released, for which localization was not a critical place, but was a nice addition.

Testing showed that the auto translation was wrong in the same places. For example, the word "Back" translated as "Ago" or "Tom" (in the sense of "20 years ago"). Sincephrases were the same crutches began to appear. At some point I decided to remove them and add a dictionary. So the program has a third window.

JavaScript Localization

Now the dictionary contained type fields:

 { ru: { en: { "": "Back" ... 

Sinceduring the addition of the dictionary, I decided to refactor the application a bit, I made many additional edits. At the same time I decided to read more about localization. At that moment I came across an article by Denis Lukianov “ A review of 7 online services for software localization ” and realized that all this time I was writing a bicycle ...

So what to do, bike so bike. From the principle decided to finish and made the final touch. Now, the node began to cut the translation immediately into a bunch of formats: JS, JSON, XML, PO, YML, CSV and save these files in two folders: “clean” and “dirty”. If we change the files in the “dirty” folder, then in the dictionary we can click “synchronization” and the node will automatically generate a dictionary based on the differences found. At the moment, synchronization works only in half of the formats, since It is not clear whether to continue to mess with the program or not.

JavaScript Localization

You can go to the site and see how it all looks or even download (link to the file in the settings). If at least 10 people sign off on the need to continue, I will bring it to mind, because I want a few more features:


Ideally does not happen
I wrote this article a week ago, but I can not stop with the refactoring of the above-described bicycle. I rewrote one, rewrote another, and now it seems that you can’t show it to anyone until the third is fixed. In general, we will consider this a working prototype.

Localization without localization


The localization system is, first of all, the control system of the value of a certain set of constants. The selected language in it is just a key by which you can switch these sets. By changing the key in one place, you change the constants in the entire application. And if you are aware of this thought, then perhaps look at the capabilities of localization systems from a different angle. Let's consider together several cases of non-standard use of the system.

Change the appearance of the application

JavaScript Localization

In our work we use the same set of modules to create typical mobile sites selling content. For each partner, we change some of the styles, which also include a set of icons. In addition, on holidays or promotions, we change the look of certain websites to create a suitable mood for the target audience (for example, a New Year's style or a theme in honor of "Valentine's Day"). To use the same layout templates, we had to remove the addresses of pictures from them. We marked each such picture with some identifier, and all the addresses were recorded in a JSON object with the corresponding keys. In order for the system to work, we needed a kind of mechanism that would automatically assign all the actual addresses to all the pictures, based on the current configuration and theme. Consequently,on the one hand, this mechanism must receive configs, and on the other hand, it must return a value by a given identifier. I would also like it to be able to walk through the DOM tree and update all the marked images on its own, if the DOM is already formed. Does the functionality of the described system remind you anything?

Example of a JSON object with image addresses:

 { header: { menu: { normal: "/images/201409/menu__normal.png", hover: "/images/201409/menu__hover.png" }, search: "/images/201407/search__blue.png" }, ... footer: { icon: "/images/201408/question.png", up: "/images/201407/up.png" } } 

JavaScript Localization

This one-to-one mechanism repeats the logic of the localization system. And if so, why create it? We can force the localization module to do this work, only as the value of the language to use the name of the desired topic. In addition, as mentioned above, our localization system can also change the addresses of the plug-in styles, which means that we will gain control over the plug-in CSS code as well. If you do not want to mix translations and languages ​​within a single configuration file, then you can not do without OOP. OOP answers the question of how to create two localization instances for different purposes (one rolls up translations, the other - styles).

Changing coefficients in mathematical formulas

Programmers from the company Unicum, which develops software for gaming machines, laid in the localization files also mathematical coefficients for calculating the winnings. This was due to the fact that gaming legislation may have significant differences depending on the country in which it is supposed to use their software. For example, the average percentage of cash winnings of gaming machines could range from 60% to 95%.

In addition, when developing a game with different levels of difficulty, you can also use a localization system for storing and quickly changing coefficients that affect the game balance.

Change of storyline

If the plot of your game is described as a graph and saved as a JSON object, then by transferring it to a localization system that can perform object fusion operations, you can create patches and additions to the main storyline. However, you can also create several storylines themselves and select them randomly when you start the game (here the key will not be the “chosen language”, but the “selected plot”).

Changing the rules of the game

What is the difference between blackjack and point game? Not much. The main difference is the number of cards in the deck 54, not 36. If you ever write online blackjack (as I did half a year ago), then you will have the option of cramming the deck into JSON, and putting it into a copy of the localization system and add to the settings option "switching rules". Now our localization system with a slight movement of the hand turns into a system for choosing the rules of the game.

Results and conclusions


Javascript:


We think over the assembly system:


We choose the finished article from Denis Lukyanov .

When layout:


Use rtl-css , look Alexander Tevosyan .

Text:


We read Pavel Doronin .

In the text to remove:


See Kristina Yaroshevich .

Add:


For information style:


We use chapters. ed., we look at Maxim Ilyakhov , we prepare screenshots .

Some goodies:

Languages ​​and their name
 { ar: "عربي", //  az: "Azərbaycan", //  be: "і", //  bg: "", //  bs: "Bosanski", //  ca: "Català", //  cs: "Čeština", //  da: "Dansk", //  de: "Deutsch", //  el: "Ελληνικά", //  en: "English", //  es: "Español", //  et: "Eesti", //  fa: "فارسی", //  fi: "Suomi", //  fr: "Français", //  he: "עברית", //  hr: "Hrvatski", //  hu: "Magyar", //  hy: "Հայ", //  ja: "日本語", //  id: "Indonesian", //  is: "Ljóðmæli", //  it: "Italiano", //  ka: "ქართული", //  kk: "Ққ", //  ko: "한국어", //  ky: "", //  lt: "Lietuvių", //  lv: "Latviešu", //  mk: "", //  ms: "Melayu", //  mt: "Malti", //  nl: "Nederlandse", //  no: "Norsk", //  pl: "Polski", //  pt: "Portuguesa", //  ro: "Română", //  ru: "", //  sk: "Slovenskej", //  sl: "Slovenski", //  sq: "Shqiptare", //  sr: "", //  sv: "Svenska", //  tg: "ҷӣ", //  th: "ไทย", //  tr: "Türk", //  tt: "", //  uk: "ї", //  vi: "Việt nam", //  zh: "中国" //  } 

Source: https://habr.com/ru/post/268915/


All Articles