diff --git a/js/src/components/Utils/HomepageRedirectComponent.vue b/js/src/components/Utils/HomepageRedirectComponent.vue
new file mode 100644
index 000000000..909b9f6f0
--- /dev/null
+++ b/js/src/components/Utils/HomepageRedirectComponent.vue
@@ -0,0 +1,15 @@
+<template>
+  <div>a</div>
+</template>
+
+<script lang="ts">
+import { Component, Vue } from "vue-property-decorator";
+import RouteName from "@/router/name";
+
+@Component
+export default class HomepageRedirectComponent extends Vue {
+  created(): void {
+    this.$router.replace({ name: RouteName.HOME });
+  }
+}
+</script>
diff --git a/js/src/router/index.ts b/js/src/router/index.ts
index 0ca197452..14f880edc 100644
--- a/js/src/router/index.ts
+++ b/js/src/router/index.ts
@@ -13,7 +13,7 @@ import { groupsRoutes } from "./groups";
 import { discussionRoutes } from "./discussion";
 import { userRoutes } from "./user";
 import RouteName from "./name";
-import { i18n } from "@/utils/i18n";
+import { AVAILABLE_LANGUAGES, i18n } from "@/utils/i18n";
 
 Vue.use(Router);
 
@@ -183,12 +183,23 @@ export const routes = [
       announcer: { message: (): string => i18n.t("Page not found") as string },
     },
   },
-  {
-    path: "*",
-    redirect: { name: RouteName.PAGE_NOT_FOUND },
-  },
 ];
 
+for (const locale of AVAILABLE_LANGUAGES) {
+  routes.push({
+    path: `/${locale}`,
+    component: (): Promise<ImportedComponent> =>
+      import(
+        /* webpackChunkName: "HomepageRedirectComponent" */ "../components/Utils/HomepageRedirectComponent.vue"
+      ),
+  });
+}
+
+routes.push({
+  path: "*",
+  redirect: { name: RouteName.PAGE_NOT_FOUND },
+});
+
 const router = new Router({
   scrollBehavior,
   mode: "history",
diff --git a/js/src/utils/i18n.ts b/js/src/utils/i18n.ts
index 7ec7e7eba..ff4a5764c 100644
--- a/js/src/utils/i18n.ts
+++ b/js/src/utils/i18n.ts
@@ -10,6 +10,8 @@ const DEFAULT_LOCALE = "en_US";
 
 const localeInLocalStorage = getLocaleData();
 
+export const AVAILABLE_LANGUAGES = Object.keys(langs);
+
 console.debug("localeInLocalStorage", localeInLocalStorage);
 
 let language =
diff --git a/lib/web/plugs/set_locale_plug.ex b/lib/web/plugs/set_locale_plug.ex
index fbfc371ea..aa947a941 100644
--- a/lib/web/plugs/set_locale_plug.ex
+++ b/lib/web/plugs/set_locale_plug.ex
@@ -18,11 +18,13 @@ defmodule Mobilizon.Web.Plugs.SetLocalePlug do
   def call(conn, _) do
     locale =
       [
+        eventual_path_locale(conn.path_info),
         conn.assigns[:user_locale],
         conn.assigns[:detected_locale],
         default_locale(),
         "en"
       ]
+      |> Enum.filter(& &1)
       |> Enum.map(&determine_best_locale/1)
       |> Enum.filter(&supported_locale?/1)
       |> hd()
@@ -31,6 +33,15 @@ defmodule Mobilizon.Web.Plugs.SetLocalePlug do
     assign(conn, :locale, locale)
   end
 
+  defp eventual_path_locale(path_info) do
+    with [locale] <- path_info,
+         true <- supported_locale?(locale) do
+      locale
+    else
+      _ -> nil
+    end
+  end
+
   @spec supported_locale?(String.t()) :: boolean()
   defp supported_locale?(locale) do
     GettextBackend
diff --git a/lib/web/views/utils.ex b/lib/web/views/utils.ex
index ab1dad8b1..1ecb6219a 100644
--- a/lib/web/views/utils.ex
+++ b/lib/web/views/utils.ex
@@ -50,8 +50,8 @@ defmodule Mobilizon.Web.Views.Utils do
     index_content
     |> replace_meta(tags)
     |> String.replace(
-      "<html lang=\"en\" dir=\"auto\">",
-      "<html lang=\"#{locale}\" dir=\"#{get_language_direction(locale)}\">"
+      ~s(<html lang="en" dir="auto">),
+      ~s(<html lang="#{locale}" dir="#{get_language_direction(locale)}">)
     )
   end