Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor i18n implementation
Browse files Browse the repository at this point in the history
- main improvements
  - de translations can now be edited from the admin interface
  - in frontend code we now have autocomplete for translation ids
- change in use
  - i18n-svelte -> lib/tr.ts
  - `$locale` -> `tr.locale`
  - `$locales` -> `tr.locales`
  - `$_('a.b')` -> `tr.d.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

TODO: relabel? tr -> i18n, .d -> .tr, i.e. `i18n.tr.a.b`
lkeegan committed Dec 16, 2024

Verified

This commit was signed with the committer’s verified signature.
lkeegan Liam Keegan
1 parent e594e1b commit 7b9c45a
Showing 51 changed files with 683 additions and 685 deletions.
3 changes: 1 addition & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -40,7 +40,6 @@
"@unovis/ts": "1.5.0-beta.0",
"cdigit": "^4.0.2",
"iso-639-1": "3.1.3",
"svelte-dnd-action": "^0.9.52",
"svelte-i18n": "^4.0.1"
"svelte-dnd-action": "^0.9.52"
}
}
4 changes: 2 additions & 2 deletions frontend/src/lib/components/Admin/AddButton.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<svelte:options runes={true} />

<script lang="ts">
import { tr } from "$lib/tr.svelte";
import PlusOutline from "flowbite-svelte-icons/PlusOutline.svelte";
import Button from "flowbite-svelte/Button.svelte";
import { _ } from "svelte-i18n";
let {
onclick,
@@ -12,5 +12,5 @@ let {
</script>

<Button color="blue" {onclick} {disabled}
><PlusOutline class="me-2 h-5 w-5" /> {$_('admin.add')}</Button
><PlusOutline class="me-2 h-5 w-5" /> {tr.d.admin.add}</Button
>
4 changes: 2 additions & 2 deletions frontend/src/lib/components/Admin/CancelButton.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<svelte:options runes={true} />

<script lang="ts">
import { tr } from "$lib/tr.svelte";
import CloseOutline from "flowbite-svelte-icons/CloseOutline.svelte";
import Button from "flowbite-svelte/Button.svelte";
import { _ } from "svelte-i18n";
let {
onclick = () => {
@@ -13,5 +13,5 @@ let {
</script>

<Button color="alternative" {onclick}
><CloseOutline class="me-2 h-5 w-5" /> {$_('admin.cancel')}</Button
><CloseOutline class="me-2 h-5 w-5" /> {tr.d.admin.cancel}</Button
>
4 changes: 2 additions & 2 deletions frontend/src/lib/components/Admin/DeleteButton.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<svelte:options runes={true} />

<script lang="ts">
import { tr } from "$lib/tr.svelte";
import TrashBinOutline from "flowbite-svelte-icons/TrashBinOutline.svelte";
import Button from "flowbite-svelte/Button.svelte";
import { _ } from "svelte-i18n";
let { onclick }: { onclick: (event: Event) => void } = $props();
</script>

<Button color="red" {onclick}><TrashBinOutline class="me-2 h-5 w-5" /> {$_('admin.delete')}</Button>
<Button color="red" {onclick}><TrashBinOutline class="me-2 h-5 w-5" /> {tr.d.admin.delete}</Button>
8 changes: 4 additions & 4 deletions frontend/src/lib/components/Admin/DeleteModal.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<svelte:options runes={true} />

<script lang="ts">
import { tr } from "$lib/tr.svelte";
import { Button, Modal } from "flowbite-svelte";
import ExclamationCircleOutline from "flowbite-svelte-icons/ExclamationCircleOutline.svelte";
import { _ } from "svelte-i18n";
let {
open = $bindable(false),
@@ -15,11 +15,11 @@ let {
<div class="text-center">
<ExclamationCircleOutline class="mx-auto mb-4 h-12 w-12 text-gray-400 dark:text-gray-200" />
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
{$_('admin.delete-are-you-sure')}
{tr.d.admin.deleteAreYouSure}
</h3>
<Button color="red" class="me-2" {onclick}>
{$_('admin.yes-sure')}
{tr.d.admin.yesSure}
</Button>
<Button color="alternative">{$_('admin.no-cancel')}</Button>
<Button color="alternative">{tr.d.admin.noCancel}</Button>
</div>
</Modal>
4 changes: 2 additions & 2 deletions frontend/src/lib/components/Admin/EditButton.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<svelte:options runes={true} />

<script lang="ts">
import { tr } from "$lib/tr.svelte";
import EditOutline from "flowbite-svelte-icons/EditOutline.svelte";
import Button from "flowbite-svelte/Button.svelte";
import { _ } from "svelte-i18n";
let { onclick }: { onclick: (event: Event) => void } = $props();
</script>

<Button color="yellow" {onclick}><EditOutline class="me-2 h-5 w-5" /> {$_('admin.edit')}</Button>
<Button color="yellow" {onclick}><EditOutline class="me-2 h-5 w-5" /> {tr.d.admin.edit}</Button>
19 changes: 12 additions & 7 deletions frontend/src/lib/components/Admin/EditMilestoneGroupModal.svelte
Original file line number Diff line number Diff line change
@@ -9,10 +9,14 @@ import {
updateMilestoneGroupAdmin,
uploadMilestoneGroupImage,
} from "$lib/client/services.gen";
import type { MilestoneGroupAdmin } from "$lib/client/types.gen";
import type {
MilestoneGroupAdmin,
MilestoneGroupText,
} from "$lib/client/types.gen";
import CancelButton from "$lib/components/Admin/CancelButton.svelte";
import SaveButton from "$lib/components/Admin/SaveButton.svelte";
import ImageFileUpload from "$lib/components/DataInput/ImageFileUpload.svelte";
import { tr } from "$lib/tr.svelte";
import {
ButtonGroup,
InputAddon,
@@ -21,7 +25,6 @@ import {
Textarea,
} from "flowbite-svelte";
import { onMount } from "svelte";
import { _, locales } from "svelte-i18n";
let {
open = $bindable(false),
@@ -30,7 +33,9 @@ let {
let files: FileList | undefined = $state(undefined);
let image: string = $state("");
const textKeys = ["title", "desc"];
const textKeys = ["title", "desc"] as Array<
keyof typeof tr.d.admin & keyof MilestoneGroupText
>;
onMount(() => {
if (milestoneGroup) {
@@ -68,13 +73,13 @@ export async function saveChanges() {
}
</script>

<Modal title={$_('admin.edit')} bind:open outsideclose size="xl">
<Modal title={tr.d.admin.edit} bind:open outsideclose size="xl">
{#if milestoneGroup}
{#each textKeys as textKey}
{@const title = $_(`admin.${textKey}`)}
{@const title = tr.d.admin[textKey]}
<div class="mb-5">
<Label class="mb-2">{title}</Label>
{#each $locales as lang_id}
{#each tr.locales as lang_id}
<div class="mb-1">
<ButtonGroup class="w-full">
<InputAddon>{lang_id}</InputAddon>
@@ -85,7 +90,7 @@ export async function saveChanges() {
</div>
{/each}
<div class="mb-5">
<Label for="img_upload" class="pb-2">{$_('admin.image')}</Label>
<Label for="img_upload" class="pb-2">{tr.d.admin.image}</Label>
<div class="flex flex-row">
<img src={image} width="48" height="48" alt="MilestoneGroup" class="mx-2" />
<ImageFileUpload
20 changes: 11 additions & 9 deletions frontend/src/lib/components/Admin/EditMilestoneModal.svelte
Original file line number Diff line number Diff line change
@@ -7,13 +7,14 @@ import {
updateMilestone,
uploadMilestoneImage,
} from "$lib/client/services.gen";
import type { MilestoneAdmin } from "$lib/client/types.gen";
import type { MilestoneAdmin, MilestoneText } from "$lib/client/types.gen";
import CancelButton from "$lib/components/Admin/CancelButton.svelte";
import DeleteModal from "$lib/components/Admin/DeleteModal.svelte";
import EditImage from "$lib/components/Admin/EditImage.svelte";
import MilestoneExpectedAgeModal from "$lib/components/Admin/MilestoneExpectedAgeModal.svelte";
import SaveButton from "$lib/components/Admin/SaveButton.svelte";
import ImageFileUpload from "$lib/components/DataInput/ImageFileUpload.svelte";
import { tr } from "$lib/tr.svelte";
import {
Button,
ButtonGroup,
@@ -23,7 +24,6 @@ import {
Range,
Textarea,
} from "flowbite-svelte";
import { _, locales } from "svelte-i18n";
let {
open = $bindable(false),
@@ -35,7 +35,9 @@ let currentMilestoneImageId: number | null = $state(null as number | null);
let showDeleteMilestoneImageModal: boolean = $state(false);
let showMilestoneExpectedAgeModal: boolean = $state(false);
const textKeys = ["title", "desc", "obs", "help"];
const textKeys = ["title", "desc", "obs", "help"] as Array<
keyof typeof tr.d.admin & keyof MilestoneText
>;
async function saveChanges() {
if (!milestone) {
@@ -75,13 +77,13 @@ async function deleteMilestoneImageAndUpdate() {
}
</script>

<Modal title={$_('admin.edit')} bind:open size="xl" outsideclose>
<Modal title={tr.d.admin.edit} bind:open size="xl" outsideclose>
{#if milestone}
{#each textKeys as textKey}
{@const title = $_(`admin.${textKey}`)}
{@const title = tr.d.admin[textKey]}
<div class="mb-5">
<Label class="mb-2">{title}</Label>
{#each $locales as lang_id}
{#each tr.locales as lang_id}
<div class="mb-1">
<ButtonGroup class="w-full">
<InputAddon>{lang_id}</InputAddon>
@@ -92,12 +94,12 @@ async function deleteMilestoneImageAndUpdate() {
</div>
{/each}
<div class="mb-5">
<Label>{`${$_("admin.expected-age")}: ${milestone.expected_age_months}m`}</Label>
<Range id="expected-age-months" min="1" max="72" bind:value={milestone.expected_age_months}/>
<Label>{`${tr.d.admin.expectedAge}: ${milestone.expected_age_months}m`}</Label>
<Range id="expectedAge-months" min="1" max="72" bind:value={milestone.expected_age_months}/>
<Button onclick={() => {showMilestoneExpectedAgeModal = true;}}>View data</Button>
</div>
<div class="mb-5">
<Label for="img_upload" class="pb-2">{$_('admin.images')}</Label>
<Label for="img_upload" class="pb-2">{tr.d.admin.images}</Label>
<div class="flex flex-row">
{#each milestone.images as milestoneImage (milestoneImage.id)}
<EditImage
10 changes: 5 additions & 5 deletions frontend/src/lib/components/Admin/EditQuestionModal.svelte
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import type {
import CancelButton from "$lib/components/Admin/CancelButton.svelte";
import InputPreview from "$lib/components/Admin/InputPreview.svelte";
import SaveButton from "$lib/components/Admin/SaveButton.svelte";
import { tr } from "$lib/tr.svelte";
import {
Badge,
Button,
@@ -26,7 +27,6 @@ import {
type SelectOptionType,
Textarea,
} from "flowbite-svelte";
import { _, locales } from "svelte-i18n";
let {
open = $bindable(false),
@@ -67,7 +67,7 @@ function updateOptionsJson() {
return;
}
const values = question.options.split(";");
for (const lang_id of $locales) {
for (const lang_id of tr.locales) {
const items = question.text[lang_id].options.split(";");
question.text[lang_id].options_json = JSON.stringify(
values.map((value, index) => ({
@@ -99,14 +99,14 @@ async function saveChanges() {
<div class="flex flex-row items-center">
<div class="mr-5 grow">
<div class="mb-5">
<Label class="mb-2">{$_("admin.question")}</Label>
<Label class="mb-2">{tr.d.admin.question}</Label>
{#each Object.values(question.text) as text}
<div class="mb-1">
<ButtonGroup class="w-full">
<InputAddon>{text.lang_id}</InputAddon>
<Input
bind:value={text.question}
placeholder={$_("admin.placeholder")}
placeholder=""
/>
</ButtonGroup>
</div>
@@ -164,7 +164,7 @@ async function saveChanges() {
<Label class="mb-2">Preview</Label>
<div class="flex flex-row">
<ButtonGroup class="mb-2 mr-2">
{#each $locales as lang_id}
{#each tr.locales as lang_id}
<Button
checked={preview_lang === lang_id}
on:click={(e) => {
13 changes: 6 additions & 7 deletions frontend/src/lib/components/Admin/Languages.svelte
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import { createLanguage, deleteLanguage } from "$lib/client/services.gen";
import AddButton from "$lib/components/Admin/AddButton.svelte";
import DeleteButton from "$lib/components/Admin/DeleteButton.svelte";
import DeleteModal from "$lib/components/Admin/DeleteModal.svelte";
import { getTranslations } from "$lib/i18n";
import { tr } from "$lib/tr.svelte";
import type { SelectOptionType } from "flowbite-svelte";
import {
Card,
@@ -18,7 +18,6 @@ import {
TableHeadCell,
} from "flowbite-svelte";
import ISO6391 from "iso-639-1";
import { _, locales } from "svelte-i18n";
const langCodes = ISO6391.getAllCodes();
const langNames = ISO6391.getAllNativeNames();
@@ -38,7 +37,7 @@ async function createLanguageAndUpdateLanguages() {
console.log(error);
} else {
console.log(data);
await getTranslations();
await tr.load();
}
}
@@ -50,21 +49,21 @@ async function deleteLanguageAndUpdateLanguages() {
console.log(error);
} else {
console.log(data);
await getTranslations();
await tr.load();
}
}
</script>

<Card size="xl" class="m-5">
<h3 class="mb-3 text-xl font-medium text-gray-900 dark:text-white">{$_('admin.languages')}</h3>
<h3 class="mb-3 text-xl font-medium text-gray-900 dark:text-white">{tr.d.admin.languages}</h3>
<Table>
<TableHead>
<TableHeadCell>Code (ISO 639-1)</TableHeadCell>
<TableHeadCell>Name</TableHeadCell>
<TableHeadCell>{$_('admin.actions')}</TableHeadCell>
<TableHeadCell>{tr.d.admin.actions}</TableHeadCell>
</TableHead>
<TableBody>
{#each $locales as lang_id}
{#each tr.locales as lang_id}
<TableBodyRow>
<TableBodyCell>
{lang_id}
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@
import { getMilestoneAgeScores } from "$lib/client/services.gen";
import type { MilestoneAgeScore } from "$lib/client/types.gen";
import PlotScoreAge from "$lib/components/DataDisplay/PlotScoreAge.svelte";
import { tr } from "$lib/tr.svelte";
import { Modal } from "flowbite-svelte";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
let {
open = $bindable(false),
@@ -34,7 +34,7 @@ onMount(async () => {
});
</script>

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

0 comments on commit 7b9c45a

Please sign in to comment.