Handling long blocks of text to be translated in i18N

I know that you cannot have multi-line json files.

At the moment I am breaking the text I need to be translated into logical blocks. However, the problem is that they are still very long lines, and it’s a horrible experience trying to edit them in VS Code.

Is there another way of handling large blocks of text in i18N?

This is one of the blocks I’m fighting with at the moment !

  "home": {
    "para1": "The management system for trading in Raw Cashew Nuts",
    "title": "Home",
    "l1":"This is a comprehensive system to manage the purchases of cashews (or other commodities), their arrival at your warehouse and shipping them out to your customers. The system was developed after having practical experience of exporting many thousands of tons of raw cashews from origin.",
    "l2":"The sytem was developed to reduce or completely eliminate:",
    "l3":"Poor stock control in the warehouse - scraps of paper partially recording stock entries etc.",
    "l4":"Arguments with the inspector over which lots have been approved",
    "l5":"Employees wasting time producing the Packing List, not checking the details and sending incorrect documents to the bank",
    "l6":"Preparing draft Bills of Lading in Word, making mistakes, waiting for the customer to approve the draft, and then trying to find the email with the correct draft attached to it to send to the shipping company.",
    "l7":"Because the buyer cannot see the supporting documents until they are emailed to him; usually days after issue, any mistakes are not picked up quickly enough. This can often lead to the customer asking for a change to the documents which might even have to be retrieved from the bank.",
    "l8":"Many other issues arising out of poor management and sloppy work which causes delays to getting the documents to the bank and the customer all delaying the time it takes to receive payment.",
    "l9":" Your buyers can log in to the system and see the shipping documents you have prepared. They can formally approve your draft documents including the Bill of Lading before you ask the shipping company to generate the originals.",
    "l10":"Scanned copies of all the principal shipping documents are stored per Bill of Lading.",
    "l11":"Whoever manages your warehouse, whether it be one of your employees or a third party warehouse manager has independent access to the system. Only the warehouse manager can receive goods into stock or dispatch them to the container terminal.",
    "l12":"The same is true for the quality inspector. Only he is able to record the quality of the cashews.",
    "l13":"This system allows your customer to completely trust you. He knows that if the quality inspector works for him, only he can record the quality. Further he also has the knowledge that if the warehouse manager is the Customer's representative or a third-party collateral manager, that only he can update the stocks.",
    "l14":"The system can also automatically generate a draft Bill of Lading, the Packing List and issue a purchase order to your supplier."
  },
2 Likes

JSON doesn’t support multiline strings. Period. That’s sad but true.
There is a solution to your issue though. Just use yml files instead for translations.
It supports multiline strings like this:

// standard
key: >
  Your long
  string here.

// this preserves \n
key: |
  ### Heading

  * Bullet
  * Points

so just switch out the loader you have with this one https://www.npmjs.com/package/i18next.yaml
npm install i18next.yaml

3 Likes

JSON doesn’t support multiline strings, but https://json5.org/ does. And since json5 is a superset of json, it is perfectly safe to replace the JSON parser in the library with JSON5.

3 Likes

I didn’t know I could use yaml. I guess I will be able to convert my json files to yaml easily enough.
But how do I switch out the loader?

Can I embed html inside the yaml?

1 Like

How would I replace the JSON parser with JSON5? Is there any risk it will break anything?

1 Like

I havent tried it out yet but the mentioned npm package just provides a backend same as the xhr one. So in theory just switch it out. As for html I guess it should. Give it perhaps a testdrive on a minimal demo set

1 Like

I think I’ll try it on my next project - a bit scared to mess around with it now :slight_smile:

1 Like

I’ve used JSON5 with JSPM and RequireJS through Gulp with this

I was using XHR backend and I had an output folder where the processed translation files went.

2 Likes

This is a good setup. The conversion from human-friendly JSON5 to machine-standard JSON can be a build step just like SCSS -> CSS.

2 Likes

I really have no grasp of gulp. I just use the aurelia-cli with requirejs “straight out of the box”. How would I incorporate this into the build?

I also use https://github.com/gilmarsquinelato/i18n-manager which is a very barebones translator which works a treat. Looking at the dox it works with yaml, but no mention of json5.

I think I’ll take a look at @zewa666 suggestion to go for yaml.

1 Like

Aggh - I need some help!

I converted my json files to yml and they’re a million time easier to handle.

I then installed i18next.yaml, but can’t work out how to change the loader to use it.

It doesn’t have a dts at @types/i18next.yaml and I can’t get TS to recognize it.

Could you please give me some guidance on how to set it up?

The error message I’m getting is

logger.js:55 i18next::backendConnector: No backend was added via i18next.use. Will not load resources.

My main.ts is:

import { EventAggregator } from "aurelia-event-aggregator";
import { Aurelia } from "aurelia-framework";
import { TCustomAttribute } from "aurelia-i18n";
import { AppRouter } from "aurelia-router";
import "bootstrap";
import Backend from "i18next-xhr-backend";
import * as i18nextYaml from "i18next.yaml";
import moment from "moment";
import environment from "./environment";
import { initialState } from "./store/initial-state";

export function configure(aurelia: Aurelia) {
  const listeningModeOptions = {
    pointer: false, // listen for pointer event interaction
    touch: false, // listen for touch event interaction
    mouse: true, // listen for mouse event interaction
    focus: false, // listen for foucs event
    windowBlur: false // listen for window blur event (navigating away from window)
  };

  aurelia.use
    .standardConfiguration()
    .plugin("aurelia-dialog")
    .plugin("aurelia-validation")
    .plugin("bcx-aurelia-reorderable-repeat")
    .plugin("aurelia-animator-css")
    .plugin("aurelia-split-pane")
    .plugin(("aurelia-blur-attribute"), listeningModeOptions)
    .plugin("aurelia-api", config => {
      config.registerEndpoint("auth");
    })
    .plugin("aurelia-store", {
      initialState,
      devToolsOptions: { serialize: false }
    })
    .plugin("aurelia-authentication")
    .plugin("aurelia-i18n", i18n => {
      const aliases = ["t", "i18n"];
      TCustomAttribute.configureAliases(aliases);

      //  i18n.i18next.use(Backend);
      i18n.i18next.use(i18nextYaml);

      i18n.i18next.on("languageChanged", lng => {
        moment.locale(lng);
      });

      return i18n.setup({
        backend: {
          // loadPath: "./locales/{{lng}}/{{ns}}.json"
          loadPath: "./locales/{{lng}}/{{ns}}.yml"
        },
        attributes: aliases,
        lng: "en",
        fallbackLng: "fr",
        interpolation: {
          escapeValue: false
        },
        debug: true
      })
        .then(() => {
          const router = aurelia.container.get(AppRouter);
          router.transformTitle = title => i18n.tr(title);

          const ea = aurelia.container.get(EventAggregator);
          ea.subscribe("i18n:locale:changed", () => {
            router.updateTitle();
          });
        });
    })
    .feature("resources");

  aurelia.use.developmentLogging(environment.debug ? "debug" : "warn");

  if (environment.testing) {
    aurelia.use.plugin("aurelia-testing");
  }

  return aurelia.start().then(() => aurelia.setRoot());
}

1 Like

If you use the built-in loader, then the config is something like this:

...
        return instance.setup({
          backend: {                                  // <-- configure backend settings
            loadPath: './locales/{{lng}}/{{ns}}.json', // <-- XHR settings for where to get the files from
            parse: require('json5').parse,
          },
          attributes: aliases,
          lng : 'de',
          fallbackLng : 'en',
          debug : false
        });
...

How do I know? :point_down:

I don’t think so.

2 Likes

Not throwing dirt intentionally at anyone but the package was last updated 7 years ago.

1 Like

Fair statement. It was just a quick search and seems there are more yml loaders available.
EDIT: I’ve tried it now out myself and looking at the code of the yml backend it seems that was way back for v1/2. So definitely out-of-date. Sry @jeremyholt but it seems you’d have to write your own backend with YML.

What I’ve quickly tried, and it seems to work, is just to leverage either the xhr backend or the builtin from the i18n plugin and do the following on configuration:

// npm install yamljs
// npm install --save-dev @types/yamljs

import {parse} from "yamljs";

// in your plugin configuration
return instance.setup({
        backend: {                                  // <-- configure backend settings
          loadPath: './locales/{{lng}}/{{ns}}.yml', // <-- switch to YML,
          parse // <--- switch out the parser for the yml one
        },
        attributes: aliases,
        lng : 'de',
        fallbackLng : 'en',
        debug : true,
      });

So pretty much the same as you suggested with json5 @khuongduybui. Seems to work so far but it’s really just a quick test so take it for a proper testdrive @jeremyholt

1 Like

Yes, this route should work, though I like @kyttike suggestion of adding a gulp task the most. Build time added = runtime saved, yes?

2 Likes

The main reason I used XHR backend is so that translations would not be bundled. That makes the initial bundle smaller and I can load translations on demand. Its nice to see a way to use JSON5 without gulp!

1 Like

Hmm - still having problems …

i18n is clearly loading the translation.yml, but reporting unable to find any keys

This is getting more complicated than I thought :slight_smile:

1 Like

The multiline key is: yourkey: > yourtext you wrote >- instead

1 Like

Could you share the gulp taks you’ve created alongside how you included it in the other existing aurelia tasks? I guess what you did is to create a conversion task, which gets called in sequence with the au build task. But it would be nice to have a working example here so that everybody who’s interested can take a look at it.

Also I fully agree with your reasoning about XHR backend. For the projects I did with i18n I’ve always used it for the exact same reason. I definitely don’t see a lot of value in bundling translations upfront, specifically in environments with large translation files and lots of languages. Nearly every user sticks to his language of choice and the XHR requests get cached, so it’s slow only the first time. But in the bundled case every user would have to load everything upfront (e.g 5 languages in one of my apps) whereas 4 of those would be a total waste of bandwith for him. Moreover, since it’s included in the bundle, every update to a language would cause the user to re-download the whole bundle and invalidate the cache, whereas with XHR only the respective languages file get’s invalidated and re-upped.

1 Like

Changed it to > but it still can’t find the key

1 Like