Good day, habravchane!
In this article I will talk about a simple version of the solution to the problem of managing text and localization in a web application, which you can implement or use ready-made.
I have long wanted to share my own thoughts and experiences ... and, of course, talk for life.
Obviously, solutions for managing texts and localization already exist, but they did not suit me for various reasons: cumbersome, inconvenient to use, does not fit, does not correspond to my vision of solving this problem, lacks functionality.
Besides, I don’t really like third-party libraries because of their tendency to grow (this is when we need only a small part of all the functionality).
In the company in which I work, there is a solution, but, in my opinion, it is also far from ideal. And the need for backward compatibility with older versions makes it unnecessarily complicated.
At some point, I wanted something simple, easy, understandable and infinitely expandable for different tasks.
Here everything seems to be clear. Or not? Let's think about what we would like.
We need to somehow get localized texts. Texts may contain variables. Variables can be localized too ?! In theory, yes. And if the variable is a date or a number ?! And still support markdown. And finally, some solution in case the text is not found.
The basis will be a simple object, where the key is the code of the text, and the value is the actual text, nothing complicated:
const textsBundle = { 'button.open': 'Open', 'button.save': 'Save', }; function TextManager(texts) { this.getText = function(code) { return texts[code]; }; } const textManager = new TextManager(textsBundle); textManager.getText('button.open');
Key names are a separate topic. It is better to immediately agree on any one option, otherwise different keys will "plow" :). There is no one solution, choose what you think is more convenient and more relevant to the project. Personally, I like the first of the following:'button.open.label'
'button.open.help_text'
or'button.label.open'
'button.help_text.open'
or'label.button.open'
'help_text.button.open'
Next, we need a mechanism that would be able to perform any manipulations with the text before producing the final result, for example, insert parameters. And then an interesting idea came to me - what if I used middleware to manipulate text? After all, I have not seen such solutions ... well, or I was looking bad :).
We will decide on the requirements for middleware: at the entrance, the middleware will receive text and parameters, and produce the resulting text, after the necessary manipulations.
The first middleware will receive the original text, and the following - the text from the previous middleware. We add the missing code:
function TextManager(texts, middleware) { function applyMiddleware(text, parameters, code) { if (!middleware) return text; return middleware.reduce((prevText, middlewareItem) => middlewareItem(prevText, parameters, code), text); } this.getText = function(code, parameters) { return applyMiddleware(texts[code], parameters, code); }; }
TextManager can produce text by its code. It can also be extended using middleware, which opens up many possibilities, for example:
We write a couple of middleware needed. You will need them 100%.
Insertparams
Allows the use of parameters in the texts. For example, we need to display the text "Hello {{username}}". The following middleware will provide this:
function InsertParams(text, parameters) { if (!text) return text; if (!parameters) return text; let nextText = text; for (let key in parameters) { if (parameters.hasOwnProperty(key)) { nextText = text.replace('{{' + key + '}}', parameters[key]); } } return nextText; }
UseCodeIfNoText
Allows you to return the text code, instead of undefined
, if the text was not found:
function UseCodeIfNoText(text, parameters, code) { return text ? text : code; }
Total we get about the following use:
const textsBundle = { 'text.hello': 'Hello', 'text.hello_with_numeric_parameter': 'Hello {{0}}', 'text.hello_with_named_parameter': 'Hello {{username}}', }; const textManager = new TextManager(textsBundle, [InsertParams, UseCodeIfNoText]); textManager.getText('nonexistent.code') // 'nonexistent.code' textManager.getText('text.hello') // 'Hello' textManager.getText('text.hello_with_numeric_parameter', ['Vasya']) // 'Hello Vasya' textManager.getText('text.hello_with_named_parameter', { username: 'Petya' }) // 'Hello Petya'
To begin with, we initialize TextManager
at the top level and add texts.
In my opinion, it is best to pull texts from the server, but for simplicity I will not do this.
const textsBundle = { 'text.hello': 'Hello {{username}}' } function TextManagerProvider({ children }) { const textManager = new TextManager(textsBundle, [InsertParams, UseCodeIfNoText]); return ( <TextManagerContext.Provider value={textManager}> {children} </TextManagerContext.Provider> ) }
Further in the component we use textManager
, for example, with the help of a hook, and we get the necessary text by code.
function SayHello({ username }) { const textManager = useContext(TextManagerContext); return ( <div> {textManager.getText('text.hello', { username })} </div> ) }
You ask "What is the localization?".
Everything is very simple - when changing the language, create a new copy of TextManager
, add texts and immediately get the result.
As you can see from the examples, the use is extremely simple, and thanks to middleware, you can extend the functionality indefinitely.
I posted my implementation on github and plan to further develop a text-manager . Use, offer improvements and, as they say there, you're welcome! :)
So I fulfilled my wish. - I wrote an article on Habr. I very much hope that this article will be useful and will appeal to the community.
Thanks for paying attention.
Source: https://habr.com/ru/post/458576/
All Articles