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

rollup-plugin-precompile-intl

v0.1.0

Published

Compile translations in ICU message format to invocable functions at build time

Downloads

10

Readme

rollup-plugin-precompile-intl

This is a rollup plugin that wires together babel-plugin-precompile-intl and precompile-intl-runtime in your rollup-powered app. It was authored with Svelte.js in mind, but it could be used with any other library.

Why would I want to use it? How does it work?

This approach is different than the taken by libraries like intl-messageformat or format-message, which do all the work in the browser. The approach taken by those libraries is more flexible as you can just load json files with translations in plain text and that's it, but it also means the library needs to ship a parser for the ICU message syntax, and it always has to have ship code for all the features that the ICU syntax supports, even features you might not use, making those libraries several times bigger.

The strategy used by this library assumes that your app will have some sort of built process that you can use to analyze and precompile the messages in your app. This process spares the browser of the burden of doing the same process in the user's devices, resulting in smaller and faster apps.

For instance if an app has the following set of translations:

{
  "plain": "Some text without interpolations",
  "interpolated": "A text where I interpolate {count} times",
  "time": "Now is {now, time}",
  "number": "My favorite number is {n, number}",
  "pluralized": "I have {count, plural,=0 {no cats} =1 {one cat} other {{count} cats}}",
  "pluralized-with-hash": "I have {count, plural, zero {no cats} one {just # cat} other {# cats}}",
  "selected": "{gender, select, male {He is a good boy} female {She is a good girl} other {They are good fellas}}",
}

The babel plugin will analyze and understand the strings in the ICU message syntax and transform them into something like:

import { __interpolate, __number, __plural, __select, __time } from "precompile-intl-runtime";
export default {
  plain: "Some text without interpolations",
  interpolated: `A text where I interpolate ${__interpolate(count)} times`,
  time: now => `Now is ${__time(now)}`,
  number: n => `My favorite number is ${__number(n)}`,
  pluralized: count => `I have ${__plural(count, { 0: "no cats", 1: "one cat", h: `${__interpolate(count)} cats`})}`,
  "pluralized-with-hash": count => `I have ${__plural(count, { z: "no cats", o: `just ${count} cat`, h: `${count} cats`})}`,
  selected: gender => __select(gender, { male: "He is a good boy", female: "She is a good girl", other: "They are good fellas"})
}

Now the translations are either strings or functions that take some arguments and generate strings using some utility helpers. Those utility helpers are very small and use the native Intl API available in all modern browsers and in node. Also, unused helpers are tree-shaken by rollup.

When the above code is minified it will results in an output that compact that often is shorter than the original ICU string:

"pluralized-with-hash": "I have {count, plural, zero {no cats} one {just # cat} other {# cats}}",
--------------------------------------------------------------------------------------------------
"pluralized-with-hash":t=>`I have ${jt(t,{z:"no cats",o:`just ${t} cat`,h:`${t} cats`})}`

The combination of a very small and treeshakeable runtime with moving the parsing into the build step results in an extremely small footprint, often < 2kb.

Setup

  1. Install rollup-plugin-precompile-intl and precompile-intl-runtime as devDependencies.

  2. Create a folder to put your translations. I like to use a /messages folder on the root. On that folder, create en.js, es.js and as many files as languages you want. On each file, export an object with your translations:

     export default {
       "recent.aria": "Find recently viewed tides",
       "menu": "Menu",
       "foot": "{count} {count, plural, =1 {foot} other {feet}}",
     }
  3. In your rollup.config.js add the plugin exported by rollup-plugin-precompile-intl, and let it know what folder your translations live in:

  import svelte from 'rollup-plugin-svelte';
  import resolve from '@rollup/plugin-node-resolve';
  import commonjs from '@rollup/plugin-commonjs';
  import replace from "@rollup/plugin-replace";
  import livereload from 'rollup-plugin-livereload';
  import { terser } from 'rollup-plugin-terser';
+ import precompileIntl from 'rollup-plugin-precompile-intl'

  const production = !process.env.ROLLUP_WATCH;
  export default [
    {
      input: "src/main.js",
      output: {
        sourcemap: true,
        format: "iife",
        name: "app",
        file: "public/build/bundle.js"
      },
      plugins: [
+       precompileIntl({ include: 'messages/*' }),
        svelte({
          // enable run-time checks when not in production
          dev: !production,
          preprocess: [],
          // we'll extract any component CSS out into
          // a separate file — better for performance
          css: css => {
            css.write("public/build/bundle.css");
          }
        })
      ]
    }
  ]

From this step onward the library almost identical to use and configure to the popular svelte-i18n. It has the same features and only some import path is different. You can check the docs of svelte-i18n for examples and details in the configuration options.

  1. Create an initializer where you can register your locales and configure your preferences. You can import your languages statically (which will add them to your bundle) or register loaders that will load the translations asynchronously. I like to use /src/i18n.js:
import { addMessages, init /*, register */ } from 'precompile-intl-runtime'
import en from "../messages/en.js";

addMessages("en", en);
// register('es', () => import('../messages/en.json')); // you can define other locales that will be imported lazily

init({
  fallbackLocale: "en",
  initialLocale: { navigator: true }
});
  1. Import this initializer from your /src/main.js
  2. Now on your .svelte files you start translating using the t store exported from precompile-intl-runtime:
<script>
  import { t } from 'precompile-intl-runtime'
</script>
<footer class="l-footer">
  <p class="t-footer">{$t("hightide")} {$t("footer.cta")}</p>
</footer>

Testing

If you want translations to work in node (for instance when running jest unit tests) you will need to install babel and configure it on the babel.config.js

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current"
        }
      }
    ]
  ]
};

If a test uses translations you will have to import the src/i18n.js initializer on the test or setup your locale and load the messages yourself.