Merge branch '379-translation-localization-vue' into 'master'

Resolve "Finish handling of internationalization in vuejs: handling translation of "translatable string""

Closes #379

See merge request Chill-Projet/chill-bundles!822
This commit is contained in:
2025-05-28 14:40:27 +00:00
22 changed files with 118 additions and 464 deletions

View File

@@ -33,6 +33,7 @@
import { mapState, mapGetters } from "vuex";
import VueMultiselect from "vue-multiselect";
import NewLocation from "./Location/NewLocation.vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import {
trans,
ACTIVITY_LOCATION,
@@ -77,15 +78,15 @@ export default {
},
methods: {
labelAccompanyingCourseLocation(value) {
return `${value.address.text} (${value.locationType.title.fr})`;
return `${value.address.text} (${localizeString(value.locationType.title)})`;
},
customLabel(value) {
return value.locationType
? value.name
? value.name === "__AccompanyingCourseLocation__"
? this.labelAccompanyingCourseLocation(value)
: `${value.name} (${value.locationType.title.fr})`
: value.locationType.title.fr
: `${value.name} (${localizeString(value.locationType.title)})`
: localizeString(value.locationType.title)
: "";
},
},

View File

@@ -44,7 +44,7 @@
:value="t"
:key="t.id"
>
{{ t.title.fr }}
{{ localizeString(t.title) }}
</option>
</select>
<label>{{
@@ -126,6 +126,7 @@ import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"
import { mapState } from "vuex";
import { getLocationTypes } from "../../api";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import {
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
@@ -270,6 +271,7 @@ export default {
this.getLocationTypesList();
},
methods: {
localizeString,
checkForm() {
let cond = true;
this.errors = [];

View File

@@ -20,7 +20,10 @@
</option>
<template v-for="t in templates" :key="t.id">
<option :value="t.id">
{{ t.name.fr || "Aucun nom défini" }}
{{
localizeString(t.name) ||
"Aucun nom défini"
}}
</option>
</template>
</select>

View File

@@ -41,7 +41,7 @@
>
<option value="" selected disabled>Zoom</option>
<option v-for="z in zoomLevels" :value="z.zoom" :key="z.id">
{{ z.label.fr }}
{{ localizeString(z.label) }}
</option>
</select>
<template v-if="pageCount > 1">
@@ -173,7 +173,7 @@
>
<option value="" selected disabled>Zoom</option>
<option v-for="z in zoomLevels" :value="z.zoom" :key="z.id">
{{ z.label.fr }}
{{ localizeString(z.label) }}
</option>
</select>
<template v-if="pageCount > 1">
@@ -395,6 +395,7 @@ import {
SIGNATURES_GO_TO_SIGNATURE_UNIQUE,
trans,
} from "translator";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
// @ts-ignore incredible but the console.log is needed
import * as PdfWorker from "pdfjs-dist/build/pdf.worker.mjs";

View File

@@ -0,0 +1,41 @@
import { TranslatableString } from "ChillMainAssets/types";
/**
* Localizes a translatable string object based on the current locale.
* A fallback logic is implemented in case no translation is available for the current locale.
*
* @param translatableString Object containing translations for different locales
* @param locale defaults to browser locale
* @returns The localized string or null if no translation is available
*/
export function localizeString(
translatableString: TranslatableString | null | undefined,
locale?: string,
): string {
if (!translatableString || Object.keys(translatableString).length === 0) {
return "";
}
const currentLocale = locale || navigator.language.split("-")[0] || "fr";
if (translatableString[currentLocale]) {
return translatableString[currentLocale];
}
// Define fallback locales
const fallbackLocales: string[] = ["fr", "en"];
for (const fallbackLocale of fallbackLocales) {
if (translatableString[fallbackLocale]) {
return translatableString[fallbackLocale];
}
}
// No fallback translation found, use the first available
const availableLocales = Object.keys(translatableString);
if (availableLocales.length > 0) {
return translatableString[availableLocales[0]];
}
return "";
}

View File

@@ -66,10 +66,7 @@ export interface UserAssociatedInterface {
id: number;
}
export interface TranslatableString {
fr?: string;
nl?: string;
}
export type TranslatableString = Record<string, string>;
export interface Postcode {
id: number;

View File

@@ -22,6 +22,7 @@
<script>
import VueMultiselect from "vue-multiselect";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "CountrySelection",
@@ -68,7 +69,7 @@ export default {
)[0];
},
transName({ name }) {
return name.fr; //TODO multilang
return localizeString(name);
},
selectCountry(value) {
//console.log('select country', value);

View File

@@ -32,7 +32,7 @@
class="chill-entity entity-social-issue"
>
<span class="badge bg-chill-l-gray text-dark">
{{ i.title.fr }}
{{ localizeString(i.title) }}
</span>
</span>
</td>
@@ -77,6 +77,7 @@
import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "MyAccompanyingCourses",
@@ -96,6 +97,7 @@ export default {
},
},
methods: {
localizeString,
getUrl(c) {
return `/fr/parcours/${c.id}`;
},

View File

@@ -26,7 +26,7 @@
>
<td>{{ $d(e.maxDate.datetime, "short") }}</td>
<td>
{{ e.evaluation.title.fr }}
{{ localizeString(e.evaluation.title) }}
</td>
<td>
<span class="chill-entity entity-social-issue">
@@ -97,6 +97,7 @@
import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
import { localizeString } from "../../lib/localizationHelper/localizationHelper";
export default {
name: "MyEvaluations",
@@ -116,6 +117,7 @@ export default {
},
},
methods: {
localizeString,
getUrl(e) {
switch (e.type) {
case "accompanying_period_work_evaluation":

View File

@@ -1,6 +1,6 @@
<template>
<template v-for="(container, index) in data.containers" :key="index">
<h4>{{ container.layer.name.fr }}</h4>
<h4>{{ localizeString(container.layer.name) }}</h4>
<ul>
<li v-for="(unit, index) in container.units" :key="index">
{{ unit.unitName }} ({{ unit.unitRefId }})
@@ -20,6 +20,7 @@ import {
getAllGeographicalUnitLayers,
} from "../../../../lib/api/address";
import { onMounted, reactive } from "vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export interface AddressDetailsGeographicalLayersProp {
address: Address;

View File

@@ -30,7 +30,7 @@
{{ address.postcode.name }}
</p>
<p v-if="null !== address.country" class="country">
{{ address.country.name.fr }}
{{ localizeString(address.country.name) }}
</p>
</div>
</template>
@@ -84,9 +84,11 @@
import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue";
import AddressDetailsButton from "ChillMainAssets/vuejs/_components/AddressDetails/AddressDetailsButton.vue";
import { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO } from "translator";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "AddressRenderBox",
methods: { localizeString },
components: {
Confidential,
AddressDetailsButton,

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { UserGroup } from "../../../types";
import { computed } from "vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
interface UserGroupRenderBoxProps {
userGroup: UserGroup;
@@ -18,7 +19,7 @@ const styles = computed<{ color: string; "background-color": string }>(() => {
<template>
<span class="badge-user-group" :style="styles">{{
userGroup.label.fr
localizeString(userGroup.label)
}}</span>
</template>

View File

@@ -2,10 +2,10 @@
<span class="chill-entity entity-user">
{{ user.label }}
<span class="user-job" v-if="user.user_job !== null"
>({{ user.user_job.label.fr }})</span
>({{ localizeString(user.user_job.label) }})</span
>
<span class="main-scope" v-if="user.main_scope !== null"
>({{ user.main_scope.name.fr }})</span
>({{ localizeString(user.main_scope.name) }})</span
>
<span
v-if="user.isAbsent"
@@ -17,8 +17,15 @@
</template>
<script>
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "UserRenderBoxBadge",
methods: {
localizeString() {
return localizeString;
},
},
props: ["user"],
};
</script>

View File

@@ -11,7 +11,7 @@
:value="s"
/>
<label class="form-check-label">
{{ s.name.fr }}
{{ localizeString(s.name) }}
</label>
</div>
</div>
@@ -24,6 +24,7 @@
<script>
import { mapState, mapGetters } from "vuex";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "Scopes",
@@ -48,6 +49,7 @@ export default {
},
},
methods: {
localizeString,
restore() {
console.log("restore");
},

View File

@@ -68,7 +68,7 @@
<!-- results which **are** attached to an objective -->
<div v-for="g in goalsPicked" :key="g.goal.id">
<div class="item-title" @click="removeGoal(g)">
<span class="removable">{{ g.goal.title.fr }}</span>
<span class="removable">{{ localizeString(g.goal.title) }}</span>
</div>
<div>
<add-result :goal="g.goal" destination="goal"></add-result>
@@ -112,7 +112,7 @@
@click="addGoal(g)"
:key="g.id"
>
<span>{{ g.title.fr }}</span>
<span>{{ localizeString(g.title) }}</span>
</li>
</ul>
</template>
@@ -157,7 +157,7 @@
@click="addEvaluation(e)"
:key="e.id"
>
<span>{{ e.title.fr }}</span>
<span>{{ localizeString(e.title) }}</span>
</li>
</ul>
</div>
@@ -460,6 +460,7 @@ import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import ListWorkflowModal from "ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const i18n = {
messages: {
@@ -661,6 +662,7 @@ export default {
},
},
methods: {
localizeString,
toggleSelect() {
this.isExpanded = !this.isExpanded;
},

View File

@@ -2,7 +2,7 @@
<div>
<a id="evaluations"></a>
<div class="item-title" :title="evaluation.id || 'no id yet'">
<span>{{ evaluation.evaluation.title.fr }}</span>
<span>{{ localizeString(evaluation.evaluation.title) }}</span>
</div>
<div class="item-url mt-3 mb-4" v-if="evaluation.evaluation.url">
@@ -69,6 +69,7 @@ import FormEvaluation from "./FormEvaluation.vue";
import Modal from "ChillMainAssets/vuejs/_components/Modal";
import ListWorkflowModal from "ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue";
import { buildLinkCreate } from "ChillMainAssets/lib/entity-workflow/api";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const i18n = {
messages: {
@@ -129,17 +130,18 @@ export default {
},
},
methods: {
localizeString,
removeEvaluation(e) {
this.$store.commit("removeEvaluation", e);
return;
},
toggleEditEvaluation(e) {
toggleEditEvaluation() {
this.$store.commit("toggleEvaluationEdit", { key: this.evaluation.key });
},
submitForm() {
this.toggleEditEvaluation();
},
goToGenerateWorkflow({ event, link, workflowName }) {
goToGenerateWorkflow({ workflowName }) {
const callback = (data) => {
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === this.evaluation.key,

View File

@@ -7,7 +7,7 @@
<ul class="list-suggest remove-items">
<li v-for="r in pickedResults" @click="removeResult(r)" :key="r.id">
<span>
{{ r.title.fr }}
{{ localizeString(r.title) }}
</span>
</li>
</ul>
@@ -50,7 +50,7 @@
@click="addResult(r)"
:key="r.id"
>
<span>{{ r.title.fr }}</span>
<span>{{ localizeString(r.title) }}</span>
</li>
</ul>
</template>
@@ -66,6 +66,8 @@
</template>
<script>
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const i18n = {
messages: {
fr: {
@@ -124,6 +126,7 @@ export default {
},
},
methods: {
localizeString,
toggleSelect() {
this.isExpanded = !this.isExpanded;
},

View File

@@ -28,7 +28,7 @@
:key="t.id"
:value="t.id"
>
{{ t.label.fr }}
{{ localizeString(t.label) }}
</option>
</select>
</div>
@@ -83,9 +83,11 @@
<script>
import CurrentHousehold from "./CurrentHousehold";
import { mapGetters, mapState } from "vuex";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "Dates",
methods: { localizeString },
components: {
CurrentHousehold,
},

View File

@@ -38,7 +38,7 @@
}"
@click="moveToPosition(conc.person.id, position.id)"
>
{{ position.label.fr }}
{{ localizeString(position.label) }}
</button>
</div>
</div>
@@ -57,6 +57,7 @@ import { mapGetters, mapState } from "vuex";
import CurrentHousehold from "./CurrentHousehold";
import PersonComment from "./PersonComment";
import PersonText from "../../_components/Entity/PersonText.vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "Positioning",
@@ -79,7 +80,7 @@ export default {
this.$store.getters.concUnpositionned.length === 0
);
},
allowHolderForConcerned: (app) => (conc) => {
allowHolderForConcerned: () => (conc) => {
console.log("allow holder for concerned", conc);
if (conc.position === null) {
return false;
@@ -89,6 +90,7 @@ export default {
},
},
methods: {
localizeString,
moveToPosition(person_id, position_id) {
this.$store.dispatch("markPosition", { person_id, position_id });
},

View File

@@ -79,7 +79,7 @@
:value="personAltNamesLabels[i]"
@input="onAltNameInput"
/>
<label :for="a.key">{{ a.labels.fr }}</label>
<label :for="a.key">{{ localizeString(a.labels) }}</label>
</div>
<!-- TODO fix placeholder if undefined
@@ -117,7 +117,7 @@
{{ $t("person.civility.placeholder") }}
</option>
<option v-for="c in config.civilities" :value="c.id" :key="c.id">
{{ c.name.fr }}
{{ localizeString(c.name) }}
</option>
</select>
<label>{{ $t("person.civility.title") }}</label>
@@ -219,6 +219,7 @@ import {
} from "../../_api/OnTheFly";
import PersonRenderBox from "../Entity/PersonRenderBox.vue";
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "OnTheFlyPerson",
@@ -427,7 +428,8 @@ export default {
}
},
methods: {
checkErrors(e) {
localizeString,
checkErrors() {
this.errors = [];
if (this.person.lastName === "") {
this.errors.push("Le nom ne doit pas être vide.");
@@ -445,7 +447,7 @@ export default {
loadData() {
getPerson(this.id).then(
(person) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.person = person;
//console.log('get person', this.person);
resolve();

View File

@@ -102,7 +102,7 @@
:key="civility.id"
:value="civility"
>
{{ civility.name.fr }}
{{ localizeString(civility.name) }}
</option>
</select>
</div>
@@ -257,6 +257,7 @@ import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress";
import { getThirdparty } from "../../_api/OnTheFly";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.vue";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "OnTheFlyThirdParty",
@@ -339,6 +340,7 @@ export default {
},
},
methods: {
localizeString,
loadData() {
return getThirdparty(this.id).then(
(thirdparty) =>