DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Cover image for How to add I18N Support to your Web Components
The Open Coder
The Open Coder

Posted on

How to add I18N Support to your Web Components

Recently I have been working on recreating a flash card as a web component. Here's a screenshot of the card (Click the image to try it out):
Comp

While this card is really well designed and has a decent UX, it's missing accessibility (a11y) and internationalization (i18n). To fix this we can recreate the card using LitElement, so that the flash card can be used anywhere.

Development Approach

The first decision I made was to use slots for passing in the question and answer. If you don't know what slots are check out my post about using slots.

To achieve this, I created an <answer-box> tag for handling the input and checking of the answer. Here's a snippet from the <answer-box> render function:

render() {
    return html`
      <div>
        <p id="question">
          <slot name="front" id="front"></slot>
          <slot name="back" id="back"></slot>
        </p>
    ...
Enter fullscreen mode Exit fullscreen mode

As you can see all the user has to do is pass in the front and back of the card when creating a <flash-card>:

<flash-card>
  <p slot="front">What is strawberry in Spanish?</p>
  <p slot="back">fresa</p>
</flash-card>
Enter fullscreen mode Exit fullscreen mode

Once the slots were working, I wanted to extend the functionality of the original design to be more accessible. For example, the original card has no audial component for hearing the answer, and it doesn't support multiple languages. When working with JavaScript in the browser we can make good use of two awesome classes for enhancing a11y and i18n.

SpeechSynthesisUtterance

SpeechSynthesisUtterance is an awesome built-in class for text-to-speech in the browser. First, I instantiate the class in the constructor with:

constructor() {
  ...
  this.speech = new SpeechSynthesisUtterance();
  this.speech.lang = navigator.language.substring(0, 2); // uses language of the browser
  ...
}
Enter fullscreen mode Exit fullscreen mode

Here we are creating the class and setting the language of the synthetic voice to be the same as the browser's default language. Next, we need a function to run whenever the user clicks on the sound icon:

speakWords() {
    const side = this.back ? 'front' : 'back';
    const comparison = this.shadowRoot
      .querySelector(`[name="${side}"]`)
      .assignedNodes({ flatten: true })[0]
      .querySelector(`[name="${side}"]`)
      .assignedNodes({ flatten: true })[0].innerText;
    this.speech.text = comparison;
    window.speechSynthesis.speak(this.speech);
  }
Enter fullscreen mode Exit fullscreen mode

The first two lines have to do with checking the data passed into the slot and determining what the correct answer is. Then on line 3 we set this.speech.text to the correct answer that was passed in. Finally, line 4 makes your browser speak the correct answer.

I18NMixin



I18NMixin is a "mixin" written by Bryan Ollendyke for managing internationalization (I18N) in Lit. To instantiate the mixin in my project I wrote:

import { I18NMixin } from '@lrnwebcomponents/i18n-manager/lib/I18NMixin.js';
...

export class AnswerBox extends I18NMixin() {
  ...
  constructor() {
    ...
    this.i18store = window.I18NManagerStore.requestAvailability();
    this.speech.lang = this.i18store.lang;
    ...
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

After importing and extending the project, all I'm doing here is setting the manager to a variable so I can access it later. You might also recognize that I am setting the default voice from before to the language specified by our mixin. Now by simply adding these few lines I can start to translate parts of my web component. For instance, I need the placeholder text in the input and the text in the button to say "Your Answer" and "Check Answer". To do that I add:

constructor() {
  super()
  this.t = {
    yourAnswer: 'Your answer',
    checkAnswer: 'Check answer'
  };
  this.registerLocalization({
    context: this,
    localesPath: new URL('../locales/', import.meta.url).href,
      locales: ['es', 'fr', 'ja'],
  });
  ...
}
Enter fullscreen mode Exit fullscreen mode

Here we are using the this.t variable provided from our mixin to establish the variables we want to be translatable. Then, I specify the path to the files and languages that we are supporting. Here's an example of the ./locales/answer-box.ja.json file:

{
    "yourAnswer": "ใ‚ใชใŸใฎ็ญ”ใˆ",
    "checkAnswer": "็ญ”ใˆใ‚’ใƒใ‚งใƒƒใ‚ฏ"
}
Enter fullscreen mode Exit fullscreen mode

As you can see we simply add a file to our ./locales folder with the format of component-name.2-letter-lang-code.json and directly translate our variables. Lastly, we add our this.t variables to our code:

render() {
  return html`
    ...
    <input
      id="answer"
      type="text"
      .placeholder="${this.t.yourAnswer}"
      @input="${this.inputChanged}"
      .value="${this.userAnswer}"
    />
    ...
    <button
      id="check"
      ?disabled="${this.userAnswer === ''}"
      @click="${this.checkUserAnswer}"
    >
      ${this.t.checkAnswer}
    </button>
    ...
  `
}
Enter fullscreen mode Exit fullscreen mode

So wherever we use this.t above, it will translate the variables if we support the language specified. Obviously, our component only supports 4 so far, so we have a ways to go.

Final Thoughts

Our flash card
You might have noticed that I made a <flash-card> component, but only showed code from <answer-box>. That's because I just wanted to highlight two cool conventions for increasing i18n in web components. If you're interested in how the styling was done and how the whole component was put together check out our repo:

A Project EdTechJoker creation

License: Apache 2.0 Lit #HAXTheWeb

See https://github.com/elmsln/edtechjoker/blob/master/fall-21/projects/p3-haxtheweb/README.md for requirements to complete this project.

Built with open-wc recommendations

Quickstart

To get started:

yarn install
yarn start
# requires node 10 & npm 6 or higher
Enter fullscreen mode Exit fullscreen mode

Navigate to the HAX page to see it in context. Make sure to change rename-me in ALL parts of the repo to your project name.

Scripts

  • start runs your app for development, reloading on file changes
  • start:build runs your app after it has been built using the build command
  • build builds your app and outputs it in your dist directory
  • test runs your test suite with Web Test Runner
  • lint runs the linter for your project
  • format fixes linting and formatting errors

Tooling configs

For most of the tools, the configuration is in the package.json to reduce the amount of files in your project.

If you customize the configuration a lot, you can consider moving them to individual files.

Demo

Link to npmjs

Top comments (0)

Want to Create an Account?
Now it's your turn!
ย 
๐Ÿ—’ Share a tutorial
๐Ÿค” Reflect on your coding journey
โ“ Ask a question

Create an account to join hundreds of thousands of DEV members on their journey.