From 7c28bf22deaff338642de163f6e42dabfbc7105b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20L=C3=B8vdal?= Date: Sun, 10 May 2026 18:54:22 +0200 Subject: [PATCH 01/15] npx ng add @jsverse/transloco ./bin/add-spdx-to-new-files.sh https://jsverse.gitbook.io/transloco/getting-started/installation --- package-lock.json | 185 +++++++++++++++++++++++++++++++++++- package.json | 1 + public/i18n/en.json | 1 + public/i18n/en.json.license | 3 + public/i18n/no.json | 1 + public/i18n/no.json.license | 3 + src/app/app.config.ts | 17 ++++ src/app/transloco-loader.ts | 16 ++++ transloco.config.ts | 13 +++ 9 files changed, 235 insertions(+), 5 deletions(-) create mode 100644 public/i18n/en.json create mode 100644 public/i18n/en.json.license create mode 100644 public/i18n/no.json create mode 100644 public/i18n/no.json.license create mode 100644 src/app/transloco-loader.ts create mode 100644 transloco.config.ts diff --git a/package-lock.json b/package-lock.json index 9cfbbaf..994129d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@angular/platform-browser": "^21.2.7", "@angular/platform-browser-dynamic": "^21.2.7", "@angular/router": "^21.2.7", + "@jsverse/transloco": "^8.3.0", "@ngneat/until-destroy": "^10.0.0", "@sentry/angular": "^10.50.0", "@sentry/cli": "^2.58.5", @@ -692,7 +693,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -887,7 +887,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2099,6 +2098,40 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsverse/transloco": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@jsverse/transloco/-/transloco-8.3.0.tgz", + "integrity": "sha512-p69/MhhAsTabDAffaiMALx4u9AwAqx24CjdECaCkUKSpCGbiYQUJPCsV4OsWjaSOxDNm9HuIX6NmqbfNZ0S3KA==", + "license": "MIT", + "dependencies": { + "@jsverse/transloco-utils": "^8.2.1", + "@jsverse/utils": "1.0.0-beta.5", + "tslib": "^2.2.0" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "rxjs": ">=6.0.0" + } + }, + "node_modules/@jsverse/transloco-utils": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@jsverse/transloco-utils/-/transloco-utils-8.3.0.tgz", + "integrity": "sha512-HZPDKadKiL3l4iZ51PF7gv7txBgrR7gQB6crdfLSrXsCvCTGDnnhd3y5qO02pBWbWMDKRXKU0clv2dd3jeTSFA==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jsverse/utils": { + "version": "1.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@jsverse/utils/-/utils-1.0.0-beta.5.tgz", + "integrity": "sha512-z7IdlV6BdSeF3Veii8Yyk64KuyTjNIQnFaW5PAhmDx0wN29lB2BFp8WO6+tJPLPjtlz2yKeNrjkp1XqnMPaeHA==", + "license": "MIT" + }, "node_modules/@listr2/prompt-adapter-inquirer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", @@ -4637,6 +4670,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -4860,6 +4899,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001788", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", @@ -5140,6 +5188,32 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5432,6 +5506,15 @@ "dev": true, "license": "MIT" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -6107,6 +6190,22 @@ "dev": true, "license": "MIT" }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -6144,6 +6243,12 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6279,9 +6384,20 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsdom": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", @@ -6390,6 +6506,12 @@ ], "license": "MIT" }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/listr2": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", @@ -7342,6 +7464,42 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -7467,6 +7625,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -7478,7 +7645,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -7732,6 +7898,15 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -8525,7 +8700,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 55ab50a..29419ac 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@angular/platform-browser": "^21.2.7", "@angular/platform-browser-dynamic": "^21.2.7", "@angular/router": "^21.2.7", + "@jsverse/transloco": "^8.3.0", "@ngneat/until-destroy": "^10.0.0", "@sentry/angular": "^10.50.0", "@sentry/cli": "^2.58.5", diff --git a/public/i18n/en.json b/public/i18n/en.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/i18n/en.json @@ -0,0 +1 @@ +{} diff --git a/public/i18n/en.json.license b/public/i18n/en.json.license new file mode 100644 index 0000000..667f787 --- /dev/null +++ b/public/i18n/en.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Håkon Løvdal + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/public/i18n/no.json b/public/i18n/no.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/i18n/no.json @@ -0,0 +1 @@ +{} diff --git a/public/i18n/no.json.license b/public/i18n/no.json.license new file mode 100644 index 0000000..667f787 --- /dev/null +++ b/public/i18n/no.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Håkon Løvdal + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/app/app.config.ts b/src/app/app.config.ts index c9a1b4a..d153fef 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -6,10 +6,14 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection, + isDevMode, } from "@angular/core"; import { provideRouter } from "@angular/router"; import { routes } from "./app.routes"; +import { provideHttpClient } from "@angular/common/http"; +import { TranslocoHttpLoader } from "./transloco-loader"; +import { provideTransloco } from "@jsverse/transloco"; export const appProviders = [ provideBrowserGlobalErrorListeners(), @@ -19,4 +23,17 @@ export const appProviders = [ export const appConfig: ApplicationConfig = { providers: appProviders, + providers: [ + provideHttpClient(), + provideTransloco({ + config: { + availableLangs: ["en", "no"], + defaultLang: "en", + // Remove this option if your application doesn't support changing language in runtime. + reRenderOnLangChange: true, + prodMode: !isDevMode(), + }, + loader: TranslocoHttpLoader, + }), + ], }; diff --git a/src/app/transloco-loader.ts b/src/app/transloco-loader.ts new file mode 100644 index 0000000..9db3ac8 --- /dev/null +++ b/src/app/transloco-loader.ts @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2026 Håkon Løvdal +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import { inject, Injectable } from "@angular/core"; +import { Translation, TranslocoLoader } from "@jsverse/transloco"; +import { HttpClient } from "@angular/common/http"; + +@Injectable({ providedIn: "root" }) +export class TranslocoHttpLoader implements TranslocoLoader { + private http = inject(HttpClient); + + getTranslation(lang: string) { + return this.http.get(`/i18n/${lang}.json`); + } +} diff --git a/transloco.config.ts b/transloco.config.ts new file mode 100644 index 0000000..bb161ad --- /dev/null +++ b/transloco.config.ts @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2026 Håkon Løvdal +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import { TranslocoGlobalConfig } from "@jsverse/transloco-utils"; + +const config: TranslocoGlobalConfig = { + rootTranslationsPath: "public/i18n/", + langs: ["en", "no"], + keysManager: {}, +}; + +export default config; From b0136d83ebdd505770940d8cf974613aef9c34e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20L=C3=B8vdal?= Date: Sun, 10 May 2026 19:25:07 +0200 Subject: [PATCH 02/15] Fix configuration --- public/i18n/{en.json => en_GB.json} | 0 .../{en.json.license => en_GB.json.license} | 0 public/i18n/{no.json => no_NB.json} | 0 .../{no.json.license => no_NB.json.license} | 0 public/i18n/no_NN.json | 1 + public/i18n/no_NN.json.license | 3 ++ public/i18n/no_SE.json | 1 + public/i18n/no_SE.json.license | 3 ++ src/app/app.config.ts | 30 +++++++++---------- src/app/available-langs.ts | 10 +++++++ transloco.config.ts | 4 ++- tsconfig.json | 3 ++ 12 files changed, 38 insertions(+), 17 deletions(-) rename public/i18n/{en.json => en_GB.json} (100%) rename public/i18n/{en.json.license => en_GB.json.license} (100%) rename public/i18n/{no.json => no_NB.json} (100%) rename public/i18n/{no.json.license => no_NB.json.license} (100%) create mode 100644 public/i18n/no_NN.json create mode 100644 public/i18n/no_NN.json.license create mode 100644 public/i18n/no_SE.json create mode 100644 public/i18n/no_SE.json.license create mode 100644 src/app/available-langs.ts diff --git a/public/i18n/en.json b/public/i18n/en_GB.json similarity index 100% rename from public/i18n/en.json rename to public/i18n/en_GB.json diff --git a/public/i18n/en.json.license b/public/i18n/en_GB.json.license similarity index 100% rename from public/i18n/en.json.license rename to public/i18n/en_GB.json.license diff --git a/public/i18n/no.json b/public/i18n/no_NB.json similarity index 100% rename from public/i18n/no.json rename to public/i18n/no_NB.json diff --git a/public/i18n/no.json.license b/public/i18n/no_NB.json.license similarity index 100% rename from public/i18n/no.json.license rename to public/i18n/no_NB.json.license diff --git a/public/i18n/no_NN.json b/public/i18n/no_NN.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/i18n/no_NN.json @@ -0,0 +1 @@ +{} diff --git a/public/i18n/no_NN.json.license b/public/i18n/no_NN.json.license new file mode 100644 index 0000000..667f787 --- /dev/null +++ b/public/i18n/no_NN.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Håkon Løvdal + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/public/i18n/no_SE.json b/public/i18n/no_SE.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/i18n/no_SE.json @@ -0,0 +1 @@ +{} diff --git a/public/i18n/no_SE.json.license b/public/i18n/no_SE.json.license new file mode 100644 index 0000000..667f787 --- /dev/null +++ b/public/i18n/no_SE.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Håkon Løvdal + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/app/app.config.ts b/src/app/app.config.ts index d153fef..206e265 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -4,36 +4,34 @@ import { ApplicationConfig, + isDevMode, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection, - isDevMode, } from "@angular/core"; import { provideRouter } from "@angular/router"; +import { provideHttpClient } from "@angular/common/http"; +import { provideTransloco } from "@jsverse/transloco"; import { routes } from "./app.routes"; -import { provideHttpClient } from "@angular/common/http"; import { TranslocoHttpLoader } from "./transloco-loader"; -import { provideTransloco } from "@jsverse/transloco"; +import { availableLangs } from "./available-langs"; export const appProviders = [ provideBrowserGlobalErrorListeners(), provideZonelessChangeDetection(), provideRouter(routes), + provideHttpClient(), + provideTransloco({ + config: { + availableLangs: availableLangs, + defaultLang: "no_NB", + reRenderOnLangChange: true, + prodMode: !isDevMode(), + }, + loader: TranslocoHttpLoader, + }), ]; export const appConfig: ApplicationConfig = { providers: appProviders, - providers: [ - provideHttpClient(), - provideTransloco({ - config: { - availableLangs: ["en", "no"], - defaultLang: "en", - // Remove this option if your application doesn't support changing language in runtime. - reRenderOnLangChange: true, - prodMode: !isDevMode(), - }, - loader: TranslocoHttpLoader, - }), - ], }; diff --git a/src/app/available-langs.ts b/src/app/available-langs.ts new file mode 100644 index 0000000..c725db7 --- /dev/null +++ b/src/app/available-langs.ts @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2026 Håkon Løvdal +// +// SPDX-License-Identifier: GPL-3.0-or-later + +export const availableLangs = [ + "en_GB", // English (United Kingdom) + "no_NB", // Norwegian bokmål + "no_NN", // Norwegian nynorsk + "no_SE", // Norwegian nordsamisk, davvisámegiella +]; diff --git a/transloco.config.ts b/transloco.config.ts index bb161ad..de97cef 100644 --- a/transloco.config.ts +++ b/transloco.config.ts @@ -4,9 +4,11 @@ import { TranslocoGlobalConfig } from "@jsverse/transloco-utils"; +import { availableLangs } from "./src/app/available-langs"; + const config: TranslocoGlobalConfig = { rootTranslationsPath: "public/i18n/", - langs: ["en", "no"], + langs: availableLangs, keysManager: {}, }; diff --git a/tsconfig.json b/tsconfig.json index e7fc10c..f58444e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,9 @@ "@shared/*": [ "./src/shared/*" ], + "@public/*": [ + "./public/*" + ], }, "target": "ES2022", "module": "preserve" From 3b702caf3f5b5d7fab4278dd88ce067b155f003d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20L=C3=B8vdal?= Date: Sun, 10 May 2026 22:29:42 +0200 Subject: [PATCH 03/15] npx ng generate component selector/language-selector ./bin/add-spdx-to-new-files.sh --- .../language-selector.component.html | 7 +++++ .../language-selector.component.scss | 5 ++++ .../language-selector.component.spec.ts | 26 +++++++++++++++++++ .../language-selector.component.ts | 13 ++++++++++ 4 files changed, 51 insertions(+) create mode 100644 src/app/selector/language-selector/language-selector.component.html create mode 100644 src/app/selector/language-selector/language-selector.component.scss create mode 100644 src/app/selector/language-selector/language-selector.component.spec.ts create mode 100644 src/app/selector/language-selector/language-selector.component.ts diff --git a/src/app/selector/language-selector/language-selector.component.html b/src/app/selector/language-selector/language-selector.component.html new file mode 100644 index 0000000..f87e7e9 --- /dev/null +++ b/src/app/selector/language-selector/language-selector.component.html @@ -0,0 +1,7 @@ + + +

language-selector works!

diff --git a/src/app/selector/language-selector/language-selector.component.scss b/src/app/selector/language-selector/language-selector.component.scss new file mode 100644 index 0000000..9949bdb --- /dev/null +++ b/src/app/selector/language-selector/language-selector.component.scss @@ -0,0 +1,5 @@ +/* + * SPDX-FileCopyrightText: 2026 Håkon Løvdal + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ diff --git a/src/app/selector/language-selector/language-selector.component.spec.ts b/src/app/selector/language-selector/language-selector.component.spec.ts new file mode 100644 index 0000000..99bf84e --- /dev/null +++ b/src/app/selector/language-selector/language-selector.component.spec.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2026 Håkon Løvdal +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { LanguageSelectorComponent } from "./language-selector.component"; + +describe("LanguageSelectorComponent", () => { + let component: LanguageSelectorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LanguageSelectorComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(LanguageSelectorComponent); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/selector/language-selector/language-selector.component.ts b/src/app/selector/language-selector/language-selector.component.ts new file mode 100644 index 0000000..623c6a2 --- /dev/null +++ b/src/app/selector/language-selector/language-selector.component.ts @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2026 Håkon Løvdal +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import { Component } from "@angular/core"; + +@Component({ + selector: "app-language-selector", + imports: [], + templateUrl: "./language-selector.component.html", + styleUrl: "./language-selector.component.scss", +}) +export class LanguageSelectorComponent {} From 6559c611eca1239feed2d26b8371fb9f77beded3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20L=C3=B8vdal?= Date: Sun, 10 May 2026 23:05:59 +0200 Subject: [PATCH 04/15] Add and use Language interface --- src/app/app.config.ts | 4 ++-- src/app/available-langs.ts | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 206e265..967513c 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -14,7 +14,7 @@ import { provideTransloco } from "@jsverse/transloco"; import { routes } from "./app.routes"; import { TranslocoHttpLoader } from "./transloco-loader"; -import { availableLangs } from "./available-langs"; +import { availableLangs, defaultLang } from "./available-langs"; export const appProviders = [ provideBrowserGlobalErrorListeners(), @@ -24,7 +24,7 @@ export const appProviders = [ provideTransloco({ config: { availableLangs: availableLangs, - defaultLang: "no_NB", + defaultLang: defaultLang, reRenderOnLangChange: true, prodMode: !isDevMode(), }, diff --git a/src/app/available-langs.ts b/src/app/available-langs.ts index c725db7..35a2b3c 100644 --- a/src/app/available-langs.ts +++ b/src/app/available-langs.ts @@ -2,9 +2,20 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -export const availableLangs = [ - "en_GB", // English (United Kingdom) - "no_NB", // Norwegian bokmål - "no_NN", // Norwegian nynorsk - "no_SE", // Norwegian nordsamisk, davvisámegiella +export type SupportedLanuages = "no_NB" | "no_NN" | "no_SE" | "en_GB"; + +export interface Language { + iso639_1code: SupportedLanuages; + displayName: string; +} + +export const defaultLang = "no_NB"; + +export const languages: Language[] = [ + { iso639_1code: "no_NB", displayName: "Norsk, bokmål" }, + { iso639_1code: "no_NN", displayName: "Norsk, nynorsk" }, + { iso639_1code: "no_SE", displayName: "Davvisámegiella (Nordsamisk)" }, + { iso639_1code: "en_GB", displayName: "English" }, ]; + +export const availableLangs = languages.map((lang) => lang.iso639_1code); From adaf4a8a6fcd07e8e50c9235a81ba6572ac4e25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20L=C3=B8vdal?= Date: Mon, 11 May 2026 22:57:02 +0200 Subject: [PATCH 05/15] Implement LanguageSelectorComponent --- src/app/app.component.html | 1 + src/app/app.component.spec.ts | 2 ++ src/app/app.component.ts | 5 +++ .../language-selector.component.html | 13 ++++++- .../language-selector.component.scss | 5 +++ .../language-selector.component.spec.ts | 8 ++++- .../language-selector.component.ts | 26 ++++++++++++-- src/app/transloco-testing.module.ts | 36 +++++++++++++++++++ src/shared/utils.ts | 7 ++++ 9 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/app/transloco-testing.module.ts diff --git a/src/app/app.component.html b/src/app/app.component.html index 10a2eae..261a62c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -24,6 +24,7 @@

Finn 1. mai program for der du bor