Software localization

How to Localize SolidJS Applications with i18next

Master the art of tailoring SolidJS applications for international users by leveraging the powerful i18next library.
Software localization blog category featured image | Phrase

SolidJS is a declarative JavaScript library for building user interfaces. It’s fast, since it doesn’t need a virtual DOM, and it only updates when needed. It also benefits from a relatively simple API, making it potentially faster to develop with than other libraries.

When it comes to JavaScript localization with SolidJS, we can use the solid-i18next library on top of the popular i18next library to get the job done effectively. This guide will walk you through localizing your SolidJS apps with solid-i18next.

We will quickly build a weather app and localize it: translating simple messages, adding a language switcher, formatting dates, and more. Let’s start already!

Translation memory visual | Phrase

The right translation software for your needs

Drive global growth by automating, managing, and translating all your content with the world’s most powerful, connective, and customizable translation software.

Explore Phrase Suite

Our demo app: SolidJS Weather

Throughout this tutorial, we will make a weather app named “SolidJS Weather”.

Our app will show you a variety of information related to the weather | Phrase

Our weather app in action showing the weather data in different languages | Phrase

To build this weather app, we will need the following packages.

Library Version used Description
SolidJS 1.7.6 Our main UI library.
i18next 22.5.1 Used to localize our app.
solid-i18next 1.2.2 Used to localize our app.
i18next-http-backend 2.2.1 Used to load translation files asynchronously.
i18next-browser-languagedetector 7.1.0 Used to automatically detect the user’s browser language.
tailwindcss 3.3.2 For styling; optional for our purposes.

Let’s use the SolidJS Vite template to kickstart a new project. From the command line:

npx degit solidjs/templates/js weather-appCode language: Bash (bash)

Let’s quickly go over the components and starter code we need to have to get a working weather app. Our root App.js looks like this:

// /src/app.js

import WeatherForm from "./components/WeatherForm";

function App() {
  return (
   {/* CSS classes removed for brevity. */}
    <div>
      <h1>SolidJS Weather App</h1>
      <WeatherForm />
    </div>
  );
}

export default App;Code language: JavaScript (javascript)

The WeatherForm component contains the following code:

// /src/components/WeatherForm.jsx

import { createSignal } from "solid-js";

const mockWeatherData = [
  {
    city: "San Francisco",
    country: "USA",
    temperatureCelcius: 18,
    temperatureFahrenheit: 64.4,
    feelsLike: 15,
    humidity: 0.7,
    date: "10/04/2023",
    weatherStations: 11,
  },
  {
    city: "Paris",
    country: "France",
    temperatureCelcius: 5,
    temperatureFahrenheit: 41,
    feelsLike: 3,
    humidity: 0.8,
    date: "10/04/2023",
    weatherStations: 10,
  },
  // ...
];

const WeatherForm = () => {
  const [city, setCity] = createSignal("");
  const [weatherData, setWeatherData] = createSignal(null);

  const handleSubmit = (event) => {
    event.preventDefault();

    const cityWeather = mockWeatherData.find(
      (weather) => weather.city.toLowerCase() === city().toLowerCase()
    );

    if (cityWeather) {
      setWeatherData(cityWeather);
    } else {
      setWeatherData({
        error: `Could not find weather for ${city()}`,
      });
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Enter a city"
          value={city()}
          onInput={(event) => setCity(event.target.value)}
         />

        <button type="submit">
          Get Weather
        </button>
      </form>

      {weatherData() && !weatherData().error && (
        <div>
          <h2>
            📍 {weatherData().city}, {weatherData().country}
          </h2>
          <div>
            <p>
              Temperature is: {weatherData().temperatureCelcius}°C
              <br />
              Or: {weatherData().temperatureFahrenheit}°F
            </p>
          </div>
          <div>
            <p>
              Feels like: {weatherData().feelsLike}°C
            </p>
          </div>
          <div>
            <p>Humidity:</p>
            <p>{weatherData().humidity}%</p>
          </div>
          <div>
            <p>Data recorded on:</p>
            <p>{weatherData().date}</p>
          </div>
          <div>
            <p>Number of Weather Stations:</p>
            <p>
              {weatherData().weatherStations}
            </p>
          </div>
        </div>
      )}
      {weatherData() && weatherData().error && (
        <div>
          <p>{weatherData().error}</p>
        </div>
      )}
    </div>
  );
};

export default WeatherForm;Code language: JavaScript (javascript)

Our weather component in the demo app | Phrase

We’re mocking and hard-coding our weather data in the component, which uses the text <input> value to filter by city. If a city is found then all its weather is displayed, otherwise we show an error message.

🗒️ Note » You can find the starter project with all the app code before localization on GitHub.

The solid-i18next library

To add i18n functionality to our weather app, we will use the solid-i18next library. solid-i18next provides utilities and components for integrating i18n functionality into SolidJS applications using the very popular i18next library. It’s straightforward to integrate into your app, and supports various localization features like changing the language, interpolation, pluralization, and more.

📣 Shout-out » Martynas Barzda created and continues to maintain solid-i18next.

How do I set up solid-i18next?

We will start by installing the required dependencies from the command line:

npm install @mbarzda/solid-i18next i18next --saveCode language: CSS (css)

Next, we need to wrap our app’s root component with solid-i18next’s <TransProvider />:

// src/index.js

// ...

import App from "./App";
import { TransProvider } from "@mbarzda/solid-i18next";

// ...

render(
  () => (
    <TransProvider lng="en-US">
      <App />
    </TransProvider>
  ),
  root
);Code language: JavaScript (javascript)

We incorporate the <TransProvider> component from solid-i18next which initializes i18next under the hood by executing i18next.init(). It creates a translation context in our SolidJS app and is responsible for providing the i18n functionality throughout our app components. We can also provide the lng prop to specify the default locale to use in the app.

How do I setup and configure i18next?

We will support 4 languages in this tutorial: English (default), Spanish, Russian and Arabic.

Let’s initialize i18next. Inside App.jsx, we create a new effect call to initialize i18next:

// /src/App.jsx

import { createEffect } from "solid-js";
// ...

createEffect(() => {
    i18next
      .init({
				// Default language.
        lng: "en-US",
				// Logging i18next information to the console.
        debug: true,
				// Interpolation option to escape passed in
        // values to avoid XSS injection.
        interpolation: {
          escapeValue: true,
        },
				// Language to use if translations in the
        // active language are not available.
        fallbackLng: false,
				// Resources i18next will use to retrieve
        // the appropriate translations based on
        // the active locale.
				resources: {
          "en-US": {
            translation: {
              app_name: "SolidJS Weather App",
              get_weather_button_label: "Get Weather",
              city_input_placeholder: "Enter a city",
              // ...
            },
          },
          "es-ES": {
            translation: {
              app_name: "Aplicación meteorológica SolidJS",
              get_weather_button_label: "Obtener el clima",
              city_input_placeholder: "Introduce una ciudad",
              // ...
            },
          },
          "ru-RU": {
            translation: {
              app_name: "Погодное приложение SolidJS",
              get_weather_button_label: "Получить погоду",
              city_input_placeholder: "Введите город",
              // ...
            },
          },
          "ar-EG": {
            translation: {
              app_name: "تطبيق الطقس سولَد چاي إس",
              get_weather_button_label: "الطقس",
              city_input_placeholder: "أدخل مدينة",
              // ...
            },
          },
        },
      })
  });

  return (
      <h1>SolidJS Weather App</h1>
      <WeatherForm />
  );
}Code language: JavaScript (javascript)

We are initializing an i18next instance inside the createEffect() to ensure we only initialize it once, instead of on every render.

How do I work with translation files?

Having all our translations inline in resources like we do above is not very scalable. Let’s move our translations into separate files, one per locale, and only load in the one we need.

🗒️ Note » A locale defines linguistic conventions for data display, with a locale code identifying a specific locale and region e.g. en-US for English as spoken in the United States.

Solid-i18next uses the i18next library and follows its format for adding translation files in folders that follow the naming convention, locales/{{lng}}/{{ns}}.json. {{lng}} is the locale and {{ns}} is the namespace (translation by default). For example, our Spanish translations would sit in locales/es-ES/translation.json.

🗒️ Note » Namespaces give us a bit of structure, so we can break up our translations into logical sections if need be (e.g. admin, login).

Let’s create a locales folder at the root of our project with locale folders inside e.g. locales/en-US, locales/es-ES.

Next, we will add a translations.json file inside each locale folder.

// /locales/en-US/translation.json

{
  "app_name": "SolidJS Weather App",
  "get_weather_button_label": "Get Weather",
  "city_input_placeholder": "Enter a city",
  // ...
}Code language: JSON / JSON with Comments (json)
// /locales/es-ES/translation.json

{
  "app_name": "Aplicación meteorológica SolidJS",
  "get_weather_button_label": "Obtener el clima",
  "city_input_placeholder": "Introduce una ciudad",
  // ...
}Code language: JSON / JSON with Comments (json)
// /locales/ru-RU/translation.json

{
 "app_name": "Погодное приложение SolidJS",
 "get_weather_button_label": "Получить погоду",
 "city_input_placeholder": "Введите город",
 // ...
}Code language: JSON / JSON with Comments (json)
// /locales/ar-EG/translation.json

{
  "app_name": "تطبيق الطقس سولَد چاي إس",
  "get_weather_button_label": "الطقس",
  "city_input_placeholder": "أدخل مدينة",
  // ...
}Code language: JSON / JSON with Comments (json)

As we progress throughout the article, we will add new translation keys. But first, we need to load these translation files into i18next.

How do I load translation files asynchronosly?

i18next supports lazy loading of translations through the i18next-http-backend plugin, which we will install in our app.

npm install i18next-http-backendCode language: Bash (bash)

And now, use it in our App.jsx so our modified initialization code becomes this:

// /src/App.jsx

import { Show, createEffect, createSignal } from "solid-js";

import Backend from "i18next-http-backend";

function App() {
	const [isReady, setIsReady] = createSignal(false);
	
	createEffect(() => {
		i18next
		  .use(Backend)
		  .init({
		    lng: "en-US",
		    debug: true,
		    interpolation: {
		      escapeValue: true
		    },
		    fallbackLng: false,
		    // The default namespace to load when none
        // are specified explicitly.
        // "translation" is the default value here,
        // so we can can remove the `ns` option here
        // entirely if we wanted.
		    ns: "translation",
		    backend: {
		      loadPath: "../locales/{{lng}}/{{ns}}.json"
		    }
		  })
		  .then(() => setIsReady(true))
		  .catch((err) => console.error(err));
	});

return (
    <Show when={isReady()} fallback={<div>Loading...</div>}>
      <h1>SolidJS Weather App</h1>
      <WeatherForm />
    </Show>
  );
}Code language: JavaScript (javascript)

Since we’re loading our translation file asynchronously, we use a SolidJS <Show> component to display a loading message.

A note on the dev language and fallback

You might get an error like the following in your browser console:

GET http://localhost:3000/locales/dev/translation.json
net::ERR ABORTED 404 (Not Found).Code language: Diff (diff)

This is normal, and just indicates that i18next is falling back to the developer language, dev. You can set fallbackLng to false during development to disable the error if you like, although according to the official docs the dev language does have its uses.

How do I retrieve the active locale?

We can use i18next’s language property to get the currently active locale.

import i18next from "i18next";

console.log("Active locale:", i18next.language);Code language: JavaScript (javascript)

How do I work with basic translation messages?

Now that we set up our translations in the supported locales, we can start using solid-i18next to translate our app. The library provides a useTransContext() hook, which provides us with a translation t function. Given a translation key, t returns the associated translation in the active locale.

// /src/App.jsx

import { useTransContext } from "@mbarzda/solid-i18next";

function App() {
  const [t] = useTransContext();

  return (
    <Show when={isReady()} fallback={<div>Loading...</div>}>
      <div>
        <h1>{t("app_name")}</h1>
      </div>
    </Show>
  );
}

export default App;Code language: JavaScript (javascript)

Try changing the lng value in the i18next config from en-US to es-ES; you will see the heading now translated into Spanish. The same goes for the other locales, ru-RU and ar-EG.Our app name translated to the es-ES locale | Phrase

🗒️ Note » solid-i18next library also has a <Trans> component for translating messages. You can Read more about <Trans> the official docs.

Now let’s switch our attention to the weather form component and translate its strings:

// /src/components/WeatherForm.jsx

import { useTransContext } from "@mbarzda/solid-i18next";

const WeatherForm = () => {
  const [t] = useTransContext();
  
  // ...

  return (
    <div>
      <form
        onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder={t("placeholder")}
          value={city()}
          onInput={(event) => setCity(event.target.value)} />
        <button type="submit">
          {t("button_label")}
        </button>
      </form>

      {weatherData() && !weatherData().error && (
        <div>
          // ...
          <div>
            <p>{t("humidity")}</p>
            <p>{weatherData().humidity}%</p>
          </div>
          <div>
            <p>{t("date_label")}</p>
            <p>{weatherData().date}</p>
          </div>
          <div>
            <p>{t("weather_stations_label")}</p>
            <p>
              {weatherData().weatherStations}
            </p>
          </div>
        </div>
      )}
      // ...
    </div>
  );
};

export default WeatherForm;Code language: JavaScript (javascript)

But what if a translation value is missing from your translation files? In that case, the translation key will act as a fallback. For example, if we do this:

<p>{t("section_heading")}</p>Code language: JavaScript (javascript)

And if you haven’t defined section_heading in your translation files, you will get “section_heading” in our browser.

Our weather app with the fallback translation key | Phrase

How do I add a language switcher?

Let’s give our users a nice little drop-down to select their language of choice. We will create a new component for this: it will comprise a basic <select> element for choosing from the supported languages in our app:

// /src/components/LanguageSwitcher.jsx

import { useTransContext } from "@mbarzda/solid-i18next";
import i18next from "i18next";

const LanguageSwitcher = () => {
  const [t, { changeLanguage }] = useTransContext();

  function handleLanguageChange(event) {
    changeLanguage(event.target.value);
  }

  return (
   <div>
       <select
					value={i18next.language} 
					onChange={handleLanguageChange}
       >
         <option value="en-US">{t("english_label")}</option>
         <option value="es-ES">{t("spanish_label")}</option>
         <option value="ru-RU">{t("russian_label")}</option>
         <option value="ar-EG">{t("arabic_label")}</option>
       </select>
    </div>
  );
};

export default LanguageSwitcher;Code language: JavaScript (javascript)

i18next provides a changeLanguage function which updates the active locale in the i18next instance. When we use the changeLanguage to update the active locale, solid-i18next ensures that all of our t() calls are re-rendered.

Our app’s language switcher in action | Phrase

How do I add dynamic values in translation messages?

Sometimes we need to include variables in our translation messages at runtime. In our app, we show an error message to the user when they search for a city we don’t have weather data for.

Our app’s error message when there’s no weather data for a particular city | Phrase

When translating this message, we’d like to interpolate the city name at runtime. We can do this using i18next’s {{variable}} format.

// /locales/en-US/translation.json

{
  "error_message": "Could not find weather for {{city}}",
}Code language: JSON / JSON with Comments (json)
// /locales/es-ES/translation.json

{
  "error_message": "No se pudo encontrar el clima para {{city}}",
}Code language: JSON / JSON with Comments (json)
// /locales/ru-RU/translation.json

{
  "error_message": "Не удалось найти погоду для {{city}}",
}Code language: JSON / JSON with Comments (json)
// /locales/ar-EG/translation.json

{
   "error_message": "لا يمكن العثور على الطقس ل{{city}}",
}Code language: JSON / JSON with Comments (json)

Inside the WeatherComponent, instead of hardcoding the error string, we can replace it with the t function, passing in the correct key and passing city as the second parameter:

// /src/components/WeatherForm.jsx

// ...

		if (cityWeather) {
			setWeatherData(cityWeather);
		} else {
			setWeatherData({
			  error: t("error_message", {
			    city: city(),
			  }),
			});
		}

// ...Code language: JavaScript (javascript)

Our app’s error message when there’s no weather data for a particular city in es-ES locale | Phrase

How do I work with plurals in translation messages?

The languages our app supports have different plural forms. English has 2 forms (one and other), Spanish has 3 (onemany and other), Russian has 4 (onefewmany and other), and Arabic has 6 (zeroonetwofewmany and other).

🗒️ Note » i18next uses the Intl.PluralRules API under the hood to determine which plural form to use for a message in the active locale.

In our app, we need the number of weather stations for a city, a pluralized message:

// /src/components/WeatherForm.jsx

// ...

return (
    <div>
      // ...
      {weatherData() && !weatherData().error && (
        <div>
          // ...
          <div>
            <p>{t("weather_stations_label")}</p>
            <p>
              {t("station", { count: weatherData().weatherStations })}
            </p>
          </div>
        </div>
      )}
      // ...
    </div>
  );
};Code language: JavaScript (javascript)

Note the count variable here: this will be used by i18next to select the appropriate plural form.

Heads up » There will be no fallback for a plural message if count is not provided.

Now let’s add the corresponding translations. For plurals, we use separate keys to mark the plural forms of a message, adding a plural form suffix to each. For example, the station message plural forms would have keys station_one, station_many, etc.

🔗 Resource » The CLDR Language Plural Rules chart is a canonical source for languages’ plural forms.

To differentiate between normal and plural form keys, i18next relies on the count option passed to t(). If it sees the count, it will look for plural form keys.

// /locales/en-US/translation.json

{
  // ...
  "station_one": "{{count}} weather station",
  "station_other": "{{count}} weather stations"
}Code language: JSON / JSON with Comments (json)
// /locales/es-ES/translation.json

{
  // ...
  "station_one": "{{count}} estación",
  "station_many": "{{count}} estaciones",
  "station_other": "{{count}} estaciones"
}Code language: JSON / JSON with Comments (json)
// /locales/ru-RU/translation.json

{
  // ...
  "station_one": "{{count}} метеостанция",
  "station_few": "{{count}} метеостанции",
  "station_many": "{{count}} Много станций",
  "station_other": "{{count}} станций"
}Code language: JSON / JSON with Comments (json)
// /locales/ar-EG/translation.json

{
  // ...
  "station_one": "محطة {{count}}",
  "station_two": "محطتان {{count}}",
  "station_few": "{{count}} محطات",
  "station_many": "{{count}} محطة",
  "station_other": "{{count}} محطة"
}Code language: JSON / JSON with Comments (json)

Number of weather stations based on the count value in en-US locale | Phrase

Number of weather stations based on the count value in ru-RU locale | Phrase

🔗 Resource » See the official i18next documenation on Plurals for more information.

Working with interval plurals

Interval plurals help to define phrases expressing the number of items that lie within a range. If you want to use interval plurals in your application where, for instance, you want to pluralize the number of likes of a post, then you will need to use a post-processor called i18next-intervalplural-postprocessor.

We first install it in our app:

npm install i18next-intervalplural-postprocessor

Then add it to our i18next initialization code:

// /src/App.jsx

import i18next from 'i18next';
import intervalPlural from 'i18next-intervalplural-postprocessor';

// ...

i18next
 .use(intervalPlural)
 .init({
   // ...
 })
// ...Code language: JavaScript (javascript)

Interval plurals have a special syntax in i18next translation messages:

// /locales/es-ES/translation.json

"likes_interval": "(0)[No me gusta];(1-100)[1+ gustos];(100-999)[100+ gustos];(1000-9999)[1k+ gustos];(10000-99999)[10k+ gustos];"Code language: JSON / JSON with Comments (json)

With this, we can use these keys in our app like this:

// /src/App.jsx

<div>
    <p>Number of likes in this post: 
      {t("likes_interval", {
        postProcess: "interval",
        count: weatherData().likes,
      })}
    </p>
  </div>Code language: JavaScript (javascript)

Interval plurals rendering number of likes in a post in en-US locale | Phrase

Interval plurals rendering number of likes in a post in es-ES locale I Phrase

How do I localize numbers?

We need to format the numbers we see for the temperature, “feels like”, and humidity values. Unlike English, Spanish and Russian, Arabic doesn’t use Western Arabic numerals (1, 2, 3). Instead, it uses Eastern Arabic numerals (١,٢,٣). Each locale can also have its own large number separation rules (e.g. 1,000,000), percentage symbol, currency formatting, and more.

🤿 Go deeper » Our Concise Guide to Number Localization covers the topic in much more detail.

So we need a way to localize our numbers. We can format numbers in our translation messages using the {{val, number}} syntax. The number keyword here is essential: It tells i18next to format the value as a number in the active locale.

🗒️ Note » i18next uses JavaScript’s built-in Intl.NumberFormat ****object for number formatting.

Let’s update all the instances of number formatting in our translation files so we get the following:

// /locales/en-US/translation.json

{
  // ...
  "temperature_celcius": "Temperature is: {{val, number}}",
  "temperature_fahrenheit": "Or: {{val, number}}",
  "feels_like": "Feels like: {{val, number}}",
  "humidity_number": "{{val, number}}",
  // ...
}Code language: JSON / JSON with Comments (json)
// /locales/es-ES/translation.json

{
  // ...
  "temperature_celcius": "La temperatura es: {{val, number}}",
  "temperature_fahrenheit": "O: {{val, number}}",
  "feels_like": "Se siente como: {{val, number}}",
  "humidity_number": "{{val, number}}",
  // ...
}Code language: JSON / JSON with Comments (json)
// /locales/ru-RU/translation.json

{
  // ...
  "temperature_celcius": "Температура: {{val, number}}",
  "temperature_fahrenheit": "Или: {{val, number}}",
  "feels_like": "Как будто: {{val, number}}",
  "humidity_number": "{{val, number}}",
  // ...
}Code language: JSON / JSON with Comments (json)
// /locales/ar-EG/translation.json

{
  // ...
  "temperature_celcius": "درجة الحرارة: {{val, number}} درجة مئوية",
  "temperature_fahrenheit": "أو: {{val, number}} درجة فهرنهايت",
  "feels_like": "تبدو مثل: {{val, number}} درجة مئوية",
  "humidity_number": "{{val, number}}",
  // ...
}Code language: JSON / JSON with Comments (json)

In our WeatherForm component, we pass the val property in the t function. This property’s value should be the corresponding weather data we need:

// /src/components/WeatherForm.jsx
// ...
{weatherData() && !weatherData().error && (<div>
  <h2>
    📍 {
    weatherData().city
  }, {
    weatherData().country
  } </h2>
  <div>
    <p> {
      t("temperature_celcius", {
        val: weatherData().temperatureCelcius,
        formatParams: {
          val: {
            style: "unit",
            unit: "celsius"
          }
        }
      })
    }
      <br/> {
      t("temperature_fahrenheit", {
        val: weatherData().temperatureFahrenheit,
        formatParams: {
          val: {
            style: "unit",
            unit: "fahrenheit"
          }
        }
      })
    } </p>
  </div>
  <div>
    <p> {
      t("feels_like", {
        val: weatherData().feelsLike,
        formatParams: {
          val: {
            style: "unit",
            unit: "celsius"
          }
        }
      })
    } </p>
  </div>
  <div>
    <p> {
      t("humidity")
    }</p>
    <p> {
      t("humidity_number", {
        val: weatherData().humidity,
        formatParams: {
          val: {
            style: "percent"
          }
        }
      })
    } </p>
  </div>
  // ...
</div>)
}Code language: JSON / JSON with Comments (json)

Note how we are also passing additional formatting options with the formatParams property to the t function. We add the nested val property along with two sub style and unit properties so that our unit of measurement for temperature values shows as expected.

Weather app showing localized numbers in en-US locale | Phrase

Weather app showing localized numbers in ru-RU locale | Phrase

How do I localize dates?

We’re being transparent and showing our users the date our weather data was recorded. Let’s localize this date. Similar to number formatting, for date formatting, we need to provide the following syntax in our translation value: {{val, datetime}}. This indicates to i18next to format the value as a DateTime.

🗒️ Note » Under the hood i18next uses the Intl.DateTimeFormat object to format localized dates.

Let’s add this DateTime to our translation files:

{
  // ...
  "data_recorded_on_date": "{{val, datetime}}",
  // ...
}Code language: JSON / JSON with Comments (json)

And then pass on the date property from our data in the weather component:

// /src/components/WeatherForm.jsx

// ...
<div>
  <p>{t("date_label")}</p>
  <p>{t("data_recorded_on_date", { val: weatherData().date })}</p>
</div>Code language: JavaScript (javascript)

Weather app showing the localized date in en-US locale | Phrase

Localized date in ru-RU locale | Phrase

If you need to modify the default date format, use the formatParams property of t function and pass the relevant values:

{t("data_recorded_on_date", {
	val: weatherData().date,
  formatParams: {
	  val: {
	    year: "2-digit",
      month: "short",
      day: "numeric",
    },
  },
})}Code language: JavaScript (javascript)

This results in a date that is a number, but the month name is shortened and the year is of 2 digits:

Localized date with custom formatting in en-US locale | Phrase

Localized date with custom formatting in es-ES locale | Phrase

🔗 Resource » Learn all the date formatting options i18next prodivides in the official docs.

How do I work with text direction (LTR, RTL)?

Arabic, among other languages, is laid out right-to-left. To accommodate this, we need to switch the <html dir> attribute so that it becomes <html dir="rtl"> when the active locale is Arabic.

// /src/App.jsx

import {createSignal, createEffect} from "solid-js";


function App() {
	//...
  const [locale, setLocale] = createSignal('en-US');

  //...

  const handleLocaleChange = (newLocale) => {
    setLocale(newLocale);
  };

  // Update the document text direction when 
  // the locale is changed.
  createEffect(() => {
    if (locale().startsWith('ar-EG')) {
      document.documentElement.setAttribute('dir', 'rtl');
    } else {
      document.documentElement.setAttribute('dir', 'ltr');
    }
  });

  //...
}

export default App;Code language: JavaScript (javascript)

Finally, we need to modify the language switcher drop-down to incorporate the locale() and onLocaleChange params:

// /src/components/LanguageSwitcher.jsx

const LanguageSwitcher = ({ locale, onLocaleChange }) => {
  const [t, { changeLanguage }] = useTransContext();

  function handleLanguageChange(event) {
    const newLocale = event.target.value;
    changeLanguage(newLocale);
    onLocaleChange(newLocale);
  }

  return (
    <div>
      <select
        value={locale}
        onChange={handleLanguageChange}
      >
        <option value="en-US">{t("english_label")}</option>
        <option value="es-ES">{t("spanish_label")}</option>
        <option value="ru-RU">{t("russian_label")}</option>
        <option value="ar-EG">{t("arabic_label")}</option>
      </select>
    </div>
  );
};

export default LanguageSwitcher;Code language: JavaScript (javascript)

Then make sure we also add these props to our App component as well:

// /src/App.jsx

import LanguageSwitcher from "./components/LanguageSwitcher";

// ...
function App() {
  // ...

  return (
    <Show when={isReady()} fallback={<div>Loading...</div>}>
      <div>
        <h1>{t("app_name")}</h1>
        <LanguageSwitcher
          locale={locale()}
          onLocaleChange={handleLocaleChange}
        />
        <WeatherForm />
      </div>
    </Show>
  );
}
export default App;Code language: JavaScript (javascript)

With these changes, your language switcher should work normally, and we now get our document in the RTL direction when in the ar-EG locale:

RTL direction when in the ar-EG locale | Phrase

How do I automatically detect the user’s language?

To let i18next automatically detect locale in our app based on the user’s preferred language, we can use the i18next-browser-languagedetector plugin. It detects the language in the following order as stated in its docs:

order: ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag', 'path', 'subdomain'],Code language: JavaScript (javascript)

Let’s install it:

npm install i18next-browser-languagedetectorCode language: Bash (bash)

Then we can use() it in our i18next code:

// /src/App.jsx

import LanguageDetector from "i18next-browser-languagedetector";

i18next
 .use(LanguageDetector)
 .init({
   // ...
 })
 .then(() => setIsReady(true))
 .catch((err) => console.error(err));Code language: JavaScript (javascript)

If a user’s browser is set to a preferred language, say fr-FR, and our app only supports fr-CA, i18next will still match the language part (fr) and use fr-CA for translations.

In cases where none of the user’s preferred locales match our app’s locales, i18next will resort to the fallbackLng set during the library’s configuration.

But how does automatic language detection interplay with our manual LanuageSwitcher? Here, if the changeLanguage method of i18next is called without any arguments, it defaults to what the i18next-browser-languagedetector plugin determines. However, if a user decides to manually select a language via the LanguageSwitcher, this will override the automatic language detection.

🗒️ Note » You can find the final project with all the component code on GitHub.

Step up your game in JavaScript localization

In this tutorial, we’ve delved into internationalizing SolidJS applications with the powerful solid-i18next and i18next libraries. We’ve covered translating messages, implementing a language switcher, and mastering interpolation and plurals with number and date formatting.

Now that your application is ready to go global, it’s time to make the translation process simpler. Enter Phrase Strings, our dedicated software localization solution, designed to streamline string translation management from start to finish.

With its robust API for automating translation workflows and seamless integrations with platforms like GitHub, GitLab, and Bitbucket, Phrase Strings takes care of the heavy lifting, giving you back time to focus on your code.

String Management UI visual | Phrase

Phrase Strings

Take your web or mobile app global without any hassle

Adapt your software, website, or video game for global audiences with the leanest and most realiable software localization platform.

Explore Phrase Strings

Phrase Strings offers a comprehensive strings editor that empowers translators to pick up content for translation with ease. Once they’ve completed their work, you can automatically integrate the translated content back into your project in the preferred file format.

Check out all Phrase features for developers and see for yourself how they can streamline your software localization workflows right from the start.