Skip to content

Commit fd1c13a

Browse files
committed
Refactor i18n implementation
- main improvements - all language texts can now be edited from the admin interface - in frontend code we now have autocomplete for translation ids - change in use - i18n-svelte -> lib/i18n.svelte - `$locale` -> `i18n.locale` - `$locales` -> `i18n.locales` - `$_('a.b')` -> `i18n.tr.a.b` - to add a text, add the id to lib/translations.ts - code changes - remove i18n-svelte library - remove locales/de.json: this is now included in the static folder of the deployed website, not part of the frontend source code anymore - use camel case for all translation ids for consistency and to allow use without quotes - de is now also editable in admin interface - resolves #211
1 parent 8acd15e commit fd1c13a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+793
-967
lines changed

frontend/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
"@unovis/ts": "1.5.0-beta.0",
4545
"cdigit": "^4.0.2",
4646
"iso-639-1": "3.1.3",
47-
"svelte-dnd-action": "^0.9.52",
48-
"svelte-i18n": "^4.0.1"
47+
"svelte-dnd-action": "^0.9.52"
4948
}
5049
}

frontend/pnpm-lock.yaml

-441
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<svelte:options runes={true} />
22

33
<script lang="ts">
4+
import { i18n } from "$lib/i18n.svelte";
45
import PlusOutline from "flowbite-svelte-icons/PlusOutline.svelte";
56
import Button from "flowbite-svelte/Button.svelte";
6-
import { _ } from "svelte-i18n";
77
88
let {
99
onclick,
@@ -12,5 +12,5 @@ let {
1212
</script>
1313

1414
<Button color="blue" {onclick} {disabled}
15-
><PlusOutline class="me-2 h-5 w-5" /> {$_('admin.add')}</Button
15+
><PlusOutline class="me-2 h-5 w-5" /> {i18n.tr.admin.add}</Button
1616
>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<svelte:options runes={true} />
22

33
<script lang="ts">
4+
import { i18n } from "$lib/i18n.svelte";
45
import CloseOutline from "flowbite-svelte-icons/CloseOutline.svelte";
56
import Button from "flowbite-svelte/Button.svelte";
6-
import { _ } from "svelte-i18n";
77
88
let {
99
onclick = () => {
@@ -13,5 +13,5 @@ let {
1313
</script>
1414

1515
<Button color="alternative" {onclick}
16-
><CloseOutline class="me-2 h-5 w-5" /> {$_('admin.cancel')}</Button
16+
><CloseOutline class="me-2 h-5 w-5" /> {i18n.tr.admin.cancel}</Button
1717
>
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<svelte:options runes={true} />
22

33
<script lang="ts">
4+
import { i18n } from "$lib/i18n.svelte";
45
import TrashBinOutline from "flowbite-svelte-icons/TrashBinOutline.svelte";
56
import Button from "flowbite-svelte/Button.svelte";
6-
import { _ } from "svelte-i18n";
77
88
let { onclick }: { onclick: (event: Event) => void } = $props();
99
</script>
1010

11-
<Button color="red" {onclick}><TrashBinOutline class="me-2 h-5 w-5" /> {$_('admin.delete')}</Button>
11+
<Button color="red" {onclick}><TrashBinOutline class="me-2 h-5 w-5" /> {i18n.tr.admin.delete}</Button>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<svelte:options runes={true} />
22

33
<script lang="ts">
4+
import { i18n } from "$lib/i18n.svelte";
45
import { Button, Modal } from "flowbite-svelte";
56
import ExclamationCircleOutline from "flowbite-svelte-icons/ExclamationCircleOutline.svelte";
6-
import { _ } from "svelte-i18n";
77
88
let {
99
open = $bindable(false),
@@ -15,11 +15,11 @@ let {
1515
<div class="text-center">
1616
<ExclamationCircleOutline class="mx-auto mb-4 h-12 w-12 text-gray-400 dark:text-gray-200" />
1717
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
18-
{$_('admin.delete-are-you-sure')}
18+
{i18n.tr.admin.deleteAreYouSure}
1919
</h3>
2020
<Button color="red" class="me-2" {onclick}>
21-
{$_('admin.yes-sure')}
21+
{i18n.tr.admin.yesSure}
2222
</Button>
23-
<Button color="alternative">{$_('admin.no-cancel')}</Button>
23+
<Button color="alternative">{i18n.tr.admin.noCancel}</Button>
2424
</div>
2525
</Modal>
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<svelte:options runes={true} />
22

33
<script lang="ts">
4+
import { i18n } from "$lib/i18n.svelte";
45
import EditOutline from "flowbite-svelte-icons/EditOutline.svelte";
56
import Button from "flowbite-svelte/Button.svelte";
6-
import { _ } from "svelte-i18n";
77
88
let { onclick }: { onclick: (event: Event) => void } = $props();
99
</script>
1010

11-
<Button color="yellow" {onclick}><EditOutline class="me-2 h-5 w-5" /> {$_('admin.edit')}</Button>
11+
<Button color="yellow" {onclick}><EditOutline class="me-2 h-5 w-5" /> {i18n.tr.admin.edit}</Button>

frontend/src/lib/components/Admin/EditMilestoneGroupModal.svelte

+12-7
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import {
99
updateMilestoneGroupAdmin,
1010
uploadMilestoneGroupImage,
1111
} from "$lib/client/services.gen";
12-
import type { MilestoneGroupAdmin } from "$lib/client/types.gen";
12+
import type {
13+
MilestoneGroupAdmin,
14+
MilestoneGroupText,
15+
} from "$lib/client/types.gen";
1316
import CancelButton from "$lib/components/Admin/CancelButton.svelte";
1417
import SaveButton from "$lib/components/Admin/SaveButton.svelte";
1518
import ImageFileUpload from "$lib/components/DataInput/ImageFileUpload.svelte";
19+
import { i18n } from "$lib/i18n.svelte";
1620
import {
1721
ButtonGroup,
1822
InputAddon,
@@ -21,7 +25,6 @@ import {
2125
Textarea,
2226
} from "flowbite-svelte";
2327
import { onMount } from "svelte";
24-
import { _, locales } from "svelte-i18n";
2528
2629
let {
2730
open = $bindable(false),
@@ -30,7 +33,9 @@ let {
3033
let files: FileList | undefined = $state(undefined);
3134
let image: string = $state("");
3235
33-
const textKeys = ["title", "desc"];
36+
const textKeys = ["title", "desc"] as Array<
37+
keyof typeof i18n.tr.admin & keyof MilestoneGroupText
38+
>;
3439
3540
onMount(() => {
3641
if (milestoneGroup) {
@@ -68,13 +73,13 @@ export async function saveChanges() {
6873
}
6974
</script>
7075

71-
<Modal title={$_('admin.edit')} bind:open outsideclose size="xl">
76+
<Modal title={i18n.tr.admin.edit} bind:open outsideclose size="xl">
7277
{#if milestoneGroup}
7378
{#each textKeys as textKey}
74-
{@const title = $_(`admin.${textKey}`)}
79+
{@const title = i18n.tr.admin[textKey]}
7580
<div class="mb-5">
7681
<Label class="mb-2">{title}</Label>
77-
{#each $locales as lang_id}
82+
{#each i18n.locales as lang_id}
7883
<div class="mb-1">
7984
<ButtonGroup class="w-full">
8085
<InputAddon>{lang_id}</InputAddon>
@@ -85,7 +90,7 @@ export async function saveChanges() {
8590
</div>
8691
{/each}
8792
<div class="mb-5">
88-
<Label for="img_upload" class="pb-2">{$_('admin.image')}</Label>
93+
<Label for="img_upload" class="pb-2">{i18n.tr.admin.image}</Label>
8994
<div class="flex flex-row">
9095
<img src={image} width="48" height="48" alt="MilestoneGroup" class="mx-2" />
9196
<ImageFileUpload

frontend/src/lib/components/Admin/EditMilestoneModal.svelte

+11-9
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import {
77
updateMilestone,
88
uploadMilestoneImage,
99
} from "$lib/client/services.gen";
10-
import type { MilestoneAdmin } from "$lib/client/types.gen";
10+
import type { MilestoneAdmin, MilestoneText } from "$lib/client/types.gen";
1111
import CancelButton from "$lib/components/Admin/CancelButton.svelte";
1212
import DeleteModal from "$lib/components/Admin/DeleteModal.svelte";
1313
import EditImage from "$lib/components/Admin/EditImage.svelte";
1414
import MilestoneExpectedAgeModal from "$lib/components/Admin/MilestoneExpectedAgeModal.svelte";
1515
import SaveButton from "$lib/components/Admin/SaveButton.svelte";
1616
import ImageFileUpload from "$lib/components/DataInput/ImageFileUpload.svelte";
17+
import { i18n } from "$lib/i18n.svelte";
1718
import {
1819
Button,
1920
ButtonGroup,
@@ -23,7 +24,6 @@ import {
2324
Range,
2425
Textarea,
2526
} from "flowbite-svelte";
26-
import { _, locales } from "svelte-i18n";
2727
2828
let {
2929
open = $bindable(false),
@@ -35,7 +35,9 @@ let currentMilestoneImageId: number | null = $state(null as number | null);
3535
let showDeleteMilestoneImageModal: boolean = $state(false);
3636
let showMilestoneExpectedAgeModal: boolean = $state(false);
3737
38-
const textKeys = ["title", "desc", "obs", "help"];
38+
const textKeys = ["title", "desc", "obs", "help"] as Array<
39+
keyof typeof i18n.tr.admin & keyof MilestoneText
40+
>;
3941
4042
async function saveChanges() {
4143
if (!milestone) {
@@ -75,13 +77,13 @@ async function deleteMilestoneImageAndUpdate() {
7577
}
7678
</script>
7779

78-
<Modal title={$_('admin.edit')} bind:open size="xl" outsideclose>
80+
<Modal title={i18n.tr.admin.edit} bind:open size="xl" outsideclose>
7981
{#if milestone}
8082
{#each textKeys as textKey}
81-
{@const title = $_(`admin.${textKey}`)}
83+
{@const title = i18n.tr.admin[textKey]}
8284
<div class="mb-5">
8385
<Label class="mb-2">{title}</Label>
84-
{#each $locales as lang_id}
86+
{#each i18n.locales as lang_id}
8587
<div class="mb-1">
8688
<ButtonGroup class="w-full">
8789
<InputAddon>{lang_id}</InputAddon>
@@ -92,12 +94,12 @@ async function deleteMilestoneImageAndUpdate() {
9294
</div>
9395
{/each}
9496
<div class="mb-5">
95-
<Label>{`${$_("admin.expected-age")}: ${milestone.expected_age_months}m`}</Label>
96-
<Range id="expected-age-months" min="1" max="72" bind:value={milestone.expected_age_months}/>
97+
<Label>{`${i18n.tr.admin.expectedAge}: ${milestone.expected_age_months}m`}</Label>
98+
<Range id="expectedAge-months" min="1" max="72" bind:value={milestone.expected_age_months}/>
9799
<Button onclick={() => {showMilestoneExpectedAgeModal = true;}}>View data</Button>
98100
</div>
99101
<div class="mb-5">
100-
<Label for="img_upload" class="pb-2">{$_('admin.images')}</Label>
102+
<Label for="img_upload" class="pb-2">{i18n.tr.admin.images}</Label>
101103
<div class="flex flex-row">
102104
{#each milestone.images as milestoneImage (milestoneImage.id)}
103105
<EditImage

frontend/src/lib/components/Admin/EditQuestionModal.svelte

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
import CancelButton from "$lib/components/Admin/CancelButton.svelte";
1414
import InputPreview from "$lib/components/Admin/InputPreview.svelte";
1515
import SaveButton from "$lib/components/Admin/SaveButton.svelte";
16+
import { i18n } from "$lib/i18n.svelte";
1617
import {
1718
Badge,
1819
Button,
@@ -26,7 +27,6 @@ import {
2627
type SelectOptionType,
2728
Textarea,
2829
} from "flowbite-svelte";
29-
import { _, locales } from "svelte-i18n";
3030
3131
let {
3232
open = $bindable(false),
@@ -67,7 +67,7 @@ function updateOptionsJson() {
6767
return;
6868
}
6969
const values = question.options.split(";");
70-
for (const lang_id of $locales) {
70+
for (const lang_id of i18n.locales) {
7171
const items = question.text[lang_id].options.split(";");
7272
question.text[lang_id].options_json = JSON.stringify(
7373
values.map((value, index) => ({
@@ -99,14 +99,14 @@ async function saveChanges() {
9999
<div class="flex flex-row items-center">
100100
<div class="mr-5 grow">
101101
<div class="mb-5">
102-
<Label class="mb-2">{$_("admin.question")}</Label>
102+
<Label class="mb-2">{i18n.tr.admin.question}</Label>
103103
{#each Object.values(question.text) as text}
104104
<div class="mb-1">
105105
<ButtonGroup class="w-full">
106106
<InputAddon>{text.lang_id}</InputAddon>
107107
<Input
108108
bind:value={text.question}
109-
placeholder={$_("admin.placeholder")}
109+
placeholder=""
110110
/>
111111
</ButtonGroup>
112112
</div>
@@ -164,7 +164,7 @@ async function saveChanges() {
164164
<Label class="mb-2">Preview</Label>
165165
<div class="flex flex-row">
166166
<ButtonGroup class="mb-2 mr-2">
167-
{#each $locales as lang_id}
167+
{#each i18n.locales as lang_id}
168168
<Button
169169
checked={preview_lang === lang_id}
170170
on:click={(e) => {

frontend/src/lib/components/Admin/Languages.svelte

+6-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createLanguage, deleteLanguage } from "$lib/client/services.gen";
55
import AddButton from "$lib/components/Admin/AddButton.svelte";
66
import DeleteButton from "$lib/components/Admin/DeleteButton.svelte";
77
import DeleteModal from "$lib/components/Admin/DeleteModal.svelte";
8-
import { getTranslations } from "$lib/i18n";
8+
import { i18n } from "$lib/i18n.svelte";
99
import type { SelectOptionType } from "flowbite-svelte";
1010
import {
1111
Card,
@@ -18,7 +18,6 @@ import {
1818
TableHeadCell,
1919
} from "flowbite-svelte";
2020
import ISO6391 from "iso-639-1";
21-
import { _, locales } from "svelte-i18n";
2221
2322
const langCodes = ISO6391.getAllCodes();
2423
const langNames = ISO6391.getAllNativeNames();
@@ -38,7 +37,7 @@ async function createLanguageAndUpdateLanguages() {
3837
console.log(error);
3938
} else {
4039
console.log(data);
41-
await getTranslations();
40+
await i18n.load();
4241
}
4342
}
4443
@@ -50,21 +49,21 @@ async function deleteLanguageAndUpdateLanguages() {
5049
console.log(error);
5150
} else {
5251
console.log(data);
53-
await getTranslations();
52+
await i18n.load();
5453
}
5554
}
5655
</script>
5756

5857
<Card size="xl" class="m-5">
59-
<h3 class="mb-3 text-xl font-medium text-gray-900 dark:text-white">{$_('admin.languages')}</h3>
58+
<h3 class="mb-3 text-xl font-medium text-gray-900 dark:text-white">{i18n.tr.admin.languages}</h3>
6059
<Table>
6160
<TableHead>
6261
<TableHeadCell>Code (ISO 639-1)</TableHeadCell>
6362
<TableHeadCell>Name</TableHeadCell>
64-
<TableHeadCell>{$_('admin.actions')}</TableHeadCell>
63+
<TableHeadCell>{i18n.tr.admin.actions}</TableHeadCell>
6564
</TableHead>
6665
<TableBody>
67-
{#each $locales as lang_id}
66+
{#each i18n.locales as lang_id}
6867
<TableBodyRow>
6968
<TableBodyCell>
7069
{lang_id}

frontend/src/lib/components/Admin/MilestoneExpectedAgeModal.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import { getMilestoneAgeScores } from "$lib/client/services.gen";
55
import type { MilestoneAgeScore } from "$lib/client/types.gen";
66
import PlotScoreAge from "$lib/components/DataDisplay/PlotScoreAge.svelte";
7+
import { i18n } from "$lib/i18n.svelte";
78
import { Modal } from "flowbite-svelte";
89
import { onMount } from "svelte";
9-
import { _ } from "svelte-i18n";
1010
1111
let {
1212
open = $bindable(false),
@@ -34,7 +34,7 @@ onMount(async () => {
3434
});
3535
</script>
3636

37-
<Modal title={$_('admin.expected-age-data')} bind:open size="lg" outsideclose>
37+
<Modal title={i18n.tr.admin.expectedAgeData} bind:open size="lg" outsideclose>
3838
{#if scores}
3939
<PlotScoreAge {scores} />
4040
{/if}

0 commit comments

Comments
 (0)