From e98acc976e61d130c0d691164d5a09c226033874 Mon Sep 17 00:00:00 2001
From: Ben <45583362+ben-basten@users.noreply.github.com>
Date: Tue, 22 Oct 2024 11:39:28 -0400
Subject: [PATCH] chore: move weblate translations to root directory (#13604)

* chore: move translations to root directory

* chore: update build processes to work with new i18n location
---
 .github/workflows/docker.yml                  |   1 +
 .github/workflows/test.yml                    |   1 +
 docker/docker-compose.dev.yml                 |   1 +
 {web/src/lib/i18n => i18n}/af.json            |   0
 {web/src/lib/i18n => i18n}/ar.json            |   0
 {web/src/lib/i18n => i18n}/az.json            |   0
 {web/src/lib/i18n => i18n}/be.json            |   0
 {web/src/lib/i18n => i18n}/bg.json            |   0
 {web/src/lib/i18n => i18n}/bi.json            |   0
 {web/src/lib/i18n => i18n}/ca.json            |   0
 {web/src/lib/i18n => i18n}/cs.json            |   0
 {web/src/lib/i18n => i18n}/cv.json            |   0
 {web/src/lib/i18n => i18n}/da.json            |   0
 {web/src/lib/i18n => i18n}/de.json            |   0
 {web/src/lib/i18n => i18n}/el.json            |   0
 {web/src/lib/i18n => i18n}/en.json            |   0
 {web/src/lib/i18n => i18n}/es.json            |   0
 {web/src/lib/i18n => i18n}/et.json            |   0
 {web/src/lib/i18n => i18n}/fa.json            |   0
 {web/src/lib/i18n => i18n}/fi.json            |   0
 {web/src/lib/i18n => i18n}/fr.json            |   0
 {web/src/lib/i18n => i18n}/he.json            |   0
 {web/src/lib/i18n => i18n}/hi.json            |   0
 {web/src/lib/i18n => i18n}/hr.json            |   0
 {web/src/lib/i18n => i18n}/hu.json            |   0
 {web/src/lib/i18n => i18n}/hy.json            |   0
 {web/src/lib/i18n => i18n}/id.json            |   0
 {web/src/lib/i18n => i18n}/it.json            |   0
 {web/src/lib/i18n => i18n}/ja.json            |   0
 {web/src/lib/i18n => i18n}/kmr.json           |   0
 {web/src/lib/i18n => i18n}/ko.json            |   0
 {web/src/lib/i18n => i18n}/lb.json            |   0
 {web/src/lib/i18n => i18n}/lt.json            |   0
 {web/src/lib/i18n => i18n}/lv.json            |   0
 {web/src/lib/i18n => i18n}/mfa.json           |   0
 {web/src/lib/i18n => i18n}/mk.json            |   0
 {web/src/lib/i18n => i18n}/mn.json            |   0
 {web/src/lib/i18n => i18n}/mr.json            |   0
 {web/src/lib/i18n => i18n}/ms.json            |   0
 {web/src/lib/i18n => i18n}/nb_NO.json         |   0
 {web/src/lib/i18n => i18n}/nl.json            |   0
 {web/src/lib/i18n => i18n}/pl.json            |   0
 {web/src/lib/i18n => i18n}/pt.json            |   0
 {web/src/lib/i18n => i18n}/pt_BR.json         |   0
 {web/src/lib/i18n => i18n}/ro.json            |   0
 {web/src/lib/i18n => i18n}/ru.json            |   0
 {web/src/lib/i18n => i18n}/sk.json            |   0
 {web/src/lib/i18n => i18n}/sl.json            |   0
 {web/src/lib/i18n => i18n}/sr_Cyrl.json       |   0
 {web/src/lib/i18n => i18n}/sr_Latn.json       |   0
 {web/src/lib/i18n => i18n}/sv.json            |   0
 {web/src/lib/i18n => i18n}/ta.json            |   0
 {web/src/lib/i18n => i18n}/te.json            |   0
 {web/src/lib/i18n => i18n}/th.json            |   0
 {web/src/lib/i18n => i18n}/tr.json            |   0
 {web/src/lib/i18n => i18n}/uk.json            |   0
 {web/src/lib/i18n => i18n}/vi.json            |   0
 {web/src/lib/i18n => i18n}/zh_Hant.json       |   0
 {web/src/lib/i18n => i18n}/zh_SIMPLIFIED.json |   0
 server/Dockerfile                             |   1 +
 web/src/app.d.ts                              |   2 +-
 .../album-page/__tests__/album-card.spec.ts   |   2 +-
 web/src/lib/constants.ts                      | 112 +++++++++---------
 web/src/lib/i18n.spec.ts                      |   4 +-
 web/src/lib/utils/thumbnail-util.spec.ts      |   2 +-
 web/svelte.config.js                          |   1 +
 66 files changed, 66 insertions(+), 61 deletions(-)
 rename {web/src/lib/i18n => i18n}/af.json (100%)
 rename {web/src/lib/i18n => i18n}/ar.json (100%)
 rename {web/src/lib/i18n => i18n}/az.json (100%)
 rename {web/src/lib/i18n => i18n}/be.json (100%)
 rename {web/src/lib/i18n => i18n}/bg.json (100%)
 rename {web/src/lib/i18n => i18n}/bi.json (100%)
 rename {web/src/lib/i18n => i18n}/ca.json (100%)
 rename {web/src/lib/i18n => i18n}/cs.json (100%)
 rename {web/src/lib/i18n => i18n}/cv.json (100%)
 rename {web/src/lib/i18n => i18n}/da.json (100%)
 rename {web/src/lib/i18n => i18n}/de.json (100%)
 rename {web/src/lib/i18n => i18n}/el.json (100%)
 rename {web/src/lib/i18n => i18n}/en.json (100%)
 rename {web/src/lib/i18n => i18n}/es.json (100%)
 rename {web/src/lib/i18n => i18n}/et.json (100%)
 rename {web/src/lib/i18n => i18n}/fa.json (100%)
 rename {web/src/lib/i18n => i18n}/fi.json (100%)
 rename {web/src/lib/i18n => i18n}/fr.json (100%)
 rename {web/src/lib/i18n => i18n}/he.json (100%)
 rename {web/src/lib/i18n => i18n}/hi.json (100%)
 rename {web/src/lib/i18n => i18n}/hr.json (100%)
 rename {web/src/lib/i18n => i18n}/hu.json (100%)
 rename {web/src/lib/i18n => i18n}/hy.json (100%)
 rename {web/src/lib/i18n => i18n}/id.json (100%)
 rename {web/src/lib/i18n => i18n}/it.json (100%)
 rename {web/src/lib/i18n => i18n}/ja.json (100%)
 rename {web/src/lib/i18n => i18n}/kmr.json (100%)
 rename {web/src/lib/i18n => i18n}/ko.json (100%)
 rename {web/src/lib/i18n => i18n}/lb.json (100%)
 rename {web/src/lib/i18n => i18n}/lt.json (100%)
 rename {web/src/lib/i18n => i18n}/lv.json (100%)
 rename {web/src/lib/i18n => i18n}/mfa.json (100%)
 rename {web/src/lib/i18n => i18n}/mk.json (100%)
 rename {web/src/lib/i18n => i18n}/mn.json (100%)
 rename {web/src/lib/i18n => i18n}/mr.json (100%)
 rename {web/src/lib/i18n => i18n}/ms.json (100%)
 rename {web/src/lib/i18n => i18n}/nb_NO.json (100%)
 rename {web/src/lib/i18n => i18n}/nl.json (100%)
 rename {web/src/lib/i18n => i18n}/pl.json (100%)
 rename {web/src/lib/i18n => i18n}/pt.json (100%)
 rename {web/src/lib/i18n => i18n}/pt_BR.json (100%)
 rename {web/src/lib/i18n => i18n}/ro.json (100%)
 rename {web/src/lib/i18n => i18n}/ru.json (100%)
 rename {web/src/lib/i18n => i18n}/sk.json (100%)
 rename {web/src/lib/i18n => i18n}/sl.json (100%)
 rename {web/src/lib/i18n => i18n}/sr_Cyrl.json (100%)
 rename {web/src/lib/i18n => i18n}/sr_Latn.json (100%)
 rename {web/src/lib/i18n => i18n}/sv.json (100%)
 rename {web/src/lib/i18n => i18n}/ta.json (100%)
 rename {web/src/lib/i18n => i18n}/te.json (100%)
 rename {web/src/lib/i18n => i18n}/th.json (100%)
 rename {web/src/lib/i18n => i18n}/tr.json (100%)
 rename {web/src/lib/i18n => i18n}/uk.json (100%)
 rename {web/src/lib/i18n => i18n}/vi.json (100%)
 rename {web/src/lib/i18n => i18n}/zh_Hant.json (100%)
 rename {web/src/lib/i18n => i18n}/zh_SIMPLIFIED.json (100%)

diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index b33e7d5662..034fbe0008 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -33,6 +33,7 @@ jobs:
               - 'server/**'
               - 'openapi/**'
               - 'web/**'
+              - 'i18n/**'
             machine-learning:
               - 'machine-learning/**'
 
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 064e3c2761..52e0ba7b07 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -30,6 +30,7 @@ jobs:
           filters: |
             web:
               - 'web/**'
+              - 'i18n/**'
               - 'open-api/typescript-sdk/**'
             server:
               - 'server/**'
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index 552b4a8673..f2509e0d2f 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -66,6 +66,7 @@ services:
       - 24678:24678
     volumes:
       - ../web:/usr/src/app
+      - ../i18n:/usr/src/i18n
       - ../open-api/:/usr/src/open-api/
       - /usr/src/app/node_modules
     ulimits:
diff --git a/web/src/lib/i18n/af.json b/i18n/af.json
similarity index 100%
rename from web/src/lib/i18n/af.json
rename to i18n/af.json
diff --git a/web/src/lib/i18n/ar.json b/i18n/ar.json
similarity index 100%
rename from web/src/lib/i18n/ar.json
rename to i18n/ar.json
diff --git a/web/src/lib/i18n/az.json b/i18n/az.json
similarity index 100%
rename from web/src/lib/i18n/az.json
rename to i18n/az.json
diff --git a/web/src/lib/i18n/be.json b/i18n/be.json
similarity index 100%
rename from web/src/lib/i18n/be.json
rename to i18n/be.json
diff --git a/web/src/lib/i18n/bg.json b/i18n/bg.json
similarity index 100%
rename from web/src/lib/i18n/bg.json
rename to i18n/bg.json
diff --git a/web/src/lib/i18n/bi.json b/i18n/bi.json
similarity index 100%
rename from web/src/lib/i18n/bi.json
rename to i18n/bi.json
diff --git a/web/src/lib/i18n/ca.json b/i18n/ca.json
similarity index 100%
rename from web/src/lib/i18n/ca.json
rename to i18n/ca.json
diff --git a/web/src/lib/i18n/cs.json b/i18n/cs.json
similarity index 100%
rename from web/src/lib/i18n/cs.json
rename to i18n/cs.json
diff --git a/web/src/lib/i18n/cv.json b/i18n/cv.json
similarity index 100%
rename from web/src/lib/i18n/cv.json
rename to i18n/cv.json
diff --git a/web/src/lib/i18n/da.json b/i18n/da.json
similarity index 100%
rename from web/src/lib/i18n/da.json
rename to i18n/da.json
diff --git a/web/src/lib/i18n/de.json b/i18n/de.json
similarity index 100%
rename from web/src/lib/i18n/de.json
rename to i18n/de.json
diff --git a/web/src/lib/i18n/el.json b/i18n/el.json
similarity index 100%
rename from web/src/lib/i18n/el.json
rename to i18n/el.json
diff --git a/web/src/lib/i18n/en.json b/i18n/en.json
similarity index 100%
rename from web/src/lib/i18n/en.json
rename to i18n/en.json
diff --git a/web/src/lib/i18n/es.json b/i18n/es.json
similarity index 100%
rename from web/src/lib/i18n/es.json
rename to i18n/es.json
diff --git a/web/src/lib/i18n/et.json b/i18n/et.json
similarity index 100%
rename from web/src/lib/i18n/et.json
rename to i18n/et.json
diff --git a/web/src/lib/i18n/fa.json b/i18n/fa.json
similarity index 100%
rename from web/src/lib/i18n/fa.json
rename to i18n/fa.json
diff --git a/web/src/lib/i18n/fi.json b/i18n/fi.json
similarity index 100%
rename from web/src/lib/i18n/fi.json
rename to i18n/fi.json
diff --git a/web/src/lib/i18n/fr.json b/i18n/fr.json
similarity index 100%
rename from web/src/lib/i18n/fr.json
rename to i18n/fr.json
diff --git a/web/src/lib/i18n/he.json b/i18n/he.json
similarity index 100%
rename from web/src/lib/i18n/he.json
rename to i18n/he.json
diff --git a/web/src/lib/i18n/hi.json b/i18n/hi.json
similarity index 100%
rename from web/src/lib/i18n/hi.json
rename to i18n/hi.json
diff --git a/web/src/lib/i18n/hr.json b/i18n/hr.json
similarity index 100%
rename from web/src/lib/i18n/hr.json
rename to i18n/hr.json
diff --git a/web/src/lib/i18n/hu.json b/i18n/hu.json
similarity index 100%
rename from web/src/lib/i18n/hu.json
rename to i18n/hu.json
diff --git a/web/src/lib/i18n/hy.json b/i18n/hy.json
similarity index 100%
rename from web/src/lib/i18n/hy.json
rename to i18n/hy.json
diff --git a/web/src/lib/i18n/id.json b/i18n/id.json
similarity index 100%
rename from web/src/lib/i18n/id.json
rename to i18n/id.json
diff --git a/web/src/lib/i18n/it.json b/i18n/it.json
similarity index 100%
rename from web/src/lib/i18n/it.json
rename to i18n/it.json
diff --git a/web/src/lib/i18n/ja.json b/i18n/ja.json
similarity index 100%
rename from web/src/lib/i18n/ja.json
rename to i18n/ja.json
diff --git a/web/src/lib/i18n/kmr.json b/i18n/kmr.json
similarity index 100%
rename from web/src/lib/i18n/kmr.json
rename to i18n/kmr.json
diff --git a/web/src/lib/i18n/ko.json b/i18n/ko.json
similarity index 100%
rename from web/src/lib/i18n/ko.json
rename to i18n/ko.json
diff --git a/web/src/lib/i18n/lb.json b/i18n/lb.json
similarity index 100%
rename from web/src/lib/i18n/lb.json
rename to i18n/lb.json
diff --git a/web/src/lib/i18n/lt.json b/i18n/lt.json
similarity index 100%
rename from web/src/lib/i18n/lt.json
rename to i18n/lt.json
diff --git a/web/src/lib/i18n/lv.json b/i18n/lv.json
similarity index 100%
rename from web/src/lib/i18n/lv.json
rename to i18n/lv.json
diff --git a/web/src/lib/i18n/mfa.json b/i18n/mfa.json
similarity index 100%
rename from web/src/lib/i18n/mfa.json
rename to i18n/mfa.json
diff --git a/web/src/lib/i18n/mk.json b/i18n/mk.json
similarity index 100%
rename from web/src/lib/i18n/mk.json
rename to i18n/mk.json
diff --git a/web/src/lib/i18n/mn.json b/i18n/mn.json
similarity index 100%
rename from web/src/lib/i18n/mn.json
rename to i18n/mn.json
diff --git a/web/src/lib/i18n/mr.json b/i18n/mr.json
similarity index 100%
rename from web/src/lib/i18n/mr.json
rename to i18n/mr.json
diff --git a/web/src/lib/i18n/ms.json b/i18n/ms.json
similarity index 100%
rename from web/src/lib/i18n/ms.json
rename to i18n/ms.json
diff --git a/web/src/lib/i18n/nb_NO.json b/i18n/nb_NO.json
similarity index 100%
rename from web/src/lib/i18n/nb_NO.json
rename to i18n/nb_NO.json
diff --git a/web/src/lib/i18n/nl.json b/i18n/nl.json
similarity index 100%
rename from web/src/lib/i18n/nl.json
rename to i18n/nl.json
diff --git a/web/src/lib/i18n/pl.json b/i18n/pl.json
similarity index 100%
rename from web/src/lib/i18n/pl.json
rename to i18n/pl.json
diff --git a/web/src/lib/i18n/pt.json b/i18n/pt.json
similarity index 100%
rename from web/src/lib/i18n/pt.json
rename to i18n/pt.json
diff --git a/web/src/lib/i18n/pt_BR.json b/i18n/pt_BR.json
similarity index 100%
rename from web/src/lib/i18n/pt_BR.json
rename to i18n/pt_BR.json
diff --git a/web/src/lib/i18n/ro.json b/i18n/ro.json
similarity index 100%
rename from web/src/lib/i18n/ro.json
rename to i18n/ro.json
diff --git a/web/src/lib/i18n/ru.json b/i18n/ru.json
similarity index 100%
rename from web/src/lib/i18n/ru.json
rename to i18n/ru.json
diff --git a/web/src/lib/i18n/sk.json b/i18n/sk.json
similarity index 100%
rename from web/src/lib/i18n/sk.json
rename to i18n/sk.json
diff --git a/web/src/lib/i18n/sl.json b/i18n/sl.json
similarity index 100%
rename from web/src/lib/i18n/sl.json
rename to i18n/sl.json
diff --git a/web/src/lib/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json
similarity index 100%
rename from web/src/lib/i18n/sr_Cyrl.json
rename to i18n/sr_Cyrl.json
diff --git a/web/src/lib/i18n/sr_Latn.json b/i18n/sr_Latn.json
similarity index 100%
rename from web/src/lib/i18n/sr_Latn.json
rename to i18n/sr_Latn.json
diff --git a/web/src/lib/i18n/sv.json b/i18n/sv.json
similarity index 100%
rename from web/src/lib/i18n/sv.json
rename to i18n/sv.json
diff --git a/web/src/lib/i18n/ta.json b/i18n/ta.json
similarity index 100%
rename from web/src/lib/i18n/ta.json
rename to i18n/ta.json
diff --git a/web/src/lib/i18n/te.json b/i18n/te.json
similarity index 100%
rename from web/src/lib/i18n/te.json
rename to i18n/te.json
diff --git a/web/src/lib/i18n/th.json b/i18n/th.json
similarity index 100%
rename from web/src/lib/i18n/th.json
rename to i18n/th.json
diff --git a/web/src/lib/i18n/tr.json b/i18n/tr.json
similarity index 100%
rename from web/src/lib/i18n/tr.json
rename to i18n/tr.json
diff --git a/web/src/lib/i18n/uk.json b/i18n/uk.json
similarity index 100%
rename from web/src/lib/i18n/uk.json
rename to i18n/uk.json
diff --git a/web/src/lib/i18n/vi.json b/i18n/vi.json
similarity index 100%
rename from web/src/lib/i18n/vi.json
rename to i18n/vi.json
diff --git a/web/src/lib/i18n/zh_Hant.json b/i18n/zh_Hant.json
similarity index 100%
rename from web/src/lib/i18n/zh_Hant.json
rename to i18n/zh_Hant.json
diff --git a/web/src/lib/i18n/zh_SIMPLIFIED.json b/i18n/zh_SIMPLIFIED.json
similarity index 100%
rename from web/src/lib/i18n/zh_SIMPLIFIED.json
rename to i18n/zh_SIMPLIFIED.json
diff --git a/server/Dockerfile b/server/Dockerfile
index 5b92311dc9..85fda6607e 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -37,6 +37,7 @@ WORKDIR /usr/src/app
 COPY web/package*.json web/svelte.config.js ./
 RUN npm ci
 COPY web ./
+COPY i18n ../i18n
 RUN npm run build
 
 
diff --git a/web/src/app.d.ts b/web/src/app.d.ts
index b13a0c97d5..ccec3f33d6 100644
--- a/web/src/app.d.ts
+++ b/web/src/app.d.ts
@@ -28,7 +28,7 @@ interface Element {
   requestFullscreen?(options?: FullscreenOptions): Promise<void>;
 }
 
-import type en from '$lib/i18n/en.json';
+import type en from '$lib/en.json';
 import 'svelte-i18n';
 
 type NestedKeys<T, K = keyof T> = K extends keyof T & string
diff --git a/web/src/lib/components/album-page/__tests__/album-card.spec.ts b/web/src/lib/components/album-page/__tests__/album-card.spec.ts
index 79136bca02..8da9fbfd45 100644
--- a/web/src/lib/components/album-page/__tests__/album-card.spec.ts
+++ b/web/src/lib/components/album-page/__tests__/album-card.spec.ts
@@ -12,7 +12,7 @@ describe('AlbumCard component', () => {
 
   beforeAll(async () => {
     await init({ fallbackLocale: 'en-US' });
-    register('en-US', () => import('$lib/i18n/en.json'));
+    register('en-US', () => import('$i18n/en.json'));
     await waitLocale('en-US');
   });
 
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index c94b53239b..aa1d976b6f 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -250,79 +250,79 @@ export const locales = [
   { code: 'zu-ZA', name: 'Zulu (South Africa)' },
 ];
 
-export const defaultLang = { name: 'English', code: 'en', loader: () => import('$lib/i18n/en.json') };
+export const defaultLang = { name: 'English', code: 'en', loader: () => import('$i18n/en.json') };
 
 export const langs = [
-  { name: 'Afrikaans', code: 'af', loader: () => import('$lib/i18n/af.json') },
-  { name: 'Arabic', code: 'ar', loader: () => import('$lib/i18n/ar.json') },
-  { name: 'Azerbaijani', code: 'az', loader: () => import('$lib/i18n/az.json') },
-  { name: 'Belarusian', code: 'be', loader: () => import('$lib/i18n/be.json') },
-  { name: 'Bulgarian', code: 'bg', loader: () => import('$lib/i18n/bg.json') },
-  { name: 'Bislama', code: 'bi', loader: () => import('$lib/i18n/bi.json') },
-  { name: 'Catalan', code: 'ca', loader: () => import('$lib/i18n/ca.json') },
-  { name: 'Czech', code: 'cs', loader: () => import('$lib/i18n/cs.json') },
-  { name: 'Chuvash', code: 'cv', loader: () => import('$lib/i18n/cv.json') },
-  { name: 'Danish', code: 'da', loader: () => import('$lib/i18n/da.json') },
-  { name: 'German', code: 'de', loader: () => import('$lib/i18n/de.json') },
+  { name: 'Afrikaans', code: 'af', loader: () => import('$i18n/af.json') },
+  { name: 'Arabic', code: 'ar', loader: () => import('$i18n/ar.json') },
+  { name: 'Azerbaijani', code: 'az', loader: () => import('$i18n/az.json') },
+  { name: 'Belarusian', code: 'be', loader: () => import('$i18n/be.json') },
+  { name: 'Bulgarian', code: 'bg', loader: () => import('$i18n/bg.json') },
+  { name: 'Bislama', code: 'bi', loader: () => import('$i18n/bi.json') },
+  { name: 'Catalan', code: 'ca', loader: () => import('$i18n/ca.json') },
+  { name: 'Czech', code: 'cs', loader: () => import('$i18n/cs.json') },
+  { name: 'Chuvash', code: 'cv', loader: () => import('$i18n/cv.json') },
+  { name: 'Danish', code: 'da', loader: () => import('$i18n/da.json') },
+  { name: 'German', code: 'de', loader: () => import('$i18n/de.json') },
   defaultLang,
-  { name: 'Greek', code: 'el', loader: () => import('$lib/i18n/el.json') },
-  { name: 'Spanish', code: 'es', loader: () => import('$lib/i18n/es.json') },
-  { name: 'Estonian', code: 'et', loader: () => import('$lib/i18n/et.json') },
-  { name: 'Persian', code: 'fa', loader: () => import('$lib/i18n/fa.json') },
-  { name: 'Finnish', code: 'fi', loader: () => import('$lib/i18n/fi.json') },
-  { name: 'French', code: 'fr', loader: () => import('$lib/i18n/fr.json') },
-  { name: 'Hebrew', code: 'he', loader: () => import('$lib/i18n/he.json') },
-  { name: 'Hindi', code: 'hi', loader: () => import('$lib/i18n/hi.json') },
-  { name: 'Croatian', code: 'hr', loader: () => import('$lib/i18n/hr.json') },
-  { name: 'Hungarian', code: 'hu', loader: () => import('$lib/i18n/hu.json') },
-  { name: 'Armenian', code: 'hy', loader: () => import('$lib/i18n/hy.json') },
-  { name: 'Indonesian', code: 'id', loader: () => import('$lib/i18n/id.json') },
-  { name: 'Italian', code: 'it', loader: () => import('$lib/i18n/it.json') },
-  { name: 'Japanese', code: 'ja', loader: () => import('$lib/i18n/ja.json') },
-  { name: 'Kurdish (Northern)', code: 'kmr', loader: () => import('$lib/i18n/kmr.json') },
-  { name: 'Korean', code: 'ko', loader: () => import('$lib/i18n/ko.json') },
-  { name: 'Luxembourgish', code: 'lb', loader: () => import('$lib/i18n/lb.json') },
-  { name: 'Lithuanian', code: 'lt', loader: () => import('$lib/i18n/lt.json') },
-  { name: 'Latvian', code: 'lv', loader: () => import('$lib/i18n/lv.json') },
-  { name: 'Malay (Pattani)', code: 'mfa', loader: () => import('$lib/i18n/mfa.json') },
-  { name: 'Macedonian', code: 'mk', loader: () => import('$lib/i18n/mk.json') },
-  { name: 'Mongolian', code: 'mn', loader: () => import('$lib/i18n/mn.json') },
-  { name: 'Marathi', code: 'mr', loader: () => import('$lib/i18n/mr.json') },
-  { name: 'Malay', code: 'ms', loader: () => import('$lib/i18n/ms.json') },
-  { name: 'Norwegian Bokmål', code: 'nb-NO', weblateCode: 'nb_NO', loader: () => import('$lib/i18n/nb_NO.json') },
-  { name: 'Dutch', code: 'nl', loader: () => import('$lib/i18n/nl.json') },
-  { name: 'Polish', code: 'pl', loader: () => import('$lib/i18n/pl.json') },
-  { name: 'Portuguese', code: 'pt', loader: () => import('$lib/i18n/pt.json') },
-  { name: 'Portuguese (Brazil) ', code: 'pt-BR', weblateCode: 'pt_BR', loader: () => import('$lib/i18n/pt_BR.json') },
-  { name: 'Romanian', code: 'ro', loader: () => import('$lib/i18n/ro.json') },
-  { name: 'Russian', code: 'ru', loader: () => import('$lib/i18n/ru.json') },
-  { name: 'Slovak', code: 'sk', loader: () => import('$lib/i18n/sk.json') },
-  { name: 'Slovenian', code: 'sl', loader: () => import('$lib/i18n/sl.json') },
+  { name: 'Greek', code: 'el', loader: () => import('$i18n/el.json') },
+  { name: 'Spanish', code: 'es', loader: () => import('$i18n/es.json') },
+  { name: 'Estonian', code: 'et', loader: () => import('$i18n/et.json') },
+  { name: 'Persian', code: 'fa', loader: () => import('$i18n/fa.json') },
+  { name: 'Finnish', code: 'fi', loader: () => import('$i18n/fi.json') },
+  { name: 'French', code: 'fr', loader: () => import('$i18n/fr.json') },
+  { name: 'Hebrew', code: 'he', loader: () => import('$i18n/he.json') },
+  { name: 'Hindi', code: 'hi', loader: () => import('$i18n/hi.json') },
+  { name: 'Croatian', code: 'hr', loader: () => import('$i18n/hr.json') },
+  { name: 'Hungarian', code: 'hu', loader: () => import('$i18n/hu.json') },
+  { name: 'Armenian', code: 'hy', loader: () => import('$i18n/hy.json') },
+  { name: 'Indonesian', code: 'id', loader: () => import('$i18n/id.json') },
+  { name: 'Italian', code: 'it', loader: () => import('$i18n/it.json') },
+  { name: 'Japanese', code: 'ja', loader: () => import('$i18n/ja.json') },
+  { name: 'Kurdish (Northern)', code: 'kmr', loader: () => import('$i18n/kmr.json') },
+  { name: 'Korean', code: 'ko', loader: () => import('$i18n/ko.json') },
+  { name: 'Luxembourgish', code: 'lb', loader: () => import('$i18n/lb.json') },
+  { name: 'Lithuanian', code: 'lt', loader: () => import('$i18n/lt.json') },
+  { name: 'Latvian', code: 'lv', loader: () => import('$i18n/lv.json') },
+  { name: 'Malay (Pattani)', code: 'mfa', loader: () => import('$i18n/mfa.json') },
+  { name: 'Macedonian', code: 'mk', loader: () => import('$i18n/mk.json') },
+  { name: 'Mongolian', code: 'mn', loader: () => import('$i18n/mn.json') },
+  { name: 'Marathi', code: 'mr', loader: () => import('$i18n/mr.json') },
+  { name: 'Malay', code: 'ms', loader: () => import('$i18n/ms.json') },
+  { name: 'Norwegian Bokmål', code: 'nb-NO', weblateCode: 'nb_NO', loader: () => import('$i18n/nb_NO.json') },
+  { name: 'Dutch', code: 'nl', loader: () => import('$i18n/nl.json') },
+  { name: 'Polish', code: 'pl', loader: () => import('$i18n/pl.json') },
+  { name: 'Portuguese', code: 'pt', loader: () => import('$i18n/pt.json') },
+  { name: 'Portuguese (Brazil) ', code: 'pt-BR', weblateCode: 'pt_BR', loader: () => import('$i18n/pt_BR.json') },
+  { name: 'Romanian', code: 'ro', loader: () => import('$i18n/ro.json') },
+  { name: 'Russian', code: 'ru', loader: () => import('$i18n/ru.json') },
+  { name: 'Slovak', code: 'sk', loader: () => import('$i18n/sk.json') },
+  { name: 'Slovenian', code: 'sl', loader: () => import('$i18n/sl.json') },
   {
     name: 'Serbian (Cyrillic)',
     code: 'sr-Cyrl',
     weblateCode: 'sr_Cyrl',
-    loader: () => import('$lib/i18n/sr_Cyrl.json'),
+    loader: () => import('$i18n/sr_Cyrl.json'),
   },
-  { name: 'Serbian (Latin)', code: 'sr-Latn', weblateCode: 'sr_Latn', loader: () => import('$lib/i18n/sr_Latn.json') },
-  { name: 'Swedish', code: 'sv', loader: () => import('$lib/i18n/sv.json') },
-  { name: 'Tamil', code: 'ta', loader: () => import('$lib/i18n/ta.json') },
-  { name: 'Telugu', code: 'te', loader: () => import('$lib/i18n/te.json') },
-  { name: 'Thai', code: 'th', loader: () => import('$lib/i18n/th.json') },
-  { name: 'Turkish', code: 'tr', loader: () => import('$lib/i18n/tr.json') },
-  { name: 'Ukrainian', code: 'uk', loader: () => import('$lib/i18n/uk.json') },
-  { name: 'Vietnamese', code: 'vi', loader: () => import('$lib/i18n/vi.json') },
+  { name: 'Serbian (Latin)', code: 'sr-Latn', weblateCode: 'sr_Latn', loader: () => import('$i18n/sr_Latn.json') },
+  { name: 'Swedish', code: 'sv', loader: () => import('$i18n/sv.json') },
+  { name: 'Tamil', code: 'ta', loader: () => import('$i18n/ta.json') },
+  { name: 'Telugu', code: 'te', loader: () => import('$i18n/te.json') },
+  { name: 'Thai', code: 'th', loader: () => import('$i18n/th.json') },
+  { name: 'Turkish', code: 'tr', loader: () => import('$i18n/tr.json') },
+  { name: 'Ukrainian', code: 'uk', loader: () => import('$i18n/uk.json') },
+  { name: 'Vietnamese', code: 'vi', loader: () => import('$i18n/vi.json') },
   {
     name: 'Chinese (Traditional)',
     code: 'zh-TW',
     weblateCode: 'zh_Hant',
-    loader: () => import('$lib/i18n/zh_Hant.json'),
+    loader: () => import('$i18n/zh_Hant.json'),
   },
   {
     name: 'Chinese (Simplified)',
     code: 'zh-CN',
     weblateCode: 'zh_SIMPLIFIED',
-    loader: () => import('$lib/i18n/zh_SIMPLIFIED.json'),
+    loader: () => import('$i18n/zh_SIMPLIFIED.json'),
   },
   { name: 'Development (keys only)', code: 'dev', loader: () => Promise.resolve({}) },
 ];
diff --git a/web/src/lib/i18n.spec.ts b/web/src/lib/i18n.spec.ts
index 13d926e647..63aae0419c 100644
--- a/web/src/lib/i18n.spec.ts
+++ b/web/src/lib/i18n.spec.ts
@@ -4,7 +4,7 @@ import { readFileSync, readdirSync } from 'node:fs';
 
 describe('i18n', () => {
   describe('loaders', () => {
-    const languageFiles = readdirSync('src/lib/i18n').sort();
+    const languageFiles = readdirSync('../i18n').sort();
     for (const filename of languageFiles) {
       test(`${filename} should have a loader`, async () => {
         const code = filename.replaceAll('.json', '');
@@ -17,7 +17,7 @@ describe('i18n', () => {
         // verify it loads the right file
         const module: { default?: unknown } = await item.loader();
         const translations = JSON.stringify(module.default, null, 2).trim();
-        const content = readFileSync(`src/lib/i18n/${filename}`).toString().trim();
+        const content = readFileSync(`../i18n/${filename}`).toString().trim();
         expect(translations === content, `${item.name} did not load ${filename}`).toEqual(true);
       });
     }
diff --git a/web/src/lib/utils/thumbnail-util.spec.ts b/web/src/lib/utils/thumbnail-util.spec.ts
index b4dbec8752..7fc53f662b 100644
--- a/web/src/lib/utils/thumbnail-util.spec.ts
+++ b/web/src/lib/utils/thumbnail-util.spec.ts
@@ -10,7 +10,7 @@ const fourPeople = [{ name: 'person1' }, { name: 'person2' }, { name: 'person3'
 describe('getAltText', () => {
   beforeAll(async () => {
     await init({ fallbackLocale: 'en-US' });
-    register('en-US', () => import('$lib/i18n/en.json'));
+    register('en-US', () => import('$i18n/en.json'));
     await waitLocale('en-US');
   });
 
diff --git a/web/svelte.config.js b/web/svelte.config.js
index a66f798f31..c5c93e5330 100644
--- a/web/svelte.config.js
+++ b/web/svelte.config.js
@@ -19,6 +19,7 @@ const config = {
       $lib: 'src/lib',
       '$lib/*': 'src/lib/*',
       '@test-data': 'src/test-data',
+      $i18n: '../i18n',
     },
   },
 };