npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@panter/manul-i18n

v0.8.4

Published

i18n-Solution for mantra apps (meteor + react). Brought to you by Panter's Manul-Team. Inspired by https://github.com/vazco/meteor-universe-i18n

Downloads

32

Readme

manul-i18n

i18n-Solution for mantra apps (meteor + react). Brought to you by Panter's Manul-Team. Inspired by https://github.com/vazco/meteor-universe-i18n

npm install --save @panter/manul-i18n

Under development. Issues, Feedback and PR's very welcome!

Features

  • supports meteor-collections as store for translations or yaml-files (with https://github.com/vazco/meteor-universe-i18n)
  • provides <T>-Component for easy translations (we call it "Mr. T")
  • you can manually translate strings with
  • you can translate SimpleSchemas in a hoc. This comes handy with <AutoForm> from https://github.com/vazco/uniforms/

Usage

First, setup the service in the context (see below), then add the translations to your store.

Structure of translations

You can group your translations into objects/namspaces, no matter if you use the yaml-store or the collection-store:

home:
  title: "Manul"
  subTitle: "aka Pallas' cat"
  greeting: "Welcome {$username}"
  content:
    image:
      altText: "An image of a manul"

login:
  email: "Email-Adress"
  password:
    label: "Password"
register:
  firstname: "Firstname"
  lastname: "Lastname"
  gender:
    label: "Gender"
    m: "Male"
    f: "Female"
    other: "Something different"

You can call these translations by the path:

i18n.t("home.title"); // will return "Manul"
i18n.t("home.greeting", {username: "macrozone"}); // will return "Welcome macrozone"
i18n.t("home") // will return an object with the translations

// in react-components
<T>home.subTitle</T> // will render <span>aka Pallas' cat</span>

See below for more details.

In react components

We provide a <T>-Component to add translations to react-components. We call it "Mr. <T>". It's inspired by https://github.com/vazco/meteor-universe-i18n

import { T } from '@panter/manul-i18n`

//....

<div>
  <h1>
    <T>home.title</T>
  </h1>
  <h2>
    <T>home.subTitle</T>
  </h2>
  <p className="greeting">
    <T username={username}>home.greeting</T>
  </p>
</div>

Translate single key from the translation store (will render as ):

<T>home.content.title</T>

If you can't use react-node, but need a plain string, pass a function as child:

<T _id="home.content.image.alttext">{(altText) => <img alt={altText} src="..." />}</T>

MR. T can also pick properites from objects/documents by path. E.g. this reads the object page.meta.title.

<T doc={page} >meta.title</T>

also works with function-children:

<T doc={page} _id="meta.title">{(altText) => <img alt={altText} src="..." />}</T>

If you have simple object with keys as locales, e.g.

const myProperty = {
  de: "German",
  fr: "French",
  it: "English"
}

you can also use Mr. T to display the right translation (empty path)

<T doc={myProperty} />

Advanced:

If i18n.isEditMode() returns true (reactivly), it will render the key instead of the translation (does not work for doc-paths currently).

You can pass property disableEditorBypass to disable this feature on a <T>:

<T disableEditorBypass>path.to.key</T>

if i18n-service provides a editTranslationAction and i18n isEditMode() is true a click on <T> will call this function / mantra-action. This is handy if you want to allow page-editors to edit translations directly in the browser (e.g. with https://github.com/panter/manul-admin)

If editTranslationAction is a string, it will invoke a (mantra-) action by this path and pass the id of the translation to the action. E.g.:


editTranslationAction: "actions.admin.gotoEditTranslation"

// will call

// in admin.js actions

gotoEditTranslation({ i18n, FlowRouter }, translationId) {
  FlowRouter.go('admin.translations.edit', { _id: translationId });
}

It can also be a function. It then will be also called similar to the action

Usage outside of react-components

use i18n.t to translate. This is a reactive data-source, so it can be used in a Tracker.autorun-context like createContainer or in composeWithTracker.

i18n is available in the mantra-context.

const errorMessage = i18n.t("errors.login.failed");
alert(errorMessage);

const greeting = i18n.t("home.greeting", {username: "macrozone"});
alert(greeting);

i18n also provides some more functions:

i18n.getLocale(); // get the current locale. This is reactive.
i18n.setLocale(newLocale); // change the locale / language. All <T> will update.
i18n.supports(locale); // true if locale is supported
i18n.getSupportedLocales(); // get all supported locales
i18n.onChangeLocale(callback); // runs callback if locale is changed

Translate SimpleSchema

If you use SimpleSchema (https://github.com/aldeed/meteor-simple-schema), and something like https://github.com/vazco/uniforms/ to generate forms automatically from a schema, you might want to translate labels and error-messages.

We provide a "hoc" (higher order component) for that, that can be used in a container.

Here is an example for a login-form:


import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
import LoginForm from '../components/LoginForm';
import { withTranslatedSchema } from "@panter/manul-i18n";

export const composer = ({context}, onData) => {
  const {LocalState, SimpleSchema} = context();
  const loginSchema = new SimpleSchema({

    email: {
      type: String,
      regEx: SimpleSchema.RegEx.Email,
    },
    password: {
      type: String
    }
  });
  onData(null, {error, loginSchema});
};

export const depsMapper = (context, actions) => ({
  login: actions.account.login,
  context: () => context
});

export default composeAll(
  // provide on object with the propnames of the schemas
  // and the namespace as the values.
  withTranslatedSchema({loginSchema: 'login'}),
  composeWithTracker(composer),
  useDeps(depsMapper)
)(LoginForm);


// LoginForm.jsx


import React from 'react';
import AutoForm from 'uniforms/AutoForm';

export default ({loginSchema, login}) => (
  <AutoForm schema={loginSchema} onSubmit={login} />  
)

The translation will be loaded from . In the example above these translations are used:

login.email and login.password

or login.email.label, login.password.label

Advanced Schema translation

You can also translate allowedValues, e.g. in a <select>-field:

const registerSchema = new SimpleSchema({
  firstname: {
    type: String
  },
  lastname: {
    type: String
  },
  gender: {
    type: String,
    allowedValues: ["m", "f", "other"]
  }
});

// translation store contains (example in yaml)

register:
  firstname: "Vorname"
  lastname: "Nachname"
  gender:
    label: "Geschlecht"
    m: "Männlich"
    w: "Weiblich"
    other: "Etwas anderes"

//...

withTranslatedSchema({registerSchema: "register"})

Setup

With Collection-Store

Client

You should provide the service in your mantra context.js. Here is an example configuration with the collection store:

import { FlowRouter } from 'meteor/kadira:flow-router';
import { Meteor } from 'meteor/meteor';
//...
import { ReactiveVar } from 'meteor/reactive-var';
import { I18n } from '@panter/manul-i18n';
import CollectionTranslationStore from '@panter/manul-i18n/dist/stores/collection';

const translationStore = new CollectionTranslationStore({
  Meteor,
  ReactiveVar, // you have to provide this atm. because it is a meteor-package
  collection: Collections.Translations, // the Meteor.Collection that hold the translations
});

const i18n = new I18n({
  supportedLocales: ['de', 'en', 'fr', 'it'],
  defaultLocale: 'de',
  translationStore,
});

// ...

const context = {
  Meteor,
  LocalState,
  // ...
  i18n
}

The Collection should have the following schema:

{
  _id: {
    type: String,
  },
  value_de: {
    type: String,
    optional: true,
  },
  value_en: {
    type: String,
    optional: true,
  },
  //...
}

The _id will be used as the translation id. Use dot-notation to group them into objects (e.g. this is needed for schema-translations).

Example:

// structure in yaml-notation:
home:
  title: "Manul"
  subTitle: "aka Pallas' cat"
  content:
    image:
      altText: "An image of a manul"

// should be stored like this in your collection:

[
{
  _id: "home.title",
  value_en: "Manul"
},
{
  _id: "home.subTitle",
  value_en: "aka Palla's cat"
},
{
  _id: "home.content.image.altText",
  value_en: "An image of a manul"
}
]

Server

On the server you need to create a new translation store as well and pass your collection. This will start the publication:

import TranslationStore from '@panter/manul-i18n/dist/stores/collection';
import { Translations } from '/lib/collections';
import { Meteor } from 'meteor/meteor';

const translationStore = new TranslationStore({
  Meteor,
  collection: Translations,
});

you don't need to use the translationStore variable, but you can use it to create a simple server-side translation service (e.g. to translate emails), which is not yet supported by manul-i18n:

// on server, you always need to specify a locale
// that's why we do not name it t, so that one does not confuse it
/* eslint import/prefer-default-export: 0*/
export const translate = (locale, keyOrNamespace, params) => (
  translationStore.translate(keyOrNamespace, { _locale: locale, ...params })
);

Setup with universe-i18n

universe-i18n (https://github.com/vazco/meteor-universe-i18n)

provides an all-in-one solution for translations. It also has a <T>-component, but with fewer features.

If you need these features as well as schema-translations, you can use manul-i18n as an adapter for universe-i18n:

import { FlowRouter } from 'meteor/kadira:flow-router';
import { Meteor } from 'meteor/meteor';
//...
import UniverseI18n from 'meteor/universe:i18n';
import { I18n } from '@panter/manul-i18n';
import UniverseTranslationStore from '@panter/manul-i18n/dist/stores/universe_i18n';

const translationStore = new UniverseTranslationStore({
  universeI18n: UniverseI18n,
  options: {} // options for universe-i18n
});

const i18n = new I18n({
  supportedLocales: ['de', 'en', 'fr', 'it'],
  defaultLocale: 'de',
  translationStore,
});
//...

There is no server-initialization needed

Advanced configuration

All constructor properties:

const i18n = new I18n({
  defaultLocale, // the default locale
  supportedLocale, // all locales that are supported
  translationStore, // the store as seen above

  // whether it should use fallback locale if translation is missing
  // the rule is the following: xx_XX --> xx --> defaultLocale
  useFallbackForMissing, // defaults to true

  // pass function as isEditMode that uses a reactive data-source
  // if you do so and it changes to true,
  // all of your translations will show their keys
  // also, if you click on one of your translations (via T),
  // editTranslationAction will be called
  isEditMode, // defaults to () => false,

  // mantra-action (string) or function
  // it gets called with the translationId whenever a user clicks on a <T>-Component
  // while isEditMode is true.
  editTranslationAction,  

  // shouldShowKeysAsFallback defines whether it should show the keys of translation
  // if the translation is not available (can also be reactive datasource)
  // this is usefull for admins and/or in development-environement
  shouldShowKeysAsFallback = () => false,
});

Import yaml-translations into a collection.

This package also provides a function to import translations from an object into a collection, e.g. to migrate from yaml to collection-store:


importIntoCollection({
  translations, // the translation object, e.g. loaded from a yaml file
  collection, // the collection to import into
  locales: // the locales that should be imported
  override: false, // wheter or not to override already existing keys.
});

Seed translations

It is highly recomended to seed translations initially!

Here is an example how you could do this (on the server):


import { Meteor } from 'meteor/meteor';

import YAML from 'yamljs';
import importIntoCollection from '@panter/manul-i18n/dist/import_into_collection';
import { Translations } from '/lib/collections';

export default () => {
  Meteor.startup(() => {
    const localesToSeed = ['de'];
    const translations = {};
    const translationsForLocale = (locale) => {
      const translation = YAML.parse(Assets.getText(`i18n-seed/${locale}.yaml`));
      translations[locale] = translation;
    };

    localesToSeed.forEach(translationsForLocale);

    importIntoCollection({
      translations,
      collection: Translations,
      locales: localesToSeed,
      override: false,
    });
  });
};