<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martin Vandersteen</title>
    <description>The latest articles on DEV Community by Martin Vandersteen (@vdsmartin).</description>
    <link>https://dev.to/vdsmartin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F569234%2F2155f723-fcd1-4480-bfe1-08ece3048e23.jpg</url>
      <title>DEV Community: Martin Vandersteen</title>
      <link>https://dev.to/vdsmartin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vdsmartin"/>
    <language>en</language>
    <item>
      <title>Deploying a Laravel app using Dokku</title>
      <dc:creator>Martin Vandersteen</dc:creator>
      <pubDate>Fri, 19 Nov 2021 18:24:55 +0000</pubDate>
      <link>https://dev.to/vdsmartin/deploying-a-laravel-app-using-dokku-5bi3</link>
      <guid>https://dev.to/vdsmartin/deploying-a-laravel-app-using-dokku-5bi3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Dokku is basically a self-hosted Heroku. It allows you to deploy your apps with a simple git push and it'll handle all the building and deploying automatically.&lt;/p&gt;

&lt;p&gt;After having trouble finding an up-to-date and exhaustive how-to guide, I decided to write the steps down, It'll help me later and hopefully you as well ? Here are the steps I went through !&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup your server
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a DigitalOcean Dokku droplet or any other basic Ubuntu/Debian Server on which you &lt;a href="https://dokku.com/docs/getting-started/installation/"&gt;installed Dokku&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(facultative) Point a domain or Sub-domain to your Dokku installation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visit your server's IP in your browser to finish the Dokku setup, make sure the SSH key is the correct one. Enter your Top Level Domain in the hostname field. I chose to use the virtualhost naming for my Dokku apps, I think it makes more sense for web apps!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install the MySQL plugin for Dokku on your server&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku plugin:install https://github.com/dokku/dokku-mysql.git mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;(optional) Install the Let's Encrypt plugin if you want free SSL certificates
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add PHP extension requirements to &lt;code&gt;composer.json&lt;/code&gt;. This tells Dokku to activate those extensions on your server. Here are the ones I needed as example !
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"require": {
  "ext-bcmath": "*",
  "ext-ctype": "*",
  "ext-curl": "*",
  "ext-exif": "*",
  "ext-fileinfo": "*",
  "ext-gd": "*",
  "ext-json": "*",
  "ext-mbstring": "*",
  "ext-pdo": "*",
  "ext-openssl": "*",
  "ext-tokenizer": "*",
  "ext-xml": "*",
  "ext-zip": "*"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;After making the changes in &lt;code&gt;composer.json&lt;/code&gt; you'll need to update it using &lt;code&gt;composer update&lt;/code&gt;. You might encounter problems with the PHP extensions not being installable on your local env, in that case use &lt;code&gt;composer update --ignore-platform-reqs&lt;/code&gt; to avoid installing them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a build script to package.json, it will be run automatically on deploy by Dokku&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "production": "mix --production",
    "build": "npm run production"
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make sure your &lt;code&gt;config/database.php&lt;/code&gt; has this line in the 'mysql' config :&lt;br&gt;
&lt;code&gt;'url' =&amp;gt; env('DATABASE_URL'),&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a Procfile (a file named Procfile at the root of the project). The &lt;code&gt;release&lt;/code&gt; part contains the command that'll be run right after a build. The &lt;code&gt;web&lt;/code&gt; part specifies the web server to run, here we simply run the default PHP server from Heroku and point it to the &lt;code&gt;public&lt;/code&gt; folder&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;release: php artisan migrate --force
web: vendor/bin/heroku-php-apache2 public/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Specify the buildpacks you'll need in a .buildpacks file at the root of the project. You can see these as "build steps" before putting your application online. &lt;code&gt;heroku-buildpack-php&lt;/code&gt; install your composer packages (amongst other things) and &lt;code&gt;heroku-buildpack-nodejs&lt;/code&gt; install your npm dependencies and runs your build script.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/heroku/heroku-buildpack-php
https://github.com/heroku/heroku-buildpack-nodejs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dokku Setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create the app on the Dokku server with this command&lt;br&gt;
&lt;code&gt;dokku apps:create {name_of_your_app}&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the git remote on your local dev environment with :&lt;br&gt;
&lt;code&gt;git remote add {remote_name} dokku@{server_url}:{app_name}&lt;/code&gt;&lt;br&gt;
&lt;code&gt;remote_name&lt;/code&gt; can be whatever. I use "staging" and "production" for example. &lt;code&gt;app_name&lt;/code&gt; is the name you gave your Dokku app at the previous step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up all your environment variables on the server using the &lt;code&gt;dokku config:set {your_app_name} VAR=VALUE VAR2=VALUE2 ...&lt;/code&gt; command. Don't forget to mention an APP_KEY, APP_ENV, APP_URL, etc !&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a MySQL database and link it to your app (your-db-name can be anything)&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku mysql:create {your-db-name}
dokku mysql:link {your-db-name} {your_app_name}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Get the MySQL connection URL (mysql://&lt;a href="mailto:user@example"&gt;user@example&lt;/a&gt;...) and set it as a variable for your app
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku mysql:info {your-db-name} --dsn
dokku config:set {your_app_name} DATABASE_URL={connection_string_from_previous_command}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Push your app to your Dokku server using :
&lt;code&gt;git push {remote_name}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  (optional) Add a Let's encrypt certificate to your app !
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Setup your contact email for Let's encrypt and generate your certificate
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set --no-restart {your_app_name} DOKKU_LETSENCRYPT_EMAIL={your_email_address}
dokku letsencrypt:enable {your_app_name}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  All done 💃
&lt;/h2&gt;

&lt;p&gt;Enjoy your app !&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>docker</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>HTTP to HTTPS Redirection with Laravel Vapor</title>
      <dc:creator>Martin Vandersteen</dc:creator>
      <pubDate>Sun, 03 Oct 2021 11:10:37 +0000</pubDate>
      <link>https://dev.to/vdsmartin/http-to-https-redirection-with-laravel-vapor-2hdf</link>
      <guid>https://dev.to/vdsmartin/http-to-https-redirection-with-laravel-vapor-2hdf</guid>
      <description>&lt;p&gt;One of the limitations of Laravel Vapor is the inability to serve HTTPS. This isn't because of Vapor though, it comes from the Api Gateways from AWS that Vapor is built on. The problem with that is that when browser try to reach your domain via HTTP, nothing happens and they get an error screen, this is annoying for users that simply type your domain name in the url bar on IOS Safari for example as it won't try to contact the HTTPS port and just fail.&lt;/p&gt;

&lt;p&gt;After two days of fighting with Cloudfront and Vapor, I finally got it working! With one caveat: If I change the domains associated to my Vapor environment in vapor.yml, I will experience a small downtime.&lt;/p&gt;

&lt;p&gt;Important thing to note, this cannot work with the default SESSION_DRIVER which is the cookie driver as the headers become too large. &lt;/p&gt;

&lt;p&gt;I'm using dynamodb as driver. Also, I'm using Api Gateway V2 and Route 53 as DNS so Vapor handles my DNS records ! If Vapor doesn't handle your DNS records you might be able to bypass some of this by changing the DNS records yourself to point at your Cloudfront distribution. In my case it would make my deployments fail.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Try this on a staging environment and test everything out carefully before trying it on production. Try to log in etc to make sure sessions work as well!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the example, well say that we have a site that is currently deployed on &lt;code&gt;example.com&lt;/code&gt; and &lt;code&gt;www.example.com&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the Cloudfront distribution
&lt;/h1&gt;

&lt;p&gt;The idea is pretty simple, we'll put a Cloudfront distribution in front of our Api Gateway to act as a reverse proxy and redirect port 80 to port 443 when applicable.&lt;/p&gt;

&lt;p&gt;Create a new Cloudfront distribution and use a subdomain of your choice as the origin url, that will be the new url of our Api Gateway that we will setup later on. Let's use &lt;code&gt;gateway.example.com&lt;/code&gt; !&lt;/p&gt;

&lt;p&gt;Give it a name of your liking then scroll down to the &lt;strong&gt;User&lt;/strong&gt; section and select &lt;strong&gt;"Redirect HTTP to HTTPS"&lt;/strong&gt; as &lt;strong&gt;User protocol policy&lt;/strong&gt; and &lt;strong&gt;"GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE"&lt;/strong&gt; as &lt;strong&gt;Allowed HTTP methods&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Regarding the Cache settings, select the &lt;strong&gt;"Legacy cache settings"&lt;/strong&gt; and select &lt;strong&gt;"All"&lt;/strong&gt; for &lt;strong&gt;Headers&lt;/strong&gt;, &lt;strong&gt;Request query&lt;/strong&gt; and &lt;strong&gt;Cookies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Settings&lt;/strong&gt; section, select the pricing category that makes the most sense to you, I'm using "Only Northern America and Europe" but it depends where your users are.&lt;/p&gt;

&lt;p&gt;As "Alternative Domain Names", make sure to add &lt;code&gt;www.example.com&lt;/code&gt; and &lt;code&gt;example.com&lt;/code&gt;. Then, right under that, create an SSL certificate for those. I just used a wildcard certificate (*.example.com, example.com). You might need to add gateway.example.com in there but I don't think so !&lt;/p&gt;

&lt;p&gt;Once you're done, click the create button and you're good to go !&lt;/p&gt;

&lt;h1&gt;
  
  
  Updating vapor.yml
&lt;/h1&gt;

&lt;p&gt;The domains section of your vapor.yml should look a little bit like this by now :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;domain:
  - www.example.com
  - example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change it to this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;domain: gateway.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, once you're ready, deploy your application but BE AWARE that your application won't be reachable until you complete the next step (pretty fast).&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the Custom Domains on the Api Gateway
&lt;/h1&gt;

&lt;p&gt;Go to your &lt;strong&gt;Api Gateway&lt;/strong&gt; settings on AWS, in the Custom Domains section. Your &lt;code&gt;www.example.com&lt;/code&gt; and &lt;code&gt;example.com&lt;/code&gt; domains should have disappeared (deleted by Vapor) after the deployment. You simply need to recreate them both (don't forget to attach the &lt;strong&gt;certificate&lt;/strong&gt;, you normally created one already via Vapor beforehand when attaching your custom domain for the first time).&lt;/p&gt;

&lt;p&gt;Then, for both of those custom domain, setup the &lt;strong&gt;API Mappings&lt;/strong&gt; to be the same as the ones created by Vapor on &lt;code&gt;gateway.example.com&lt;/code&gt;. Select the right API Endpoint and Step (there is only one most of the time) then click save ! &lt;/p&gt;

&lt;p&gt;Do that for both domains and, after a few seconds, your app should now be accessible and redirect HTTP to HTTPS successfully :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I'm by no means an AWS expert so there might be ways to do this in a safer or more optimized way, if that's the case don't hesitate to comment down below.&lt;/p&gt;

&lt;p&gt;As I said above, if you change the domains in your vapor.yml, you will need to re-do the last step so keep that in mind. I will personnaly work on creating an after-deploy script for Vapor that will ensure those Custom Domains exist and re-create them if they don't !&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>aws</category>
      <category>https</category>
      <category>vapor</category>
    </item>
    <item>
      <title>Why I use Laravel as a Rails aficionado</title>
      <dc:creator>Martin Vandersteen</dc:creator>
      <pubDate>Thu, 05 Aug 2021 19:26:05 +0000</pubDate>
      <link>https://dev.to/vdsmartin/why-i-use-laravel-as-a-rails-aficionado-4ea2</link>
      <guid>https://dev.to/vdsmartin/why-i-use-laravel-as-a-rails-aficionado-4ea2</guid>
      <description>&lt;p&gt;I've been building web products professionnally and for fun for more than 7 years now. Despite being a complete Ruby on Rails fan (my first love) and having first experienced Laravel in the worst ways possible (taking over a legacy SaaS that was a complete mess), I still decided to use Laravel for my next major projects.&lt;/p&gt;

&lt;p&gt;Both present similar MVC structures and similar functionalities, but they have quite different ecosystems. To me, being a good developer means choosing the right tool for the job, here are some reasons why I chose Laravel over Rails for those applications.&lt;/p&gt;

&lt;h1&gt;
  
  
  The ecosystem
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9a2dyy84jm7qe8ipn9o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9a2dyy84jm7qe8ipn9o.png" alt="The Laravel Ecosystem"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to Tailor Otwell and others, there are a lot of VERY powerful satellite packages and tools that'll make you win precious time when building web products. Here are some of the ones that drove my decision :&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Sail
&lt;/h3&gt;

&lt;p&gt;Super easy development environment : &lt;code&gt;$ sail up&lt;/code&gt; and you're good to go ! It spins up a web server, database, redis server and even mailhog to monitor your emails. All through Docker🐋 so you don't pollute your computer with useless servers that will clash with your other development projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Breeze &amp;amp; Jetstream
&lt;/h3&gt;

&lt;p&gt;Thanks to those, you can start your application with all the boring authentication part pre-made for you. This includes signup, login, profile settings, dashboard, email verification &amp;amp; forgot password, password change, 2FA, .. Such a huge timesaver !&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Nova
&lt;/h3&gt;

&lt;p&gt;Powerful, beautiful &amp;amp; customizable admin ! Quite easy to install and to extend, with a lot of very useful plugins that'll make your life way easier. Building admins for web apps is a big part of the development time and any time saved is a big money saver, as long as the quality remains.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Vapor
&lt;/h3&gt;

&lt;p&gt;Vapor turns your app into a Serverless app that autoscales in a hearthbeat. It manages your AWS ressources for you and allows you to change things in your Cloud with simple commands. It will save you weeks of handling your servers, I really think it has a crazy good Return On Investment for small to mid-sized applications. Focus on building your app and not on fiddling in AWS !&lt;/p&gt;

&lt;h3&gt;
  
  
  Statamic
&lt;/h3&gt;

&lt;p&gt;Statamic is a pretty powerful and modern CMS that'll get you out of your Wordpress nightmares. Simple and efficient ! It recently saved me when I needed to build a BIG application that was a mix of a Website, CMS, CRM and E-commerce. Statamic made it possible for my client to edit pages &amp;amp; products in an efficient way while giving me the whole power of Laravel to meet requirements for the CRM aspect of things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Spark
&lt;/h3&gt;

&lt;p&gt;I personally never used it but it seems to be the Laravel Jetstream of SaaS products ! (It actually plays along well with Jetstream &amp;amp; Breeze) It handles subscriptions, payment providers &amp;amp; invoices for you so you can focus on the actual value your app brings.&lt;/p&gt;

&lt;h1&gt;
  
  
  The community
&lt;/h1&gt;

&lt;p&gt;Pretty simple and straightforward but as there are more people using Laravel, it's easier to find answers to your problem on the web as well as finding help !&lt;/p&gt;

&lt;p&gt;But that was not the deciding factor for me as, in my opinion, Rails is often easier to understand and more logical than Laravel. The main reason why the community is so important to me is that I'm making it easy for my clients to find new developers later on, as they will need someone to maintain and improve on the application that I built someday. There are a lot more people that use PHP in the world than Ruby and it's important to keep that in mind when you build an application that will be the epicenter of someone's business.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I'm still in love with Rails ! And I love that it keeps growing so well and evolving (can't wait to try Hotwire and compare it with Livewire), and it still is one of my favourite tools. &lt;/p&gt;

&lt;p&gt;I just think it's important to be flexible and to be able to avoid getting stuck with one framework the becomes a Bible you franctically shake in front of everybody that uses something else! &lt;/p&gt;

&lt;p&gt;Be curious and open to new things, give it a look and objectively assess if it's for you, or not, and if it actually brings new value to the programming world. There are tons of frameworks popping up everyday and while a lot are probably good, only a few will really fit you and make your work more efficient.&lt;/p&gt;

&lt;p&gt;What's your take on this ? How do you position yourself in this world of frameworks ? 😜&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>rails</category>
      <category>php</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Simple i18n for StimulusJS !</title>
      <dc:creator>Martin Vandersteen</dc:creator>
      <pubDate>Tue, 23 Mar 2021 15:04:06 +0000</pubDate>
      <link>https://dev.to/vdsmartin/simple-i18n-for-stimulusjs-1lc</link>
      <guid>https://dev.to/vdsmartin/simple-i18n-for-stimulusjs-1lc</guid>
      <description>&lt;p&gt;I recently had to setup internationalization on my website &lt;a href="https://www.feeka.studio"&gt;Feeka.studio&lt;/a&gt; which is only built with HTML/SCSS/StimulusJS and a few JS libraries. &lt;/p&gt;

&lt;p&gt;Surprisingly enough, I didn't find much material on how to easily implement it with a Stimulus controller and I spent some time researching in order to make it work. I hope this short walkthrough will help other people in the same situation !&lt;/p&gt;

&lt;h1&gt;
  
  
  The HTML
&lt;/h1&gt;

&lt;p&gt;For this example, I created a very simple layout with a banner containing a language switcher and a catchline, along with a section holding some random content. I also sprinkled a bit of CSS on it to make it a bit more organized, everything is in the Codepen at the end of the article.&lt;/p&gt;

&lt;p&gt;Here is what it looks like :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"banner"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"switcher"&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"locale"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"catchphrase"&lt;/span&gt; &lt;span class="na"&gt;data-i18n=&lt;/span&gt;&lt;span class="s"&gt;"[html]catchphrase"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="na"&gt;data-i18n=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Setting up our Stimulus controller
&lt;/h1&gt;

&lt;p&gt;For this example, I will be creating a single "LocaleController". Keep in mind that I will be putting all of my Javascript in the same file here since I'm using Codepen, but it's always good to split the different parts of the application, for example the controller should be declared in its own file, and the i18n configuration we'll be writing later on should be in another one. Being organized and maintaining the separation of concerns makes our lives easier in the long run !&lt;/p&gt;

&lt;p&gt;Here is the basis of my controller :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;LocaleController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;language&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;span data-action="click-&amp;gt;locale#changeLocale"
      data-locale="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" data-target="locale.language"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;`&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;changeLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-locale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlightCurrentLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;highlightCurrentLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languageTargets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-locale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First of all, I'm defining a getter for our list of languages which for this example will be French and English. The &lt;code&gt;title&lt;/code&gt; represents the text that should appear in the language switcher and the code is what we'll use to manage our translations with i18next later on, it could also be written using the standard 'fr-FR' notation. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;initialize()&lt;/code&gt; function, I'm setting up my dynamic language switcher by iterating over the available languages and inserting a span for each of them, along with a data-action attribute that will call our &lt;code&gt;changeLocale()&lt;/code&gt; function on click and a data-locale attribute that will make it easy to retrieve the language code when the element is clicked. I'm also manually setting the locale to French at the moment but that will be handled by i18next once we implement it.&lt;/p&gt;

&lt;p&gt;Right now the &lt;code&gt;changeLocale(e)&lt;/code&gt; function only makes sure we hide the current language in the switcher and show the other one. Using CSS, I made sure only the &lt;span&gt; with the 'active' class is shown.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Here is the current state of things : &lt;a href="https://codepen.io/martinvandersteen/pen/vYgEEMN"&gt;https://codepen.io/martinvandersteen/pen/vYgEEMN&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We just have a language switcher that switches when you click it, nothing crazy, but that'll change quickly !&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding i18next in the mix
&lt;/h1&gt;

&lt;p&gt;For this, I'm using some additional packages : 'i18next' that manages the bulk of the i18n job, 'loc-i18next' that will insert the translations in our HTML to make it a bit easier for us and 'i18next-browser-languagedetector' that does exactly what the name suggests ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing our packages
&lt;/h2&gt;

&lt;p&gt;At the top of my file, I'll be creating simple objects like these to make it easy to see on CodePen :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frTranslations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;catchphrase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bienvenue sur &amp;lt;strong&amp;gt;DEV.TO&amp;lt;/strong&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contenu statique de mon site internet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enTranslations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;catchphrase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome to &amp;lt;strong&amp;gt;DEV.TO&amp;lt;/strong&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Static content of my website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the production environment I'm putting all of my translations in two &lt;code&gt;/locales/fr/global.json&lt;/code&gt; and &lt;code&gt;/locales/en/global.json&lt;/code&gt; files, then I'm simply importing them when I'm initializing i18next, that makes it all a bit cleaner. But that'll do just fine for the sake of the example !&lt;/p&gt;

&lt;p&gt;You can see that the "catchphrase" and "content" keys are actually the ones used in the &lt;code&gt;[data-i18n]&lt;/code&gt; attributes in our HTML, that's what enables our 'loc-i18next' package to know where to insert the various translations in our file.&lt;/p&gt;

&lt;p&gt;After writing down those translation objects, let's initialize i18next like so :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We're telling i18next to use(LngDetector) which is the name I used to import our 'i18next-browser-languagedetector' package&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LngDetector&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
  &lt;span class="na"&gt;supportedLngs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// The list of languages we use&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// The default language to use when no translations are found in the current locale&lt;/span&gt;
  &lt;span class="na"&gt;detection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;navigator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// What to infer the initial locale from, this is given by our LngDetector&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frTranslations&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// Specifying our translations&lt;/span&gt;
    &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;enTranslations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Writing down errors in the console if need be&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then initialize 'loc-i18next' which will insert our translations in the DOM by specifying the HTML attribute we used to mark the places used for the content :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We attach localize to our i18next instance and tell him to look for 'data-i18n' attributes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;locI18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;selectorAttr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-i18n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With everything setup, it's time to insert our i18next logic into our controller and make it all work together !&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the Controller
&lt;/h2&gt;

&lt;p&gt;In our Initialize method, we'll simply wrap everything with our &lt;code&gt;i18n.then(() =&amp;gt; {});&lt;/code&gt; call, that will make sure we only run that code after i18n has been fully initialized with the translations and current browser language, like so :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;span data-action="click-&amp;gt;locale#changeLocale"
        data-locale="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" data-target="locale.language"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;`&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we're also setting the locale at the end of the function with &lt;code&gt;setLocale(i18next.language)&lt;/code&gt;, using the language automatically detected by our i18next LngDetector as argument.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;setLocale(locale)&lt;/code&gt;, we'll make sure that we change the locale directly at the i18next level now, and we'll call the &lt;code&gt;localize(selector)&lt;/code&gt; method from 'loc-i18next' in order to update the content according to the new language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;localize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-i18n]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// localize() takes as argument a selector, by passing '[data-i18n]' we update all DOM elements that have a data-i18n attribute set&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlightCurrentLocale&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we also have to update the "highlightCurrentLocale()" function so that it uses "i18next.language" to define the current locale used.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;And voilà ! It is a pretty simple setup, so don't hesitate to build a bit on that basis, by changing part of the URL when the local changes and infering the locale from the URL/Browser cache/... You will find quite some documentation regarding the i18next package, even though a lot of it is about react-i18next, it still applies.&lt;/p&gt;

&lt;p&gt;Here is the final codepen link : &lt;a href="https://codepen.io/martinvandersteen/pen/abpbvgK"&gt;https://codepen.io/martinvandersteen/pen/abpbvgK&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope it will help you setting up i18n on your own websites, cheers ! &lt;/p&gt;

</description>
      <category>stimulus</category>
      <category>i18n</category>
      <category>javascript</category>
      <category>json</category>
    </item>
  </channel>
</rss>
