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

nodoku-i18n

v0.3.3

Published

<!-- TOC --> * [Getting started](#getting-started) * [Installing Nodoku localization](#installing-nodoku-localization) * [Integrating localization in the project](#integrating-localization-in-the-project) * [The localization language for Nodoku

Downloads

423

Readme

GitHub link: https://github.com/nodoku/nodoku-i18n

nodoku-i18n is a localization library for Nodoku static site generator.

One of the parameter Nodoku RenderingPage component receives is the localization function - imageUrlProvider: ImageUrlProvider | undefined.

This parameter is optional. If not provided, the text from the content MD file is used.

The localization function has the following signature:

type I18nextProvider = (lng: string) => Promise<{t: (text: NdTranslatableText) => string}>;

For a given language, I18nextProvider function is supposed to return a structure containing the t() function, which later will be used for the page localization.

(text: NdTranslatableText) => string

The Nodoku components are localized out of the box, so if the I18nextProvider defined in the RenderingPage call, its t() function will be used throughout all the textual content

Note that even the image url's can be localized. By default, the translation value for the image url's are supplied wrapped in curly braces

{../images/my-image.png}

Curly braces usually mean that the value shouldn't be translated by the automatic translation, if a localization backend, such as Simplelocalize or Locize, is used.

See below for more details.

Getting started

Installing Nodoku localization

The installation of the nodoku-i18n library is straightforward:

npm install nodoku-core nodoku-i18n

Integrating localization in the project

Nodoku localization integration consists of two phases:

  • initializing the localization store

    import {NodokuI18n} from "nodoku-i18n";
    ...
    await NodokuI18n.Simplelocalize.initI18nStore( ["nodoku-landing", "docs", "faq"], 'en')

    The parameters to the translation store initialization are specific to the backend which is chosen for translation. See below for details about this example.

  • calling the RenderingPage Nodoku component with the localization function NodokuI18n.Simplelocalize.i18nForNodoku

    return (
        <RenderingPage
            lng={lng}
            renderingPriority={RenderingPriority.skin_first}
            skin={skin}
            content={content}
            i18nextProvider={NodokuI18n.Simplelocalize.i18nForNodoku}
            imageUrlProvider={imageUrlProvider}
            componentResolver={nodokuComponentResolver}
        />
    );
    

See below for details about the translation backend Simplelocalize, which is used in this example

The localization language for Nodoku rendering

The Nodoku RenderingPage component receives as a mandatory parameter the language, in which the page should be displayed.

lng - the attribute telling Nodoku in which language the page should be displayed.

Don't confuse it with the language the content is provided in MD file.

The content language is provided during parsing, as a parameter to the parser (see Parsing and translation key generation for details on parsing).

Whereas the lng parameter is given to the RenderingPage to display the content in the desired language.

Nodoku will automatically use the provided backend to get the translations, so that the visual content is rendered in the desired language.

Important: The localization discussed here is the Server Side localization, i.e. the localization that would be happening during Server Side Rendering of NextJS. Hence, the lng parameter should be available at the time of building.

One of the possible solutions is to use the NextJS static page parametrization, as follows:

src
    app
        [lng]
            page.tsx

And the page is accessed using the language parameter in the URL

https://www.my-domain.com/en/

If this approach is used, one should provide the static params provider - generateStaticParams, as follows (or in a similar way):

export async function generateStaticParams(): Promise<{ params: { lng: string } }[]> {
  return (await NodokuI18n.Simplelocalize.allLanguages()).map(l => {
    return {params: {lng: l.key}};
  });
}

Nodoku localization foundation

Parsing and translation key generation

A textual information unit in Nodoku is a structure represented by the class NdTranslatableText

export class NdTranslatableText {
    key: string = "";
    ns: string = "";
    text: string = "";
}

This structure is intended to contain a single piece of translatable textual content.

And the attributes has the following meaning:

  • key: the translation key of the piece of text
  • ns: the translation namespace of the piece of text
  • text: the actual textual content, as it has been extracted from the MD file during parsing.

This structure is created during the MD file parsing, where each title, paragraph and image are extracted and stored in such a way.

Upon MD file parsing we extract the textual content, and it is stored in the text field.

The namespace (the ns attribute) is provided as a parameter to the parsing function.

And finally, the translation key (the key attribute) is generated automatically, using the content block id as a prefix.

Consider the following example:


```yaml
nd-block:
  attributes:
    sectionName: nodoku-way
``

# Step 1: _Think_
## Create content promoting your product or service as an **MD file**

We are parsing it the following call to parser:

const content: NdContentBlock[] = await contentMarkdownProvider("http://localhost:3001/site/nodoku-landing.md", "en", "nodoku-landing")

As you can see, we are directly specifying the language in which the content is written - "en", as well as the namespace - "nodoku-landing", to which the content will be assigned.

This would end up in the following data structure:

[
  {
    "key": "sectionName=nodoku-way-block-0.title",
    "ns": "nodoku-landing",
    "text": "Step 1: <em>Think</em>"
  },
  {
    "key": "sectionName=nodoku-way-block-0.subTitle",
    "ns": "nodoku-landing",
    "text": "Create content promoting your product or service as an <strong>MD file</strong>"
  }

]

The translation keys are automatically generated using the meta attributes of the content block, the sequential index of the content block in the stream and the name of the actual content block attribute this translation is assigned to ("title", "subTitle").

The content language

One of the parameters given to the content provider is the language, in which the content is written.

The Nodoku localization mechanism doesn't translate the content, if the requested language corresponds to the content language.

Indeed, if the content is already supplied in the required language, no translation is needed.

This principle is also important since otherwise the same content would have been presented twice - in the content MD file, and in the translation backend.

To avoid this "brain split" the content language should be specified, and it is taken into account in the t() function to bypass the translation, if the page language corresponds to the content language.

Dealing with mutating translation keys

The Nodoku localization, as any other localization, is heavily dependent on the translation keys, to which a piece of textual content is assigned.

And the translation keys in turn are relying on the content blockId, since this is the prefix of each translation key for the given content block.

Such approach might pose a problem, when the content blockId changes.

If it does, all the associated translation keys will be changed as well, and the backend wouldn't be able to provide it.

Consider the following example:


```yaml
nd-block:
  attributes:
    sectionName: nodoku-way
``

# Step 1: Think

# Step 2: Skin

As has shown above, this would generate the following translation keys:

  • sectionName=nodoku-way-block-0.title
    • corresponds to the text: Step 1: Think
  • sectionName=nodoku-way-block-1.title
    • corresponds to the text: Step 2: Skin

if for some reason the sections are exchanged in their order in the markdown file, as follows:


```yaml
nd-block:
  attributes:
    sectionName: nodoku-way
``

# Step 2: Skin

# Step 1: Think

the correspondence between the translation keys and the text would be changed:

  • sectionName=nodoku-way-block-0.title
    • corresponds to the text: Step 2: Skin
  • sectionName=nodoku-way-block-1.title
    • corresponds to the text: Step 1: Think

This happens because the automatic generation of the translation keys is based on the sequential order of the content blocks in the MD file.

If the backend already contained the translations for these keys, these translations would not be correct anymore, since the underlying text had changed.

Below we'll see the strategies to handle this kind of problem.

However, one should be aware of the consequences of changing the order of textual blocks.

i18next

The nodoku-i18n is based on the well-known and highly popular library i18next

This library allows loading and storing translation resources, and it manages both the current language and the fallback language.

The Nodoku localization - nodoku-i18n - takes care of creating the required instances of i18next, one per localization languages.

Note, that during the NextJS build process all the pages in different languages are built at the same time, in parallel.

Therefore, it is very important to maintain a separate instance of i18n for each page language being built.

Simplelocalize backend

Simplelocalize is a company providing a cloud based solution to the localization.

It has a backend web interface, where the localization can be managed.

nodoku-i18n provides an adapter for this backend

Creating a project in Simplelocalize

Prior to use the Simplelocalize backend, one needs to create an account, and a localization project.

Once this is done, the .env.local should be modified to contain the project and API keys of the localization project, as follows:

SIMPLELOCALIZE_API_KEY=

SIMPLELOCALIZE_PROJECT_TOKEN=

The Simplelocalize adapter, provided in nodoku-i18n, is relying on these environment variables to connect to the backend.

The keys are available in the account on Simplelocalize as follows:

  1. login to Simplelocalize
  2. in the Dashboard click on the project
  3. Go to Settings -> Credentials
  4. Copy the API key and the project token to the .env.local, as has been shown above

Initializing the backend

Prior to using the Simplelocalize Nodoku backend, one needs to initialize it as follows:

await NodokuI18n.Simplelocalize.initI18nStore( ["<list of namespaces>"], "<the fallback language>")

for example:

await NodokuI18n.Simplelocalize.initI18nStore( ["nodoku-landing", "faq", "docs"], "en")

Several things are important here:

Blocking initialization

Note the keyword await in front of the call to the translation backend initialization.

The t() used for content translation in the Nodoku components is a synchronous call.

In other words, all the translation resources, that might be needed for the execution of this funciton - translation keys and namespaces for the given language - should be preloaded, and readily available at the moment of rendering.

Therefore, the backend initialization is naturally asynchronous, as it includes preloading of the translation resources.

The call should be blocking, since we cannot start rendering prior to the resources being fully loaded.

The namespaces

The list of namespaces should be exhaustive.

For the same reason, we need to know in advance all the namespaces for which the translation resources should be loaded.

The fallback language

The third parameter this call accepts is the fallback language.

The fallback language in case of Nodoku indicates the language, which should not be translated.

This language should correspond to the language of the MD file content.

Recall, that this language is given as a parameter to the parser to extract the content blocks.

Here is an example:

const content: NdContentBlock[] = await contentMarkdownProvider("https://my-project-backend.com/site/nodoku-landing.md", "en", "nodoku-landing")

await NodokuI18n.Simplelocalize.initI18nStore( ["nodoku-landing"], "en")

Note that the content language, given to the content provider - "en" - precisely corresponds to the fallback language, with which the Nodoku localization backend is initialized.

As has been mentioned above, Nodoku doesn't translate the text content if it is already supplied in the required language (in the source MD file).

As we'll see later this parameter is required and important, but for now it is important to note that

the translation backend fallback language should correspond to the content language

Specifying the i18nextProvider

The attribute i18nextProvider is an entry point for the RenderingPage Nodoku component, as far as localization is concerned:

<RenderingPage
        lng={lng}
        renderingPriority={RenderingPriority.skin_first}
        skin={skin}
        content={content}
        i18nextProvider={NodokuI18n.Simplelocalize.i18nForNodoku}
        imageUrlProvider={imageUrlProvider}
        componentResolver={nodokuComponentResolver}
/>

The Nodoku Simplelocalize backend provides this function readily available to be used in the RenderingPage component.

Interaction with the cloud storage

Once the Nodoku translation backend is initialized and rendering is started, the Nodoku adapter will start initeracting with the cloud backend.

Initial translation upload

At the first run, when no resources are yet available, the content is being uploaded to the cloud storage for each translation key encountered during rendering.

This is happening thanks to the missing keys handler, supplied as a parameter during i18next initialization.

This is done automatically, and the user should not be worrying about that.

Thanks to the fact that the fallback language has been provided during Nodoku translation backend initialization, the text for the translation keys is uploaded on the correct language.

This is why it is important to specify the fallback language to be equivalent to the content language.

Managing translations

Once the initial translation in the default language has been uploaded, the user can start providing the translation.

They can use either manual or automatic translations, using the web interface of the backend.

In the dev environment, the API is used to obtain the translation resources.

Whereas on production normally the CDN resources are used, and translation publishing is required.

Managing the content change

It might so happen, due to the modification of the MD file, that the text initially uploaded to the translation cloud backend, is changed.

Recall, that this problem has been discussed in Dealing with mutating translation keys

This change should be reflected, and Nodoku backend provides several strategies to deal with that.

The strategy is specified as a parameter to the Nodoku initialization function.

Three strategies are currently available:

Updating the fallback language text

key: OnFallbackLngTextUpdateStrategy.update_fallback_lng_only

This strategy uploads the new text to the given translation key on the fallback language.

Further manual review process might be required to ensure, the translation still correspond to the modified text.

Deleting all the translations

key: OnFallbackLngTextUpdateStrategy.delete_translations

This strategy, in addition to the text update on the fallback language, would delete any existing translations, that might be available on other languages.

This way the user is forced to provide again the new translations for modified text.

Deleting all the translations

key: OnFallbackLngTextUpdateStrategy.reset_reviewed_status

This strategy, in addition to the text update on the fallback language, would leave the existing translations intact.

Rather, it would reset the "Reviewed" flag on Simplelocalize backend.

The user can then use the filtering mechanism to search for translations that need to be verified.