• Stars
    star
    122
  • Rank 292,031 (Top 6 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 9 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Internationalization package for Meteor with React integration.

vazco/universe:i18n

GitHub Workflow Status GitHub


Internationalization package that offers much better performance than others.

The package supports:

  • namespacing of translation strings
  • YAML file formats
  • string interpolation
  • both types of parameters (named and positional)
  • typographic notation of numbers
  • regional dialects inheritance mechanism (e.g. 'en-US' inherits from translations assigned to 'en')
  • ECMAScript 6 modules
  • supports dynamic imports (Client does not need to download all translations at once)
  • remote loading of translations from a different host

Table of Contents

Installation

$ meteor add universe:i18n

Typescript

This package is integrated with zodern:types package. If you use zodern:types package in your project, it will automatically download the current type definitions.

However, you can still use types from the DefinitelyTyped package.

$ meteor npm install --save @types/meteor-universe-i18n

Migration to v2

  • Locales and currency data have been removed. That means, the following functions are no longer available: parseNumber, getLanguages, getCurrencyCodes, getCurrencySymbol, getLanguageName, getLanguageNativeName and isRTL. If your app needs them, check if the Intl API suits your needs. If not, copy the values you need from v1 source
  • The _purify option has been removed, as it wasn't working on the server anyway. For detailed explanation and an alternative, see this comment
  • Both createTranslator and createReactiveTranslator have been removed. If your project is using them, simply create your own helpers on top of getTranslation. You can copy or modify implementation from the v1 source.
  • The built-in React integration is no longer there, i.e., both createComponent and getRefreshMixin functions have been removed. Refer to the Integration with React section for details.
  • The Blaze integration package (universe-i18n-blaze) is deprecated. Refer to the Integration with Blaze section for details.

If you want to read more about v2, check out the roadmap as well as the main pull request.

Usage

import i18n from 'meteor/universe:i18n';

Setting/getting locale

i18n.setLocale('en-US', params);
i18n.getLocale(); // en-US

Params in setLocale are optional yet offer additional possibilities:

  • noDownload - disables downloading translation file on the client (client side)
  • silent - protects against broadcasting the refresh event (both sides)
  • async - downloads translation file in an async way (client side)
  • fresh - downloads fresh translations (ignores the browser cache)

If you want to use the browser's locale, you can do it as follows:

// somewhere in the page layout (or possibly in the router?)
function getLang() {
  return (
    navigator.languages?.[0] ||
    navigator.language ||
    navigator.browserLanguage ||
    navigator.userLanguage ||
    'en-US'
  );
}

i18n.setLocale(getLang());

Keep in mind though that it will work on the client side and in server methods called from client (on the same connection). In other places where connection is not detected, you must provide it self. By the way, It's good option is also use 'accept-language' header to recognize client's locale on server side.

Adding translations by methods

import i18n from 'meteor/universe:i18n';

i18n.addTranslation('en-US', 'Common', 'no', 'No');
i18n.addTranslation('en-US', 'Common.ok', 'Ok');

i18n.addTranslations('en-US', {
  Common: {
    hello: 'Hello {$name} {$0}!',
  },
});

i18n.addTranslations('en-US', 'Common', {
  hello: 'Hello {$name} {$0}!',
});

Getting translations

You can obtain translation strings by using i18n.getTranslation() or, quicker, calling i18n.__()

i18n.__(key);
i18n.__(key, params);
i18n.__(namespace, key, params);
i18n.__(key, key, key, key, params);

// same with "getTranslation", e.g.:
i18n.getTranslation(key, key, key, key, params);

String Interpolation

If needed, parameters can be passed as the last one of the function arguments, as an array or an object since they can be named or positional (ie. indexed). Additionally, for positional parameters it is irrelevant whether they are passed as an array or an object with keys '0', '1', '2'... Besides, 'positional' properties of such an object can be mixed with named ones.

_namespace: ''
hello: Hello {$name}!
lengthOfArr: length {$length}
items: The first item is {$0} and the last one is {$2}!
i18n.__('hello', { name: 'Ania' }); // output: Hello Ania!
i18n.__('lengthOfArr', { length: ['a', 'b', 'c'].length }); // output: length 3
i18n.__('items', ['a', 'b', 'c']); // output: The first item is a and the last one is c!

Translations files

Instead of setting translations directly through i18n.addTranslation(s), you can store them in YAML or JSON files, named .i18n.yml, .i18n.json accordingly. Translation files should be imported on the client side:

// client/main.jsx
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { App } from '/imports/ui/App';

import '../i18n/en.i18n.json';
import '../i18n/de.i18n.json';

Meteor.startup(() => {
  render(<App />, document.getElementById('react-target'));
});

Recognition locale of translation

Files can be named freely as long as they have their respective locale declared under the key '_locale'.

# translation.i18n.yml
_locale: en-US
title: Title

Otherwise, files should be named after their respective locales or placed in directories named accordingly.

en.i18n.yml
en.i18n.json
en_us.i18n.yml
en-us.i18n.yml
en/us.i18n.yml
en-US/someName.i18n.yml
someDir/en-us/someName.i18n.yml

Namespace

Translations in a translation file can be namespaced (depending on where they are located). A namespace can be set up only for a whole file, yet a file as such can add more deeply embedded structures.

Tip: A good practise is using PascalCase for naming of namespaces and for leafs use camelCase. This helps protect against conflicts namespace with string.

Splitting keys in file

Comma-separated or x-separated keys in file e.g.:

_splitKey: '.'
Chapter.title: Title
Chapter.xxx: XXX

or

_splitKey: ':'
Chapter:title: Title
Chapter:xxx: XXX

Will be loaded as following structure:

Chapter:
  title: Title
  xxx: XXX

Translation in packages

For example, translations files in packages are by default namespaced by their package name.

// file en.i18n.json in the universe:profile package
{
  "_locale": "en",
  "userName": "User name"
}
i18n.__('universe:profile', 'userName'); // output: User name

You can change a default namespace for a file by setting a prefix to this file under the key "_namespace".

// file en.i18n.json in the universe:profile package
{
  "_locale": "en-US",
  "_namespace": "Common",
  "userName": "User name"
}

And then:

i18n.__('Common', 'userName'); // output: User name
i18n.__('Common.userName'); // output: User name

TIP: You can also add translations from a package on the top-level by passing empty string "" in the key "_namespace".

Translation in application

Here your translations by default are not namespaced which means that your translations from an application space are top-level and can override every other namespace.

For example:

# file en_us.i18n.yml in an application space (not from a package)
_locale: en-US
userName: user name
i18n.__('userName'); // output: User name

If you want to add translations under a namespace, you should define it in the key '_namespace'.

_locale: en-US
_namespace: User.Listing.Item
userName: User name
i18n.__('User.Listing.Item.userName'); // output: User name
i18n.__('User', 'Listing', 'Item.userName'); // output: User name

Listener on language change

// adds a listener on language change
i18n.onChangeLocale(function (newLocale) {
  console.log(newLocale);
});

// removes a listener
i18n.offChangeLocale(fn);

// does something on the first language change and then stops the listener
i18n.onceChangeLocale(fn);

API

// adds a translation
i18n.addTranslation(locale, namespace, key, ..., translation);

// adds translations (same as addTranslation)
i18n.addTranslations(locale, namespace, translationsMap);

// gets a translation in params (_locale)
i18n.getTranslation(namespace, key, ..., params);
i18n.__(namespace, key,..., params);

// get translations (locale is by default set to the current one)
i18n.getTranslations(namespace, locale);

// options
i18n.setOptions({
    // default locale
    defaultLocale: 'en-US',

    // opens string
    open: '{$',

    // closes string
    close: '}',

    // decides whether to show when there's no translation in the current and default language
    hideMissing: false,

    // url to the host with translations (default: Meteor.absoluteUrl())
    // useful when you want to load translations from a different host
    hostUrl: 'http://current.host.url/',

    // (on the server side only) gives you the possibility to add/change response headers
    translationsHeaders = {'Cache-Control':'max-age=2628000'},

    // synchronizes server connection with locale on client. (method invoked by client will be with client side locale)
    sameLocaleOnServerConnection: true
});

// supports dynamic imports
import('../fr.i18n.yml');

// changes locale
i18n.setLocale(locale, params);
// this function on the client side returns a promise (but only if parameter `noDownload !== true`)
// Called from client, it sets locale for connection on server side.
// It mean that method invoked by client will be with client side locale.
// You can turn off this synchronization by setting the global option `sameLocaleOnServerConnection: false`

// Setting locale for connection (if `connectionId` is not provided system will try detect current connection id)
i18n.setLocaleOnConnection(locale, connectionId=);
// this function sets locale in all places, where connection can be detected (like meteor methods)

// gets the current locale
i18n.getLocale();

// fetches translations file from the remote server (client/server)
i18n.loadLocale(locale, params)
// @params on the client { fresh = false, async = false, silent = false,
// host = i18n.options.hostUrl, pathOnHost = i18n.options.pathOnHost }
// @params on server { queryParams = {}, fresh = false, silent = false,
// host = i18n.options.hostUrl, pathOnHost = i18n.options.pathOnHost }
// on the server side, this method uses HTTP.get with query parameter `type=json` to fetch json data
// on the client side, it adds a new script with translations to the head node
// this function returns a promise

// executes function in the locale context,
i18n.runWithLocale(locale, func)
// it means that every default locale used inside a called function will be set to a passed locale
// keep in mind that locale must be loaded first (if it is not bundled)

Integrations

This section showcases some of the ways of integrating universe:i18n with different frameworks. More detailed examples can be found in the integrations directory.

Integration with React

There are few different ways to integrate this package with a React application. Here is the most "React-way" solution facilitating React Context:

// imports/i18n/i18n.tsx
import { i18n } from 'meteor/universe:i18n';
import React, {
  ReactNode,
  createContext,
  useCallback,
  useContext,
  useEffect,
  useState,
} from 'react';

const localeContext = createContext(i18n.getLocale());

export type LocaleProviderProps = { children: ReactNode };

export function LocaleProvider({ children }: LocaleProviderProps) {
  const [locale, setLocale] = useState(i18n.getLocale());
  useEffect(() => {
    i18n.onChangeLocale(setLocale);
    return () => {
      i18n.offChangeLocale(setLocale);
    };
  }, [setLocale]);

  return (
    <localeContext.Provider value={locale}>{children}</localeContext.Provider>
  );
}

export function useLocale() {
  return useContext(localeContext);
}

export function useTranslator(prefix = '') {
  const locale = useLocale();
  return useCallback(
    (key: string, ...args: unknown[]) =>
      i18n.getTranslation(prefix, key, ...args),
    [locale],
  );
}

Created above useTranslator hook can be used in the following way:

// imports/ui/App.tsx
import React from 'react';
import { LocaleProvider, useTranslator } from '/imports/i18n/i18n';

const Component = () => {
  const t = useTranslator();
  return (
    <div>
      <h1>{t('hello')}</h1>
    </div>
  );
};

export const App = () => (
  <LocaleProvider>
    <Component />
  </LocaleProvider>
);

Here are other options for React integration:

The most straight-forward approach. Gets transaltion every time language is changed.
import { i18n } from 'meteor/universe:i18n';
import { useEffect, useState } from 'react';

export function useTranslation(key: string, ...args: unknown[]) {
  const setLocale = useState(i18n.getLocale())[1];
  useEffect(() => {
    i18n.onChangeLocale(setLocale);
    return () => {
      i18n.offChangeLocale(setLocale);
    };
  }, [setLocale]);
  return i18n.getTranslation(key, ...args);
}
Improved version of the solution above. Gets translation every time acctual translation changes, instead of reacting on language changes. Usefull when different languages has same translations.
import { i18n } from 'meteor/universe:i18n';
import { useEffect, useState } from 'react';

export function useTranslation(key: string, ...args: unknown[]) {
  const getTranslation = () => i18n.getTranslation(key, ...args);
  const [translation, setTranslation] = useState(getTranslation());
  useEffect(() => {
    const update = () => setTranslation(getTranslation());
    i18n.onChangeLocale(update);
    return () => {
      i18n.offChangeLocale(update);
    };
  }, []);
  return translation;
}
The meteor-way solution that facilitates ReactiveVar and useTracker. The advantage of this approach is creating only one listener instead of creating a listener on every locale change.
import { i18n } from 'meteor/universe:i18n';
// https://docs.meteor.com/api/reactive-var.html
import { ReactiveVar } from 'meteor/reactive-var';
// https://blog.meteor.com/introducing-usetracker-react-hooks-for-meteor-cb00c16d6222
import { useTracker } from 'meteor/react-meteor-data';

const localeReactive = new ReactiveVar<string>(i18n.getLocale());
i18n.onChangeLocale(localeReactive.set);

export function getTranslationReactive(key: string, ...args: unknown[]) {
  localeReactive.get();
  return i18n.getTranslation(key, ...args);
}

export function useTranslation(key: string, ...args: unknown[]) {
  return useTracker(() => getTranslationReactive(key, ...args), []);
}

Integration with Blaze

import { i18n } from 'meteor/universe:i18n';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

const localeReactive = new ReactiveVar<string>(i18n.getLocale());
i18n.onChangeLocale(localeReactive.set);

Template.registerHelper('__', function (key: string, ...args: unknown[]) {
  localeReactive.get();
  return i18n.getTranslation(key, ...args);
});

Integration with SimpleSchema package

Add following-like code to main.js:

const registerSchemaMessages = () => {
  SimpleSchema.messages({
    required: i18n.__('SimpleSchema.required'),
  });
};

i18n.onChangeLocale(registerSchemaMessages);
registerSchemaMessages();

Put the default error messages somewhere in your project on both sides e.g.:

_locale: en
_namespace: SimpleSchema

required: '[label] is required'
minString: '[label] must be at least [min] characters'
maxString: '[label] cannot exceed [max] characters'
minNumber: '[label] must be at least [min]'
maxNumber: '[label] cannot exceed [max]'
minNumberExclusive: '[label] must be greater than [min]'
maxNumberExclusive: '[label] must be less than [max]'
minDate: '[label] must be on or after [min]'
maxDate: '[label] cannot be after [max]'
badDate: '[label] is not a valid date'
minCount: 'You must specify at least [minCount] values'
maxCount: 'You cannot specify more than [maxCount] values'
noDecimal: '[label] must be an integer'
notAllowed: '[value] is not an allowed value'
expectedString: '[label] must be a string'
expectedNumber: '[label] must be a number'
expectedBoolean: '[label] must be a boolean'
expectedArray: '[label] must be an array'
expectedObject: '[label] must be an object'
expectedConstructor: '[label] must be a [type]'
RegEx:
  msg: '[label] failed regular expression validation'
  Email: '[label] must be a valid e-mail address'
  WeakEmail: '[label] must be a valid e-mail address'
  Domain: '[label] must be a valid domain'
  WeakDomain: '[label] must be a valid domain'
  IP: '[label] must be a valid IPv4 or IPv6 address'
  IPv4: '[label] must be a valid IPv4 address'
  IPv6: '[label] must be a valid IPv6 address'
  Url: '[label] must be a valid URL'
  Id: '[label] must be a valid alphanumeric ID'
keyNotInSchema: '[key] is not allowed by the schema'

Running Tests

meteor test-packages --driver-package meteortesting:mocha universe:i18n

License

Like every package maintained by Vazco, universe:i18n is MIT licensed.

More Repositories

1

uniforms

A React library for building forms from any schema.
TypeScript
1,957
star
2

universe-modules

Use ES6 / ES2015 modules in Meteor with SystemJS
JavaScript
52
star
3

meteor-universe-collection

Meteor collection on steroids.
JavaScript
30
star
4

sparrowql

Declarative MongoDB aggregations.
TypeScript
30
star
5

meteor-universe-autoform-select

Meteor universe autoform select
JavaScript
16
star
6

meteor-universe-e2e

Complete end-to-end/acceptance testing solution for Meteor based on Mocha & Puppeteer
JavaScript
15
star
7

meteor-universe-modules-npm

Universe package to import of npm packages that works on client and server in meteor
JavaScript
13
star
8

meteor-universe-selectize

Meteor universe selectize standalone
JavaScript
12
star
9

meteor-slidedeck

Meteor Slide Deck
JavaScript
10
star
10

meteor-universe-accounts-ui

Accounts UI replacement for Universe using React and Semantic UI
JavaScript
10
star
11

universe-react-bootstrap

ReactBootstrap project wrapped for Meteor
JavaScript
9
star
12

meteor-vazco-maps

Google Maps wrapper with gmaps.js plugin
JavaScript
9
star
13

meteor-universe-utilities-react

JavaScript
9
star
14

eslint-config-vazco

ESLint rules used across Vazco.eu in React projects
JavaScript
9
star
15

meteor-universe-utilities

Many awesome utilities
JavaScript
6
star
16

universe-ecmascript

Supports ES2015+ in all .js files with modules
JavaScript
6
star
17

universe-core

JavaScript
5
star
18

universe-react-table

Fast, flexible, and simple data tables for meteor
JavaScript
5
star
19

process-custodian

This package helps with organizing of tasks between few instances of same app, It can identify processes and track them activity.
JavaScript
5
star
20

universe-react-flipcard

React flipcard for meteor
JavaScript
5
star
21

meteor-universe-access

Document level access/permission for publication (provides new method publish in allow/deny)
JavaScript
5
star
22

i18n-table

A React component that helps organize and manage translations
TypeScript
4
star
23

uup

Simple version of mup (Meteor UP) that doesn't run as a root
JavaScript
4
star
24

meteor-universe-utilities-blaze

Universe Utilities for blaze
JavaScript
4
star
25

universe-files

Easy file uploading feature for meteor with local file system and amazon s3 support
JavaScript
3
star
26

meteor-universe-ui

JavaScript
3
star
27

meteor-universe-any-join

JavaScript
3
star
28

github-actions-branch-name

TypeScript
3
star
29

vazco-maps-demo-app

Meteor demo app - Vazco Maps showcase
JavaScript
3
star
30

meteor-universe-admin

JavaScript
3
star
31

meteor-universe-autoform-select-demo

Meteor Universe Autoform Select DEMO
HTML
3
star
32

meteor-universe-modules-compiler

Compiler based on babel-compiler with enabled modules
JavaScript
3
star
33

meteor-universe-autoform-scheduler

Meteor universe autoform scheduler
JavaScript
2
star
34

universe-react-accordion

React accordion component for Meteor Js
JavaScript
2
star
35

universe-mailchimp-v3-api

JavaScript
2
star
36

meteor-pipelines

Docker image for BitBucket Pipelines CI with Meteor & all required stuff for E2E and other testing
Dockerfile
2
star
37

uniforms-example-app

Simple demo app showing uniforms usage
JavaScript
2
star
38

universe-i18n-blaze

Blaze helper
JavaScript
2
star
39

universe-buckets

Sandboxes your publication data, es6/7
JavaScript
2
star
40

meteord

MeteorD - Docker Runtime for Meteor Apps for Production Deployments
Shell
2
star
41

meteor-universe-gallery

Meteor universe gallery
JavaScript
2
star
42

meteor-universe-reactive-queries

Adds reactive query parameters to iron router, ( no refresh, is reactive, works like session)
JavaScript
2
star
43

meteor-universe-modules-entrypoint

Use universe:modules without need for *.import.js extension and run Meteor through one entry point.
JavaScript
2
star
44

meteor-universe-ui-react

JavaScript
1
star
45

mongo-collections-dumper

Easily dump and restore small mongo collections without using mongodump. Useful for fixtures reloading.
JavaScript
1
star
46

universe-react-clock-picker

React TimePicker for Meteor JS
JavaScript
1
star
47

meteor-universe-selectize-demo

Meteor universe selectize standalone demo
JavaScript
1
star
48

meteor-universe-ui-react-forms

JavaScript
1
star
49

meteor-universe-calendar

JavaScript
1
star
50

forminer

Vazco Form Builder Docs
MDX
1
star
51

universe-fb-share-content

Publish post on facebook (as a page or user)
JavaScript
1
star
52

meteor-universe-admin-users

JavaScript
1
star
53

github-consumer

For demonstration reasons
JavaScript
1
star
54

teamcity-agent-node

Docker image for TeamCity Agent with useful stuff for node.js projects used across Vazco.eu
Dockerfile
1
star
55

meteor-universe-react-carousel

Carousel component built with React
JavaScript
1
star
56

open-standards

JavaScript
1
star
57

universe-react-ui-tree

React tree component for meteor (It use universe modules)
JavaScript
1
star