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

@gaia-law/i18n-modules

v1.1.0

Published

Modular approach for i18n in Webpack, similar to CSS Modules

Downloads

32

Readme

@gaia-law/i18n-modules

CSS Modules for i18n. A webpack plugin that brings support for a modular approach to dictionaries. Especially useful for tools such as Phraseapp. Pairs well with react-polyglot.

Usage:

Setup

In your webpack config:

const I18nModules = require('@gaia-law/i18n-modules');
//...
plugins: [
  new I18nModules(),
],

Create an options RC file in your project's root named .i18n-modules-rc.js with this example content:

module.exports = {
  // Optional, your keys will be relative to this folder, usually `./app` or `./client`.
  // Path must be either absolute or relative to the process.cwd() or `context` option passed to webpack.
  keysRoot: './',
  // Optional, how you will be naming your translation modules,
  // similar to name.module.css, or in this case name.translations.json
  moduleEnding: '.translations.json',

  // Usually the same as the location in phraseapp.yml - the path to your dictionary folder or files
  // where [locale_code] is to be replaced with the language name.
  // Path must be either absolute or relative to the process.cwd() or `context` option passed to webpack.
  // Sync binary also uses this option to locate dictionaries.
  dictionaryPattern: './dictionaries/[locale_code].json',

  // Optional, signals that the original dictionary is to be updated at the end of modules bundling, defaults to `true`.
  // Set this to `false` when you don't need to update the original dictionary
  // and only wish to use bundled translations, for example for SSR bundle build.
  emitFile: true,

  // Optional, a function to customize how unique module IDs are generated
  // Arguments are:
  //  keysRoot: string, same as above but rebased to be absolute
  //  moduleEnding: string, same as above
  //  filePath: string, an absolute path to the translations module file
  // Must return a stable unique string
  getId: (keysRoot, moduleEnding, filePath) => idString,
  
  // Optional, an object to customize the format dictionarys are kept in
  // Defaults to JSON representation
  dictionaryAdapter: {
    parse: (buffer) => someObj,
    stringify: (obj) => someStr,
  }
};

If you don't expect to use the CLI (see below), then instead of creating an RC file you can simply pass an options object as an argument to the plugin. Passed options will be merged with those in the RC file if present and the defaults. This way you can also override RC options by passing extra options directly.

Loader alone

In case you don't need to bundle or update original dictionaries and only need the part that remaps module paths, you can use the module loader directly. In webpack config use it as any other loader:

const { loader: i18nLoader } = require('@gaia-law/i18n-modules');
//...
module: {
  rules: [
    {
      test: /\.translations\.json/,
      use: [{
        loader: i18nLoader,
        options: {
          // keysRoot, moduleEnding, getId: same as for the plugin
        },
      }],
    },
  ]
}

Working with modules

Create a translations module myfile.translations.json:

{
  "de-DE": {
    "content": {
      "totally": {
        "necessary": {
          "nesting": "Majestätischer Inhalt"
        }
      }
    },
    "title": "Unglaublicher Titel"
  },
  "en-US": {
    "content": {
      "totally": {
        "necessary": {
          "nesting": "Majestic content"
        }
      }
    },
    "title": "Amazing title"
  }
}

You need to pay attention to the language names in your modules, the plugin only validates that they match a certain pattern, but not the values.

Import translations:

import lang from './myfile.translations';

// Instead of string paths use object lookup
t(lang.title);

// Nesting is fully supported
t(lang.content.totally.necessary.nesting);

When importing your translation modules all of the values will be remapped to lookup strings for your main dictionary.

Referencing bundled modules

Load your main dictionary like usual:

import dictionary from '../dictionaries/de-DE';
// Pass the dictionary to your translations library, e.g. i18n-polyglot.

The dictionary will contain it's original content and the generated modules.

Options:

  • keysRoot (optional) - the keys will be generated relative to this folder. Use this to make generated keys shorter by excluding static parts of the path, e.g. if all your translations are in ./frontend/app/components pass that as the keysRoot. Defaults to './'.
  • moduleEnding (optional) - the ending that will be used for the translation modules. You can't use : symbol in file names for these files. It's an OS limitation for MacOS and Windows anyway, but *nix users should be aware. Defaults to '.translations.json'
  • dictionaryPattern (required) - the path to your dictionary folder or files. Use [locale_code] placeholder to get current language name injected into the file name. Try not to name your dictionaries in such a way where part of the name might be confused for a language name. For example this might break the build ./dicts/gr-EAT.[locale_code].json
  • emitFile (optional) - determines if the bundled dictionary needs to also be stored on the disk. Set to false when you prefer to use @gaia-law/i18n-modules binary to update original dictionaries or don't intend to store module build results anywhere. Defaults to true.

CLI:

This package comes with a CLI because you might need a way to sync updates from Phraseapp or translations hosting service back into your modules and a simple way to build all languages from modules at once. To use this CLI you must create an RC configuration file.

  • npx @gaia-law/i18n-modules build will build all dictionaries from modules
  • npx @gaia-law/i18n-modules update will update all modules from dictionaries. This will not create any new module files from dictionary as this is likely a result of a typo.
  • npx @gaia-law/i18n-modules clean will remove all generated modules from dictionaries

env:

We support I18N_MODULES_CONTEXT env variable to set current working directory to something else. It works similarly to webpack's context option. When set both plugin and @gaia-law/i18n-modules binary will look for the RC file relative to that context folder. In addition when running the @gaia-law/i18n-modules binary all relative file paths in the RC file will be relative to that context. I18N_MODULES_CONTEXT itself accepts paths relative to current working directory, e.g. one could set I18N_MODULES_CONTEXT=./frontend/app and it would work.

An example with Phraseapp CLI:

  • npx @gaia-law/i18n-modules build && phraseapp push will push all generated modules to phraseapp
  • phraseapp pull && npx @gaia-law/i18n-modules update will update all modules with changes from phraseapp

i18n-modules is also added to your npm env. You could add this to your package.json:

"scripts": {
  "lang:push": "i18n-modules build && phraseapp push",
  "lang:pull": "phraseapp pull && i18n-modules update",
  "lang:clean": "i18n-modules clean",
  "lang:sync": "npm run lang:push && npm run lang:pull",
}

NextJS users

NextJS builds in dev mode incrementally. That means with default config you will always have merge conflicts in the dictionaries folder. To avoid this only emit full dictionaries during full build.

This is an example implementation of next.config.js that achieves this:

const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');
const I18nModules = require('@gaia-law/i18n-modules');


const getConfig = (phase) => {
  const nextConfig = {
    webpack: (config) => {
      config.plugins.push(
        new I18nModules({ emitFile: phase !== PHASE_DEVELOPMENT_SERVER }),
      );
      return config;
    },
  };

  return nextConfig;
};

module.exports = getConfig;

Debugging

This module uses debug internally with label i18n_modules. To debug use DEBUG=i18n_modules in your env: DEBUG=i18n_modules npx webpack.