Skip to content

Commit

Permalink
feat: Introduced @lingui/snowpack-plugin (#947)
Browse files Browse the repository at this point in the history
Co-authored-by: Aman Kubanychbek <amankubanychbek@gmail.com>
  • Loading branch information
semoal and amankkg authored Jan 19, 2021
1 parent f7b59ab commit 96bd31b
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Documentation contents
Configuration <ref/conf>
Locale Detection <ref/locale-detector>
Webpack Loader <ref/loader>
Snowpack Plugin <ref/snowpack-plugin>
Catalog Formats <ref/catalog-formats>
ICU MessageFormat <ref/message-format>

Expand Down
46 changes: 46 additions & 0 deletions docs/ref/snowpack-plugin.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
***********************************************
API Reference - Snowpack Plugin (@lingui/snowpack-plugin)
***********************************************

It's a good practice to use compiled message catalogs during development. However,
running :cli:`compile` everytime messages are changed soon becomes tedious.

``@lingui/snowpack-plugin`` is a snowpack loader, which compiles messages on the fly:

Installation
============

Install ``@lingui/snowpack-plugin`` as a development dependency:

.. code-block:: sh
npm install --save-dev @lingui/snowpack-plugin
# Or using yarn
# yarn add --dev @lingui/snowpack-plugin
Usage
=====

Simply add ``@lingui/snowpack-plugin`` inside your ``snowpack.config.js``:

.. code-block:: js
module.exports = {
plugins: [
'@lingui/snowpack-plugin',
],
}
Then in your code all you need is to use dynamic() import. Extension is mandatory. In case of using po format, use ``.po``.

.. code-block:: jsx
export async function dynamicActivate(locale: string) {
const { messages } = await import(`./locales/${locale}/messages.po`)
i18n.load(locale, messages)
i18n.activate(locale)
}
See the `guide about dynamic loading catalogs <../guides/dynamic-loading-catalogs.html>`_
for more info.
4 changes: 2 additions & 2 deletions packages/cli/src/api/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,8 +522,8 @@ export function getCatalogForFile(file: string, catalogs: Array<Catalog>) {
const catalogFile = `${catalog.path}${catalog.format.catalogExtension}`
const catalogGlob = catalogFile.replace(LOCALE, "*")
const match = micromatch.capture(
normalizeRelativePath(catalogGlob),
normalizeRelativePath(file)
normalizeRelativePath(path.relative(catalog.config.rootDir, catalogGlob)),
normalizeRelativePath(file),
)
if (match) {
return {
Expand Down
42 changes: 42 additions & 0 deletions packages/snowpack-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[![License][badge-license]][license]
[![Version][badge-version]][package]
[![Downloads][badge-downloads]][package]

# @lingui/snowpack-plugin

> Snowpack plugin which compiles on the fly the .po files for auto-refreshing. In summary, `lingui compile` command isn't required when using this plugin
`@lingui/snowpack-plugin` is part of [LinguiJS][linguijs]. See the [documentation][documentation] for all information, tutorials and examples.

## Installation

```sh
npm install --save-dev @lingui/snowpack-plugin
# yarn add --dev @lingui/snowpack-plugin
```

## Usage

### Via `snowpack.config.js` (Recommended)

**snowpack.config.js**

```js
module.exports = {
plugins: [
'@lingui/snowpack-plugin',
],
}
```

## License

[MIT][license]

[license]: https://github.com/lingui/js-lingui/blob/main/LICENSE
[linguijs]: https://github.com/lingui/js-lingui
[documentation]: https://lingui.js.org/
[package]: https://www.npmjs.com/package/@lingui/snowpack-plugin
[badge-downloads]: https://img.shields.io/npm/dw/@lingui/snowpack-plugin.svg
[badge-version]: https://img.shields.io/npm/v/@lingui/snowpack-plugin.svg
[badge-license]: https://img.shields.io/npm/l/@lingui/snowpack-plugin.svg
1 change: 1 addition & 0 deletions packages/snowpack-plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./src"
33 changes: 33 additions & 0 deletions packages/snowpack-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@lingui/snowpack-plugin",
"version": "3.4.0",
"description": "Snowpack plugin for Lingui message catalogs",
"main": "index.js",
"license": "MIT",
"keywords": [
"snowpack-plugin",
"i18n",
"snowpack",
"linguijs",
"internationalization",
"i10n",
"localization",
"i9n",
"translation"
],
"repository": {
"type": "git",
"url": "https://github.com/lingui/js-lingui.git"
},
"bugs": {
"url": "https://github.com/lingui/js-lingui/issues"
},
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@babel/runtime": "^7",
"@lingui/cli": "^3.4.0",
"@lingui/conf": "^3.4.0"
}
}
65 changes: 65 additions & 0 deletions packages/snowpack-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import path from "path"
import { getConfig } from "@lingui/conf"
import { createCompiledCatalog, getCatalogs, getCatalogForFile } from "@lingui/cli/api"

type LinguiConfigOpts = {
cwd?: string;
configPath?: string;
skipValidation?: boolean;
}
type SnowpackLoadOpts = {
filePath: string
}
function extractLinguiMessages(snowpackConfig?, linguiConfig: LinguiConfigOpts = {}) {
const strict = process.env.NODE_ENV !== 'production'
const config = getConfig(linguiConfig)

return {
name: '@lingui/snowpack-plugin',
resolve: {
input: ['.po', '.json'],
output: ['.js'],
},
async load({ filePath }: SnowpackLoadOpts) {
const catalogRelativePath = path.relative(config.rootDir, filePath)
const EMPTY_EXT = /\.[0-9a-z]+$/.test(filePath)
const JS_EXT = /\.js+$/.test(filePath)

if (!EMPTY_EXT || JS_EXT) {
const formats = {
minimal: ".json",
po: ".po",
lingui: ".json"
}
throw new Error(`@lingui/snowpack-plugin: File extension is mandatory, for ex: import('./locales/en/messages${formats[config.format]}')`)
}

const fileCatalog = getCatalogForFile(
catalogRelativePath,
getCatalogs(config)
)

const { locale, catalog } = fileCatalog
const catalogs = catalog.readAll()

const messages = Object.keys(catalogs[locale]).reduce((acc, key) => {
acc[key] = catalog.getTranslation(catalogs, locale, key, {
fallbackLocales: config.fallbackLocales,
sourceLocale: config.sourceLocale,
})

return acc
}, {})

const compiled = createCompiledCatalog(locale, messages, {
strict,
namespace: config.compileNamespace,
pseudoLocale: config.pseudoLocale,
})

return compiled
}
}
}

export default extractLinguiMessages
7 changes: 7 additions & 0 deletions packages/snowpack-plugin/test/.linguirc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"locales": ["en", "cs"],
"catalogs": [{
"path": "<rootDir>/locale/{locale}/messages"
}],
"format": "po"
}
21 changes: 21 additions & 0 deletions packages/snowpack-plugin/test/__snapshots__/index.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`snowpack-plugin should return compiled catalog 1`] = `/*eslint-disable*/module.exports={messages:{"Hello World":"Hello World","My name is {name}":["My name is ",["name"]]}};`;

exports[`snowpack-plugin should return error if import doesn't contain extension 1`] = `@lingui/snowpack-plugin: File extension is mandatory, for ex: import('./locales/en/messages.po')`;

exports[`snowpack-plugin to match snapshot 1`] = `
Object {
load: [Function],
name: @lingui/snowpack-plugin,
resolve: Object {
input: Array [
.po,
.json,
],
output: Array [
.js,
],
},
}
`;
25 changes: 25 additions & 0 deletions packages/snowpack-plugin/test/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import path from "path"
import snowpackPlugin from "../src"

describe("snowpack-plugin", () => {
it("to match snapshot", () => {
const p = snowpackPlugin();
expect(p).toMatchSnapshot();
})

it("should return error if import doesn't contain extension", async () => {
const p = snowpackPlugin()
expect(async () => p.load({ filePath: "./fixtures/locale/en/messages" })).rejects.toThrowErrorMatchingSnapshot()
})

it("should return compiled catalog", async() => {
const p = snowpackPlugin(null, {
configPath: path.resolve(
__dirname,
".linguirc",
),
})
const result = await p.load({ filePath: path.join(__dirname, "locale", "en", "messages.po") })
expect(result).toMatchSnapshot()
})
})
5 changes: 5 additions & 0 deletions packages/snowpack-plugin/test/locale/cs/messages.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
msgid "Hello World"
msgstr "Hello World"

msgid "My name is {name}"
msgstr "My name is {name}"
5 changes: 5 additions & 0 deletions packages/snowpack-plugin/test/locale/en/messages.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
msgid "Hello World"
msgstr "Hello World"

msgid "My name is {name}"
msgstr "My name is {name}"
4 changes: 4 additions & 0 deletions scripts/build/bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ const bundles = [
type: bundleTypes.NODE,
entry: "@lingui/babel-plugin-extract-messages"
},
{
type: bundleTypes.NODE,
entry: "@lingui/snowpack-plugin"
},

{
type: bundleTypes.NODE,
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,13 @@
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"

"@babel/runtime@^7":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
version "7.11.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
Expand Down

1 comment on commit 96bd31b

@vercel
Copy link

@vercel vercel bot commented on 96bd31b Jan 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.