Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
@yandex-cloud/i18n
Advanced tools
Utilities in the I18N package are designed for internationalization of Yandex Cloud UI services.
npm install --save @yandex-cloud/i18n
Accepts options
object with optional logger
that would be used for logging library warnings.
Logger should have explicit log
method with following signature:
message
- string of message that would be loggedoptions
- object of logging options:
level
- level for logging message, always 'info'
logger
- where to log library messagesextra
- additional options object, with a single type
string, that is always i18n
keysets/en.json
{
"wizard": {
"label_error-widget-no-access": "No access to the chart"
}
}
keysets/ru.json
{
"wizard": {
"label_error-widget-no-access": "Нет доступа к чарту"
}
}
index.js
const ru = require('./keysets/ru.json');
const en = require('./keysets/en.json');
const {I18N} = require('../src');
const i18n = new I18N();
i18n.registerKeysets('ru', ru);
i18n.registerKeysets('en', en);
i18n.setLang('ru');
console.log(
i18n.i18n('wizard', 'label_error-widget-no-access')
); // -> "Нет доступа к чарту"
i18n.setLang('en');
console.log(
i18n.i18n('wizard', 'label_error-widget-no-access')
); // -> "No access to the chart
// Keyset allows for a simpler translations retrieval
const keyset = i18n.keyset('wizard');
console.log(
keyset('label_error-widget-no-access')
); // -> "No access to the chart"
i18n.setLang('ru');
console.log(
keyset('label_error-widget-no-access')
); // -> "Нет доступа к чарту"
// Checking if keyset has a key
if (i18n.has('wizard', 'label_error-widget-no-access')) {
i18n.i18n('wizard', 'label_error-widget-no-access')
}
The library supports templating. Templated variables are enclosed in double curly brackets, and the values are passed to the i18n function as a key-value dictionary:
keysets.json
{
"label_template": "No matches found for '{{inputValue}}' in '{{folderName}}'"
}
index.js
i18n('label_template', {inputValue: 'something', folderName: 'somewhere'}); // => No matches found for "something" "somewhere"
Pluralization can be used for easy localization of keys that depend on numeric values.
The pluralized key contains 4 values (for the numerals 1, 2-4, 5-9, and 0, respectively). Variable name for pluralization: count
.
For example:
keysets.json
{
"label_seconds": ["{{count}} second is left", "{{count}} seconds are left", "{{count}} seconds are left", "No time left"]
}
index.js
i18n('label_seconds', {count: 1}); // => 1 second
i18n('label_seconds', {count: 3}); // => 3 seconds
i18n('label_seconds', {count: 7}); // => 7 seconds
i18n('label_seconds', {count: 10}); // => 10 seconds
i18n('label_seconds', {count: 0}); // => No time left
To type the i18nInstance.i18n
function, follow the steps:
Prepare a JSON keyset file so that the typing procedure can fetch data. Where you fetch keysets from, add creation of an additional data.json
file. To decrease the file size and speed up IDE parsing, you can replace all values by 'str'
.
// Example from the console
async function createFiles(keysets: Record<Lang, LangKeysets>) {
await mkdirp(DEST_PATH);
const createFilePromises = Object.keys(keysets).map((lang) => {
const keysetsJSON = JSON.stringify(keysets[lang as Lang], null, 4);
const content = umdTemplate(keysetsJSON);
const hash = getContentHash(content);
const filePath = path.resolve(DEST_PATH, `${lang}.${hash.slice(0, 8)}.js`);
// <New lines>
let typesPromise;
if (lang === 'ru') {
const keyset = keysets[lang as Lang];
Object.keys(keyset).forEach((keysetName) => {
const keyPhrases = keyset[keysetName];
Object.keys(keyPhrases).forEach((keyName) => {
// mutate object!
keyPhrases[keyName] = 'str';
});
});
const JSONForTypes = JSON.stringify(keyset, null, 4);
typesPromise = writeFile(path.resolve(DEST_PATH, `data.json`), JSONForTypes, 'utf-8');
}
// </New lines>
return Promise.all([typesPromise, writeFile(filePath, content, 'utf-8')]);
});
await Promise.all(createFilePromises);
}
In your ui/utils/i18n
directories (where you configure i18n and export it to be used by all interfaces), import the typing function I18NFn
with your Keysets
. After your i18n has been configured, return the casted function
// Example from the console
import {I18NFn} from '@yandex-cloud/i18n';
// This must be a typed import!
import type Keysets from '../../../dist/public/build/i18n/data.json';
const i18nInstance = new I18N();
type TypedI18n = I18NFn<typeof Keysets>;
// ...
export const ci18n = (i18nInstance.i18n as TypedI18n).bind(i18nInstance, 'common');
export const cui18n = (i18nInstance.i18n as TypedI18n).bind(i18nInstance, 'common.units');
export const i18n = i18nInstance.i18n.bind(i18nInstance) as TypedI18n;
Typing logic
There are several typing use cases:
i18n('common', 'label_subnet'); // ok
i18n('dcommon', 'label_dsubnet'); // error: Argument of type '"dcommon"' is not assignable to parameter of type ...
i18n('common', 'label_dsubnet'); // error: Argument of type '"label_dsubnet"' is not assignable to parameter of type ...
const someUncomputebleString = `label_random-index-${Math.floor(Math.random() * 4)}`;
i18n('some_service', someUncomputebleString); // ok
for (let i = 0; i < 4; i++) {
i18n('some_service', `label_random-index-${i}`); // ok
}
const labelColors = ['red', 'green', 'yelllow', 'white'] as const;
for (let i = 0; i < 4; i++) {
i18n('some_service', `label_color-${labelColors[i]}`); // ok
}
const labelWrongColors = ['red', 'not-existing', 'yelllow', 'white'] as const;
for (let i = 0; i < 4; i++) {
i18n('some_service', `label_color-${labelWrongColors[i]}`); // error: Argument of type '"not-existing"' is not assignable to parameter of type ...
}
Why typing via a class isn't supported
This function can break or complicate some i18n scenarios, so it was added as a functional extension. If it proves effective, we would probably add it to a class to avoid casting exported functions.
Why built-in methods might fail
Implementing of traversal of nested structures and conditional types using typed built-in function methods is a complex enough task. That's why typing works only when using a direct function call and a bind
call up to the third argument.
Why can't I generate a ts file straightforwardly to typecast key values as well?
You can do that by passing the result type to I18NFn. However, with large file sizes, ts starts consuming huge amounts of resources, slowing down the IDE dramatically, but with JSON file this is not the case.
Why other methods of the I18N class haven't been typed?
They can be typed, we'll appreciate if you help implementing it. The case is that other methods are used in 1% of cases.
FAQs
i18n library for Yandex Cloud UI services
We found that @yandex-cloud/i18n demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 6 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.