mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch 'manage-translations' into 'master'
Create scripts to manage translations more consistently See merge request Chill-Projet/chill-bundles!723
This commit is contained in:
commit
cd99633d15
7
.changes/unreleased/Feature-20241118-150627.yaml
Normal file
7
.changes/unreleased/Feature-20241118-150627.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: "Implementation of new translation management with one source of truth for both
|
||||||
|
twig and vue component templates using YAML files. \nDuplicate translation keys
|
||||||
|
can also be detected with new command."
|
||||||
|
time: 2024-11-18T15:06:27.929549251+01:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
@ -67,10 +67,12 @@
|
|||||||
"symfony/security-guard": "^5.4",
|
"symfony/security-guard": "^5.4",
|
||||||
"symfony/security-http": "^5.4",
|
"symfony/security-http": "^5.4",
|
||||||
"symfony/serializer": "^5.4",
|
"symfony/serializer": "^5.4",
|
||||||
|
"symfony/stimulus-bundle": "^2.19",
|
||||||
"symfony/string": "^5.4",
|
"symfony/string": "^5.4",
|
||||||
"symfony/templating": "^5.4",
|
"symfony/templating": "^5.4",
|
||||||
"symfony/translation": "^5.4",
|
"symfony/translation": "^5.4",
|
||||||
"symfony/twig-bundle": "^5.4",
|
"symfony/twig-bundle": "^5.4",
|
||||||
|
"symfony/ux-translator": "^2.19",
|
||||||
"symfony/validator": "^5.4",
|
"symfony/validator": "^5.4",
|
||||||
"symfony/webpack-encore-bundle": "^1.11",
|
"symfony/webpack-encore-bundle": "^1.11",
|
||||||
"symfony/workflow": "^5.4",
|
"symfony/workflow": "^5.4",
|
||||||
|
@ -36,4 +36,6 @@ return [
|
|||||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
|
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||||
|
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
3
config/packages/ux_translator.yaml
Normal file
3
config/packages/ux_translator.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ux_translator:
|
||||||
|
# The directory where the JavaScript translations are dumped
|
||||||
|
dump_directory: '%kernel.project_dir%/var/translations'
|
31
docs/source/development/translations.rst
Normal file
31
docs/source/development/translations.rst
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Translations
|
||||||
|
*************
|
||||||
|
|
||||||
|
Translator-UX: one source of truth
|
||||||
|
==================================
|
||||||
|
|
||||||
|
The Translator-ux integration streamlines the process of managing and using translation keys dynamically in our views, whether they be in twig or vue components. The goal is to have one source of truth
|
||||||
|
for all translations and avoid having to add translation keys in the YAML files as well as in i18ns files.
|
||||||
|
|
||||||
|
To add new translation keys, you can define them in your translation YAML files. Running `symfony console cache:clear` will subsequently update the compiled translation keys which can then also be imported and
|
||||||
|
used within any vue component. For use within a twig template they can be leveraged by using the |trans function.
|
||||||
|
Within vue components you will have to import the translation keys you require and then they can be used in the template with the trans() function.
|
||||||
|
|
||||||
|
It is advisable, before adding a translation key to do a search on the existing translation keys of the translation you require. An IDE will allow you to do so easily.
|
||||||
|
However to avoid the creation of duplicate translation keys a command also exists to detect them. We also strongly advise you to use this command as explained below.
|
||||||
|
|
||||||
|
Detect duplicates command
|
||||||
|
=========================
|
||||||
|
|
||||||
|
The DetectTranslationDuplicatesCommand `chill:detect-duplicate-translations` is a Symfony console command designed to identify duplicate translations across YAML files in a project.
|
||||||
|
It checks for repeated translation values linked to different keys within a specified locale.
|
||||||
|
The command accepts two main options:
|
||||||
|
|
||||||
|
1. `--locale`: to specify the language locale to check (defaulting to 'en')
|
||||||
|
2. `--exclude-namespaces`: to list namespaces to ignore during the check.
|
||||||
|
3. [optional] `--verify-hash`: can be used to ensure that the hash of current duplicates matches a given expected value,
|
||||||
|
aiding in maintaining translation integrity.
|
||||||
|
|
||||||
|
When duplicates are detected, they are displayed in a table format, listing the repeated translations alongside the keys where they are found.
|
||||||
|
If a mismatch occurs between the computed and expected hash values, an error message is displayed to signal a potential issue in translation consistency.
|
||||||
|
This command is useful for maintaining clean and consistent translations, avoiding redundancy in your YAML files.
|
@ -13,6 +13,9 @@
|
|||||||
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
|
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
|
||||||
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
|
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
|
||||||
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
||||||
|
"@hotwired/stimulus": "^3.0.0",
|
||||||
|
"@symfony/stimulus-bridge": "^3.2.0",
|
||||||
|
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||||
"@symfony/webpack-encore": "^4.1.0",
|
"@symfony/webpack-encore": "^4.1.0",
|
||||||
"@tsconfig/node14": "^1.0.1",
|
"@tsconfig/node14": "^1.0.1",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
@ -21,6 +24,7 @@
|
|||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
"dompurify": "^3.1.0",
|
"dompurify": "^3.1.0",
|
||||||
"fork-awesome": "^1.1.7",
|
"fork-awesome": "^1.1.7",
|
||||||
|
"intl-messageformat": "^10.5.11",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"marked": "^12.0.1",
|
"marked": "^12.0.1",
|
||||||
"node-sass": "^8.0.0",
|
"node-sass": "^8.0.0",
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li class="add-persons">
|
<li class="add-persons">
|
||||||
<add-persons
|
<add-persons
|
||||||
buttonTitle="activity.add_persons"
|
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
modalTitle="activity.add_persons"
|
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
v-bind:key="addPersons.key"
|
v-bind:key="addPersons.key"
|
||||||
v-bind:options="addPersonsOptions"
|
v-bind:options="addPersonsOptions"
|
||||||
@addNewPersons="addNewPersons"
|
@addNewPersons="addNewPersons"
|
||||||
@ -40,6 +40,20 @@ import { mapState, mapGetters } from 'vuex';
|
|||||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
||||||
import PersonsBloc from './ConcernedGroups/PersonsBloc.vue';
|
import PersonsBloc from './ConcernedGroups/PersonsBloc.vue';
|
||||||
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
|
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
|
||||||
|
import {
|
||||||
|
ACTIVITY_BLOC_PERSONS,
|
||||||
|
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||||
|
ACTIVITY_BLOC_PERSONS_NOT_ASSOCIATED,
|
||||||
|
ACTIVITY_BLOC_THIRDPARTY,
|
||||||
|
ACTIVITY_BLOC_USERS,
|
||||||
|
ACTIVITY_ADD_PERSONS,
|
||||||
|
ACTIVITY_LOCATION,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
|
MULTISELECT_SELECT_LABEL,
|
||||||
|
MULTISELECT_DESELECT_LABEL,
|
||||||
|
MULTISELECT_SELECTED_LABEL,
|
||||||
|
trans,
|
||||||
|
} from "../../../../../../../../../../../assets/translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ConcernedGroups",
|
name: "ConcernedGroups",
|
||||||
@ -48,16 +62,22 @@ export default {
|
|||||||
PersonsBloc,
|
PersonsBloc,
|
||||||
PersonText
|
PersonText
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_ADD_PERSONS
|
||||||
|
};
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
personsBlocs: [
|
personsBlocs: [
|
||||||
{ key: 'persons',
|
{ key: 'persons',
|
||||||
title: 'activity.bloc_persons',
|
title: trans(ACTIVITY_BLOC_PERSONS),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: false
|
included: false
|
||||||
},
|
},
|
||||||
{ key: 'personsAssociated',
|
{ key: 'personsAssociated',
|
||||||
title: 'activity.bloc_persons_associated',
|
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
|
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
|
||||||
},
|
},
|
||||||
@ -67,12 +87,12 @@ export default {
|
|||||||
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
|
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
|
||||||
},
|
},
|
||||||
{ key: 'thirdparty',
|
{ key: 'thirdparty',
|
||||||
title: 'activity.bloc_thirdparty',
|
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity ? window.activity.activityType.thirdPartiesVisible !== 0 : true
|
included: window.activity ? window.activity.activityType.thirdPartiesVisible !== 0 : true
|
||||||
},
|
},
|
||||||
{ key: 'users',
|
{ key: 'users',
|
||||||
title: 'activity.bloc_users',
|
title: trans(ACTIVITY_BLOC_USERS),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity ? window.activity.activityType.usersVisible !== 0 : true
|
included: window.activity ? window.activity.activityType.usersVisible !== 0 : true
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<teleport to="#location">
|
<teleport to="#location">
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<label :class="locationClassList">
|
<label :class="locationClassList">
|
||||||
{{ $t("activity.location") }}
|
{{ trans(ACTIVITY_LOCATION) }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
@ -13,11 +13,11 @@
|
|||||||
open-direction="top"
|
open-direction="top"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:placeholder="$t('activity.choose_location')"
|
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||||
:custom-label="customLabel"
|
:custom-label="customLabel"
|
||||||
:select-label="$t('multiselect.select_label')"
|
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||||
:deselect-label="$t('multiselect.deselect_label')"
|
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||||
:selected-label="$t('multiselect.selected_label')"
|
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||||
:options="availableLocations"
|
:options="availableLocations"
|
||||||
group-values="locations"
|
group-values="locations"
|
||||||
group-label="locationGroup"
|
group-label="locationGroup"
|
||||||
@ -34,6 +34,14 @@
|
|||||||
import { mapState, mapGetters } from "vuex";
|
import { mapState, mapGetters } from "vuex";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
import NewLocation from "./Location/NewLocation.vue";
|
import NewLocation from "./Location/NewLocation.vue";
|
||||||
|
import {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_LOCATION,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
|
MULTISELECT_SELECT_LABEL,
|
||||||
|
MULTISELECT_DESELECT_LABEL,
|
||||||
|
MULTISELECT_SELECTED_LABEL
|
||||||
|
} from '../../../../../../../../../../../assets/translator'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Location",
|
name: "Location",
|
||||||
@ -41,6 +49,16 @@ export default {
|
|||||||
NewLocation,
|
NewLocation,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_LOCATION,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
|
MULTISELECT_SELECT_LABEL,
|
||||||
|
MULTISELECT_DESELECT_LABEL,
|
||||||
|
MULTISELECT_SELECTED_LABEL
|
||||||
|
};
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
locationClassList:
|
locationClassList:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-sm btn-create" @click="openModal">
|
<a class="btn btn-sm btn-create" @click="openModal">
|
||||||
{{ $t('activity.create_new_location') }}
|
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -14,7 +14,7 @@
|
|||||||
@close="modal.showModal = false">
|
@close="modal.showModal = false">
|
||||||
|
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
|
<h3 class="modal-title">{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}</h3>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<form>
|
<form>
|
||||||
@ -26,17 +26,17 @@
|
|||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<select class="form-select form-select-lg" id="type" required v-model="selectType">
|
<select class="form-select form-select-lg" id="type" required v-model="selectType">
|
||||||
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
|
<option selected disabled value="">{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}</option>
|
||||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
||||||
{{ t.title.fr }}
|
{{ t.title.fr }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label>{{ $t('activity.location_fields.type') }}</label>
|
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
|
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
|
||||||
<label for="name">{{ $t('activity.location_fields.name') }}</label>
|
<label for="name">{{ trans(ACTIVITY_LOCATION_FIELDS_NAME) }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<add-address
|
<add-address
|
||||||
@ -49,15 +49,15 @@
|
|||||||
|
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
|
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
|
||||||
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
|
<label for="phonenumber1">{{ trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||||
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
|
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
|
||||||
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
|
<label for="phonenumber2">{{ trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
|
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
|
||||||
<label for="email">{{ $t('activity.location_fields.email') }}</label>
|
<label for="email">{{ trans(ACTIVITY_LOCATION_FIELDS_EMAIL) }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
@ -66,7 +66,7 @@
|
|||||||
<button class="btn btn-save"
|
<button class="btn btn-save"
|
||||||
@click.prevent="saveNewLocation"
|
@click.prevent="saveNewLocation"
|
||||||
>
|
>
|
||||||
{{ $t('action.save') }}
|
{{ trans(SAVE) }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -81,6 +81,13 @@ import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"
|
|||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { getLocationTypes } from "../../api";
|
import { getLocationTypes } from "../../api";
|
||||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
|
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
|
||||||
|
import {
|
||||||
|
SAVE,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_EMAIL, ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2, ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_TYPE, ACTIVITY_CHOOSE_LOCATION_TYPE, ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
|
trans
|
||||||
|
} from "../../../../../../../../../../../../assets/translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NewLocation",
|
name: "NewLocation",
|
||||||
@ -88,6 +95,19 @@ export default {
|
|||||||
Modal,
|
Modal,
|
||||||
AddAddress,
|
AddAddress,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
SAVE,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||||
|
ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
|
};
|
||||||
|
},
|
||||||
props: ['availableLocations'],
|
props: ['availableLocations'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialIssuesClassList">{{ $t('activity.social_issues') }}</label>
|
<label :class="socialIssuesClassList">{{ trans(ACTIVITY_SOCIAL_ISSUES) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
|
|
||||||
@ -31,7 +31,7 @@
|
|||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
:show-labels="false"
|
:show-labels="false"
|
||||||
:loading="issueIsLoading"
|
:loading="issueIsLoading"
|
||||||
:placeholder="$t('activity.choose_other_social_issue')"
|
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||||
:options="socialIssuesOther"
|
:options="socialIssuesOther"
|
||||||
@select="addIssueInList">
|
@select="addIssueInList">
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialActionsClassList">{{ $t('activity.social_actions') }}</label>
|
<label :class="socialActionsClassList">{{ trans(ACTIVITY_SOCIAL_ACTIONS) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
|
|
||||||
@ -51,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span v-else-if="socialIssuesSelected.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
<span v-else-if="socialIssuesSelected.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
||||||
{{ $t('activity.select_first_a_social_issue') }}
|
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<template v-else-if="socialActionsList.length > 0">
|
<template v-else-if="socialActionsList.length > 0">
|
||||||
@ -66,7 +66,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<span v-else-if="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
<span v-else-if="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
||||||
{{ $t('activity.social_action_list_empty') }}
|
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
||||||
@ -81,6 +81,11 @@ import VueMultiselect from 'vue-multiselect';
|
|||||||
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue';
|
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue';
|
||||||
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue';
|
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue';
|
||||||
import { getSocialIssues, getSocialActionByIssue } from '../api.js';
|
import { getSocialIssues, getSocialActionByIssue } from '../api.js';
|
||||||
|
import {
|
||||||
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE, ACTIVITY_SOCIAL_ACTIONS,
|
||||||
|
ACTIVITY_SOCIAL_ISSUES, ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE, trans
|
||||||
|
} from "../../../../../../../../../../../assets/translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SocialIssuesAcc",
|
name: "SocialIssuesAcc",
|
||||||
@ -89,6 +94,16 @@ export default {
|
|||||||
CheckSocialAction,
|
CheckSocialAction,
|
||||||
VueMultiselect
|
VueMultiselect
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||||
|
ACTIVITY_SOCIAL_ACTIONS,
|
||||||
|
ACTIVITY_SOCIAL_ISSUES,
|
||||||
|
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE
|
||||||
|
};
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
issueIsLoading: false,
|
issueIsLoading: false,
|
||||||
|
@ -101,6 +101,31 @@ activity:
|
|||||||
Insert a document: Insérer un document
|
Insert a document: Insérer un document
|
||||||
Remove a document: Supprimer le document
|
Remove a document: Supprimer le document
|
||||||
comment: Commentaire
|
comment: Commentaire
|
||||||
|
errors: Le formulaire contient des erreurs
|
||||||
|
social_issues: Problématiques sociales
|
||||||
|
choose_other_social_issue: Ajouter une autre problématique sociale...
|
||||||
|
social_actions: Actions d'accompagnement
|
||||||
|
select_first_a_social_issue: Sélectionnez d'abord une problématique sociale
|
||||||
|
social_action_list_empty: Aucune action sociale disponible
|
||||||
|
add_persons: Ajouter des personnes concernées
|
||||||
|
bloc_persons: Usagers
|
||||||
|
bloc_persons_associated: Usagers du parcours
|
||||||
|
bloc_persons_not_associated: Tiers non-pro.
|
||||||
|
bloc_thirdparty: Tiers professionnels
|
||||||
|
bloc_users: T(M)S
|
||||||
|
location: Localisation
|
||||||
|
choose_location: Choisissez une localisation
|
||||||
|
choose_location_type: Choisissez un type de localisation
|
||||||
|
create_new_location: Créer une nouvelle localisation
|
||||||
|
location_fields:
|
||||||
|
name: Nom
|
||||||
|
type: Type
|
||||||
|
phonenumber1: Téléphone
|
||||||
|
phonenumber2: Autre téléphone
|
||||||
|
email: Adresse courriel
|
||||||
|
create_address: Créer une adresse
|
||||||
|
edit_address: Modifier l'adresse
|
||||||
|
|
||||||
No documents: Aucun document
|
No documents: Aucun document
|
||||||
|
|
||||||
# activity filter in list page
|
# activity filter in list page
|
||||||
|
@ -123,7 +123,6 @@ Role: Rôles
|
|||||||
Role creation: Nouveau rôle
|
Role creation: Nouveau rôle
|
||||||
Role edit: Modifier un rôle
|
Role edit: Modifier un rôle
|
||||||
|
|
||||||
'': ''
|
|
||||||
xlsx: xlsx
|
xlsx: xlsx
|
||||||
ods: ods
|
ods: ods
|
||||||
csv: csv
|
csv: csv
|
||||||
|
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
|
||||||
|
class DetectTranslationDuplicatesCommand extends Command
|
||||||
|
{
|
||||||
|
protected static $defaultName = 'chill:detect-duplicate-translations';
|
||||||
|
|
||||||
|
public function __construct(private readonly Translator $translator, private readonly KernelInterface $kernel)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setDescription('Detects duplicate translations in YAML files.')
|
||||||
|
->addOption('locale', null, InputOption::VALUE_REQUIRED, 'Locale to check for duplicate translations', 'en')
|
||||||
|
->addOption('exclude-namespaces', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Namespaces to exclude from duplicate detection', [])
|
||||||
|
->addArgument('verify-hash', InputArgument::OPTIONAL, 'The expected hash to verify translation integrity');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$locale = $input->getOption('locale');
|
||||||
|
$excludedNamespaces = $input->getOption('exclude-namespaces');
|
||||||
|
$expectedHash = $input->getArgument('verify-hash');
|
||||||
|
|
||||||
|
// Loop through all bundles and get the translation directories
|
||||||
|
foreach ($this->kernel->getBundles() as $bundle) {
|
||||||
|
$bundlePath = $bundle->getPath();
|
||||||
|
$translationDir = $this->getTranslationDirectory($bundle->getName(), $bundlePath);
|
||||||
|
|
||||||
|
if (null !== $translationDir && is_dir($translationDir)) {
|
||||||
|
foreach (glob($translationDir.'/*.yaml') as $file) {
|
||||||
|
$this->translator->addResource('yaml', $file, $locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$catalogue = $this->translator->getCatalogue($locale);
|
||||||
|
|
||||||
|
$allTranslations = [];
|
||||||
|
|
||||||
|
// Iterate through each domain in the catalogue
|
||||||
|
foreach ($catalogue->all() as $domain => $translations) {
|
||||||
|
foreach ($translations as $key => $value) {
|
||||||
|
if ($this->isExcludedNamespace("{$domain}.{$key}", $excludedNamespaces)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (is_array($value)) {
|
||||||
|
$this->flattenTranslation($value, "{$domain}.{$key}", $allTranslations);
|
||||||
|
} else {
|
||||||
|
if (!isset($allTranslations[$value])) {
|
||||||
|
$allTranslations[$value] = [];
|
||||||
|
}
|
||||||
|
$allTranslations[$value][] = "{$domain}.{$key}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect values that appear in more than one key
|
||||||
|
$duplicates = array_filter($allTranslations, fn ($keys) => count($keys) > 1);
|
||||||
|
|
||||||
|
$duplicatesHash = $this->generateDuplicatesHash($duplicates);
|
||||||
|
|
||||||
|
if ('' !== $expectedHash) {
|
||||||
|
if ($duplicatesHash === $expectedHash) {
|
||||||
|
$output->writeln('<info>Translations are consistent with the expected hash.</info>');
|
||||||
|
|
||||||
|
$output->writeln("<info>Current duplicate hash: {$duplicatesHash}</info>");
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
$output->writeln('<error>Translation hash mismatch! Potential duplicate added.</error>');
|
||||||
|
$this->renderDuplicatesTable($output, $duplicates, $locale);
|
||||||
|
|
||||||
|
$output->writeln("<info>Current duplicate hash: {$duplicatesHash}</info>");
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->renderDuplicatesTable($output, $duplicates, $locale);
|
||||||
|
|
||||||
|
$output->writeln("<info>Current duplicate hash: {$duplicatesHash}</info>");
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function flattenTranslation(array $translations, string $prefix, array &$allTranslations): void
|
||||||
|
{
|
||||||
|
foreach ($translations as $key => $value) {
|
||||||
|
$fullKey = "{$prefix}.{$key}";
|
||||||
|
if (is_array($value)) {
|
||||||
|
$this->flattenTranslation($value, $fullKey, $allTranslations);
|
||||||
|
} else {
|
||||||
|
if (!isset($allTranslations[$value])) {
|
||||||
|
$allTranslations[$value] = [];
|
||||||
|
}
|
||||||
|
$allTranslations[$value][] = $fullKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTranslationDirectory(string $bundleName, string $bundlePath): ?string
|
||||||
|
{
|
||||||
|
$translationDir = $bundlePath.'/translations';
|
||||||
|
|
||||||
|
if ('ChillAsideActivityBundle' === $bundleName) {
|
||||||
|
$translationDir = $bundlePath.'/src/translations';
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_dir($translationDir) ? $translationDir : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function wrapText(string $text, int $width): string
|
||||||
|
{
|
||||||
|
return wordwrap($text, $width, "\n", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isExcludedNamespace(string $key, array $excludedNamespaces): bool
|
||||||
|
{
|
||||||
|
foreach ($excludedNamespaces as $namespace) {
|
||||||
|
if (str_starts_with($key, (string) $namespace)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateDuplicatesHash(array $duplicates): string
|
||||||
|
{
|
||||||
|
ksort($duplicates);
|
||||||
|
foreach ($duplicates as $translation => $keys) {
|
||||||
|
sort($keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash('md5', serialize($duplicates));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderDuplicatesTable(OutputInterface $output, array $duplicates, string $locale): void
|
||||||
|
{
|
||||||
|
if ([] === $duplicates) {
|
||||||
|
$output->writeln("<info>No duplicate translations found for locale '{$locale}'.</info>");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("<comment>Duplicate translations found for locale '{$locale}':</comment>");
|
||||||
|
$table = new Table($output);
|
||||||
|
$table->setHeaders(['Translation', 'Used in Keys']);
|
||||||
|
|
||||||
|
foreach ($duplicates as $translation => $keys) {
|
||||||
|
$wrappedTranslation = $this->wrapText($translation, 40);
|
||||||
|
$wrappedKeys = $this->wrapText(implode(', ', $keys), 80);
|
||||||
|
$table->addRow([$wrappedTranslation, $wrappedKeys]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->render();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { multiSelectMessages } from 'ChillMainAssets/vuejs/_js/i18n'
|
import { multiSelectMessages } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||||
|
// translations added to yaml file and used through symfony ux translator.
|
||||||
|
// these can be deleted once everything is verified to work.
|
||||||
const addressMessages = {
|
const addressMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
add_an_address_title: 'Créer une adresse',
|
add_an_address_title: 'Créer une adresse',
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<a class="nav-link" :class="{ active: isActive('person') }">
|
<a class="nav-link" :class="{ active: isActive('person') }">
|
||||||
<label for="person">
|
<label for="person">
|
||||||
<input type="radio" name="person" id="person" v-model="radioType" value="person">
|
<input type="radio" name="person" id="person" v-model="radioType" value="person">
|
||||||
{{ $t('onthefly.create.person') }}
|
{{ $trans('ONTHEFLY_CREATE_PERSON') }}
|
||||||
</label>
|
</label>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -37,59 +37,61 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import OnTheFlyPerson from 'ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue';
|
import OnTheFlyPerson from 'ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue';
|
||||||
import OnTheFlyThirdparty from 'ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue';
|
import OnTheFlyThirdparty from 'ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue';
|
||||||
|
|
||||||
export default {
|
// Define props
|
||||||
name: "OnTheFlyCreate",
|
const props = defineProps(['action', 'allowedTypes', 'query']);
|
||||||
props: ['action', 'allowedTypes', 'query'],
|
|
||||||
components: {
|
// Create a ref for type
|
||||||
OnTheFlyPerson,
|
const type = ref(null);
|
||||||
OnTheFlyThirdparty
|
|
||||||
},
|
// Computed
|
||||||
data() {
|
const radioType = computed({
|
||||||
return {
|
get: () => type.value,
|
||||||
type: null
|
set(value) {
|
||||||
|
type.value = value;
|
||||||
|
console.log('## type:', value, ', action:', props.action);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
computed: {
|
|
||||||
radioType: {
|
// Mounted
|
||||||
set(type) {
|
onMounted(() => {
|
||||||
this.type = type;
|
type.value = (props.allowedTypes.length === 1 && props.allowedTypes.includes('thirdparty'))
|
||||||
console.log('## type:', type, ', action:', this.action);
|
? 'thirdparty'
|
||||||
},
|
: 'person';
|
||||||
get() {
|
});
|
||||||
return this.type;
|
|
||||||
}
|
// Methods
|
||||||
},
|
const isActive = (tab) => {
|
||||||
},
|
return type.value === tab;
|
||||||
mounted() {
|
};
|
||||||
this.type = (this.allowedTypes.length === 1 && this.allowedTypes.includes('thirdparty')) ? 'thirdparty' : 'person'
|
|
||||||
},
|
const castDataByType = () => {
|
||||||
methods: {
|
switch (radioType.value) {
|
||||||
isActive(tab) {
|
|
||||||
return (this.type === tab) ? true : false;
|
|
||||||
},
|
|
||||||
castDataByType() {
|
|
||||||
switch (this.radioType) {
|
|
||||||
case 'person':
|
case 'person':
|
||||||
return this.$refs.castPerson.$data.person;
|
return $refs.castPerson.$data.person;
|
||||||
case 'thirdparty':
|
case 'thirdparty':
|
||||||
let data = this.$refs.castThirdparty.$data.thirdparty;
|
let data = $refs.castThirdparty.$data.thirdparty;
|
||||||
if (data.address !== undefined && data.address !== null) {
|
if (data.address !== undefined && data.address !== null) {
|
||||||
data.address = { id: data.address.address_id }
|
data.address = { id: data.address.address_id };
|
||||||
} else {
|
} else {
|
||||||
data.address = null;
|
data.address = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
default:
|
default:
|
||||||
throw Error('Invalid type of entity')
|
throw Error('Invalid type of entity');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
// Register components
|
||||||
|
defineExpose({
|
||||||
|
isActive,
|
||||||
|
castDataByType
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<span v-if="entity.type === 'person'" class="badge rounded-pill bg-person">
|
<span v-if="entity.type === 'person'" class="badge rounded-pill bg-person">
|
||||||
{{ $t('person') }}
|
{{ trans(PERSON) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="entity.type === 'thirdparty'" class="badge rounded-pill bg-thirdparty">
|
<span v-if="entity.type === 'thirdparty'" class="badge rounded-pill bg-thirdparty">
|
||||||
<template v-if="options.displayLong !== true">
|
<template v-if="options.displayLong !== true">
|
||||||
{{ $t('thirdparty.thirdparty')}}
|
{{ trans(THIRDPARTY)}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<i class="fa fa-fw fa-user" v-if="entity.kind === 'child'"></i>
|
<i class="fa fa-fw fa-user" v-if="entity.kind === 'child'"></i>
|
||||||
@ -14,41 +14,36 @@
|
|||||||
<i class="fa fa-fw fa-user-md" v-else></i>
|
<i class="fa fa-fw fa-user-md" v-else></i>
|
||||||
|
|
||||||
<template v-if="options.displayLong === true">
|
<template v-if="options.displayLong === true">
|
||||||
<span v-if="entity.kind === 'child'">{{ $t('thirdparty.child')}}</span>
|
<span v-if="entity.kind === 'child'">{{ trans(THIRDPARTY_CONTACT_OF)}}</span>
|
||||||
<span v-else-if="entity.kind === 'company'">{{ $t('thirdparty.company')}}</span>
|
<span v-else-if="entity.kind === 'company'">{{ trans(THIRDPARTY_COMPANY)}}</span>
|
||||||
<span v-else>{{ $t('thirdparty.contact')}}</span>
|
<span v-else>{{ trans(THIRDPARTY_A_CONTACT)}}</span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="entity.type === 'user'" class="badge rounded-pill bg-user">
|
<span v-if="entity.type === 'user'" class="badge rounded-pill bg-user">
|
||||||
{{ $t('user')}}
|
{{ trans(ACCEPTED_USERS)}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="entity.type === 'household'" class="badge rounded-pill bg-user">
|
<span v-if="entity.type === 'household'" class="badge rounded-pill bg-user">
|
||||||
{{ $t('household')}}
|
{{ trans(HOUSEHOLD)}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import {
|
||||||
name: "BadgeEntity",
|
trans,
|
||||||
props: ['options', 'entity'],
|
HOUSEHOLD,
|
||||||
i18n: {
|
ACCEPTED_USERS,
|
||||||
messages: {
|
THIRDPARTY_A_CONTACT,
|
||||||
fr: {
|
THIRDPARTY_COMPANY,
|
||||||
person: "Usager",
|
THIRDPARTY_CONTACT_OF,
|
||||||
thirdparty: {
|
PERSON, THIRDPARTY
|
||||||
thirdparty: "Tiers",
|
} from '../../../../../../../../../../assets/translator'
|
||||||
child: "Personne de contact",
|
|
||||||
company: "Personne morale",
|
const props = defineProps({
|
||||||
contact: "Personne physique",
|
options: Object,
|
||||||
},
|
entity: Object
|
||||||
user: 'TMS',
|
})
|
||||||
household: 'Ménage',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -9,23 +9,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref } from 'vue';
|
||||||
name: "Confidential",
|
|
||||||
data() {
|
const isBlurred = ref(true);
|
||||||
return {
|
const toggleIcon = ref('fa-eye');
|
||||||
isBlurred: true,
|
|
||||||
toggleIcon: 'fa-eye',
|
const toggleBlur = () => {
|
||||||
};
|
isBlurred.value = !isBlurred.value;
|
||||||
},
|
toggleIcon.value = isBlurred.value ? 'fa-eye' : 'fa-eye-slash';
|
||||||
methods : {
|
};
|
||||||
toggleBlur() {
|
|
||||||
console.log(this.positionBtnFar);
|
|
||||||
this.isBlurred = !this.isBlurred;
|
|
||||||
this.toggleIcon = this.isBlurred ? 'fa-eye' : 'fa-eye-slash';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang='scss'>
|
<style scoped lang='scss'>
|
||||||
|
@ -51,12 +51,12 @@
|
|||||||
<div v-if="useDatePane === true" class="address-more">
|
<div v-if="useDatePane === true" class="address-more">
|
||||||
<div v-if="address.validFrom">
|
<div v-if="address.validFrom">
|
||||||
<span class="validFrom">
|
<span class="validFrom">
|
||||||
<b>{{ $t('validFrom') }}</b>: {{ $d(address.validFrom.date) }}
|
<b>{{ trans(ADDRESS_VALID_FROM) }}</b>: {{ $d(address.validFrom.date) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="address.validTo">
|
<div v-if="address.validTo">
|
||||||
<span class="validTo">
|
<span class="validTo">
|
||||||
<b>{{ $t('validTo') }}</b>: {{ $d(address.validTo.date) }}
|
<b>{{ trans(ADDRESS_VALID_TO) }}</b>: {{ $d(address.validTo.date) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -64,48 +64,47 @@
|
|||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
|
import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
|
||||||
import AddressDetailsButton from "ChillMainAssets/vuejs/_components/AddressDetails/AddressDetailsButton.vue";
|
import AddressDetailsButton from "ChillMainAssets/vuejs/_components/AddressDetails/AddressDetailsButton.vue";
|
||||||
|
import { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO } from '../../../../../../../../../../../assets/translator'
|
||||||
|
|
||||||
export default {
|
// Props
|
||||||
name: 'AddressRenderBox',
|
const props = defineProps({
|
||||||
components: {
|
|
||||||
Confidential,
|
|
||||||
AddressDetailsButton,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
address: {
|
address: {
|
||||||
type: Object
|
type: Object,
|
||||||
},
|
},
|
||||||
isMultiline: {
|
isMultiline: {
|
||||||
default: true,
|
default: true,
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
},
|
},
|
||||||
useDatePane: {
|
useDatePane: {
|
||||||
default: false,
|
default: false,
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
},
|
},
|
||||||
showButtonDetails: {
|
showButtonDetails: {
|
||||||
default: true,
|
default: true,
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
});
|
||||||
component() {
|
|
||||||
return this.isMultiline === true ? "div" : "span";
|
// Components
|
||||||
},
|
const components = {
|
||||||
multiline() {
|
Confidential,
|
||||||
return this.isMultiline === true ? "multiline" : "";
|
AddressDetailsButton,
|
||||||
},
|
|
||||||
isConfidential() {
|
|
||||||
return this.address.confidential;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const component = computed(() => (props.isMultiline ? 'div' : 'span'));
|
||||||
|
|
||||||
|
const multiline = computed(() => (props.isMultiline ? 'multiline' : ''));
|
||||||
|
|
||||||
|
const isConfidential = computed(() => props.address?.confidential);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
p {
|
p {
|
||||||
&:after {
|
&:after {
|
||||||
|
@ -5,9 +5,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
const props = defineProps(['user'])
|
||||||
name: "UserRenderBoxBadge",
|
|
||||||
props: ['user'],
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -2,39 +2,30 @@
|
|||||||
<div class="d-grid gap-2 my-3">
|
<div class="d-grid gap-2 my-3">
|
||||||
<button class="btn btn-misc" type="button" v-if="!subscriberFinal" @click="subscribeTo('subscribe', 'final')">
|
<button class="btn btn-misc" type="button" v-if="!subscriberFinal" @click="subscribeTo('subscribe', 'final')">
|
||||||
<i class="fa fa-check fa-fw"></i>
|
<i class="fa fa-check fa-fw"></i>
|
||||||
{{ $t('subscribe_final') }}
|
{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-misc" type="button" v-if="subscriberFinal" @click="subscribeTo('unsubscribe', 'final')">
|
<button class="btn btn-misc" type="button" v-if="subscriberFinal" @click="subscribeTo('unsubscribe', 'final')">
|
||||||
<i class="fa fa-times fa-fw"></i>
|
<i class="fa fa-times fa-fw"></i>
|
||||||
{{ $t('unsubscribe_final') }}
|
{{ trans(WORKFLOW_UNSUBSCRIBE_FINAL) }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-misc" type="button" v-if="!subscriberStep" @click="subscribeTo('subscribe', 'step')">
|
<button class="btn btn-misc" type="button" v-if="!subscriberStep" @click="subscribeTo('subscribe', 'step')">
|
||||||
<i class="fa fa-check fa-fw"></i>
|
<i class="fa fa-check fa-fw"></i>
|
||||||
{{ $t('subscribe_all_steps') }}
|
{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-misc" type="button" v-if="subscriberStep" @click="subscribeTo('unsubscribe', 'step')">
|
<button class="btn btn-misc" type="button" v-if="subscriberStep" @click="subscribeTo('unsubscribe', 'step')">
|
||||||
<i class="fa fa-times fa-fw"></i>
|
<i class="fa fa-times fa-fw"></i>
|
||||||
{{ $t('unsubscribe_all_steps') }}
|
{{ trans(WORKFLOW_UNSUBSCRIBE_ALL_STEPS) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.ts';
|
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.ts';
|
||||||
|
import { defineProps, defineEmits } from 'vue';
|
||||||
|
import { trans, WORKFLOW_SUBSCRIBE_FINAL, WORKFLOW_UNSUBSCRIBE_FINAL, WORKFLOW_SUBSCRIBE_ALL_STEPS, WORKFLOW_UNSUBSCRIBE_ALL_STEPS } from '../../../../../../../../../../../assets/translator'
|
||||||
|
|
||||||
export default {
|
// props
|
||||||
name: "EntityWorkflowVueSubscriber",
|
const props = defineProps({
|
||||||
i18n: {
|
|
||||||
messages: {
|
|
||||||
fr: {
|
|
||||||
subscribe_final: "Recevoir une notification à l'étape finale",
|
|
||||||
unsubscribe_final: "Ne plus recevoir de notification à l'étape finale",
|
|
||||||
subscribe_all_steps: "Recevoir une notification à chaque étape du suivi",
|
|
||||||
unsubscribe_all_steps: "Ne plus recevoir de notification à chaque étape du suivi",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
entityWorkflowId: {
|
entityWorkflowId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
@ -47,55 +38,22 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
emits: ['subscriptionUpdated'],
|
|
||||||
methods: {
|
//methods
|
||||||
subscribeTo(step, to) {
|
const subscribeTo = (step, to) => {
|
||||||
let params = new URLSearchParams();
|
let params = new URLSearchParams();
|
||||||
params.set('subscribe', to);
|
params.set('subscribe', to);
|
||||||
|
|
||||||
const url = `/api/1.0/main/workflow/${this.entityWorkflowId}/${step}?` + params.toString();
|
const url = `/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` + params.toString();
|
||||||
|
|
||||||
makeFetch('POST', url).then(response => {
|
makeFetch('POST', url).then(response => {
|
||||||
this.$emit('subscriptionUpdated', response);
|
emit('subscriptionUpdated', response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* ALTERNATIVES
|
|
||||||
*
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="laststep">
|
|
||||||
<label class="form-check-label" for="laststep">{{ $t('subscribe_final') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="allsteps">
|
|
||||||
<label class="form-check-label" for="allsteps">{{ $t('subscribe_all_steps') }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="list-group my-3">
|
// emit
|
||||||
<label class="list-group-item">
|
const emit = defineEmits(['subscriptionUpdated'])
|
||||||
<input class="form-check-input me-1" type="checkbox" value="">
|
|
||||||
{{ $t('subscribe_final') }}
|
|
||||||
</label>
|
|
||||||
<label class="list-group-item">
|
|
||||||
<input class="form-check-input me-1" type="checkbox" value="">
|
|
||||||
{{ $t('subscribe_all_steps') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-group-vertical my-3" role="group">
|
|
||||||
<button type="button" class="btn btn-outline-primary">
|
|
||||||
<i class="fa fa-check fa-fw"></i>
|
|
||||||
{{ $t('subscribe_final') }}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-primary">
|
|
||||||
<i class="fa fa-check fa-fw"></i>
|
|
||||||
{{ $t('subscribe_all_steps') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -38,24 +38,24 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="w.isOnHoldAtCurrentStep" class="badge bg-success rounded-pill">{{ $t('on_hold') }}</span>
|
<span v-if="w.isOnHoldAtCurrentStep" class="badge bg-success rounded-pill">{{ trans(WORKFLOW_ON_HOLD) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
<div class="item-col flex-grow-1">
|
<div class="item-col flex-grow-1">
|
||||||
<p v-if="isUserSubscribedToStep(w)">
|
<p v-if="isUserSubscribedToStep(w)">
|
||||||
<i class="fa fa-check fa-fw"></i>
|
<i class="fa fa-check fa-fw"></i>
|
||||||
{{ $t('you_subscribed_to_all_steps') }}
|
{{ trans(WORKFLOW_YOU_DESCRIBED_TO_ALL_STEPS) }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="isUserSubscribedToFinal(w)">
|
<p v-if="isUserSubscribedToFinal(w)">
|
||||||
<i class="fa fa-check fa-fw"></i>
|
<i class="fa fa-check fa-fw"></i>
|
||||||
{{ $t('you_subscribed_to_final_step') }}
|
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a :href="goToUrl(w)" class="btn btn-sm btn-show" :title="$t('action.show')"></a>
|
<a :href="goToUrl(w)" class="btn btn-sm btn-show" :title="trans(SEE)"></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -65,76 +65,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import Popover from 'bootstrap/js/src/popover';
|
import Popover from 'bootstrap/js/src/popover';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { trans, SEE, BY_USER, WORKFLOW_AT, WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS, WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP, WORKFLOW_ON_HOLD } from '../../../../../../../../../../../assets/translator'
|
||||||
|
|
||||||
const i18n = {
|
// props
|
||||||
messages: {
|
const props = defineProps({
|
||||||
fr: {
|
|
||||||
you_subscribed_to_all_steps: "Vous recevrez une notification à chaque étape",
|
|
||||||
you_subscribed_to_final_step: "Vous recevrez une notification à l'étape finale",
|
|
||||||
by: "Par",
|
|
||||||
at: "Le",
|
|
||||||
on_hold: "En attente"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ListWorkflow",
|
|
||||||
i18n: i18n,
|
|
||||||
props: {
|
|
||||||
workflows: {
|
workflows: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
methods: {
|
|
||||||
goToUrl(w) {
|
// methods
|
||||||
return `/fr/main/workflow/${w.id}/show`;
|
const goToUrl = (w) => `/fr/main/workflow/${w.id}/show`;
|
||||||
},
|
const getPopTitle = (step) => {
|
||||||
getPopTitle(step) {
|
|
||||||
if (step.transitionPrevious != null) {
|
if (step.transitionPrevious != null) {
|
||||||
//console.log(step.transitionPrevious.text);
|
//console.log(step.transitionPrevious.text);
|
||||||
let freezed = step.isFreezed ? `<i class="fa fa-snowflake-o fa-sm me-1"></i>` : ``;
|
let freezed = step.isFreezed ? `<i class="fa fa-snowflake-o fa-sm me-1"></i>` : ``;
|
||||||
return `${freezed}${step.transitionPrevious.text}`;
|
return `${freezed}${step.transitionPrevious.text}`;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getPopContent(step) {
|
const getPopContent = (step) => {
|
||||||
if (step.transitionPrevious != null) {
|
if (step.transitionPrevious != null) {
|
||||||
if (step.transitionPreviousBy !== null) {
|
|
||||||
return `<ul class="small_in_title">
|
return `<ul class="small_in_title">
|
||||||
<li><span class="item-key">${i18n.messages.fr.by} : </span><b>${step.transitionPreviousBy.text}</b></li>
|
<li><span class="item-key">{{ trans(BY_USER) }} : </span><b>${step.transitionPreviousBy.text}</b></li>
|
||||||
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li>
|
<li><span class="item-key">{{ trans(WORKFLOW_AT) }} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
|
||||||
</ul>`
|
</ul>`
|
||||||
;
|
;
|
||||||
} else {
|
|
||||||
return `<ul class="small_in_title">
|
|
||||||
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li>
|
|
||||||
</ul>`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
const formatDate = (datetime) => datetime.split('T')[0] +' '+ datetime.split('T')[1].substring(0,5)
|
||||||
formatDate(datetime) {
|
const isUserSubscribedToStep = (w) => false;
|
||||||
return datetime.split('T')[0] +' '+ datetime.split('T')[1].substring(0,5)
|
const isUserSubscribedToFinal = (w) => false;
|
||||||
},
|
|
||||||
isUserSubscribedToStep(w) {
|
onMounted(() => {
|
||||||
// todo
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
isUserSubscribedToFinal(w) {
|
|
||||||
// todo
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||||
const popoverList = triggerList.map(function (el) {
|
const popoverList = triggerList.map(function (el) {
|
||||||
//console.log('popover', el)
|
|
||||||
return new Popover(el, {
|
return new Popover(el, {
|
||||||
html: true,
|
html: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<pick-workflow
|
<Pick-workflow
|
||||||
:relatedEntityClass="this.relatedEntityClass"
|
:relatedEntityClass="this.relatedEntityClass"
|
||||||
:relatedEntityId="this.relatedEntityId"
|
:relatedEntityId="this.relatedEntityId"
|
||||||
:workflowsAvailables="workflowsAvailables"
|
:workflowsAvailables="workflowsAvailables"
|
||||||
@ -8,15 +8,15 @@
|
|||||||
:countExistingWorkflows="countWorkflows"
|
:countExistingWorkflows="countWorkflows"
|
||||||
@go-to-generate-workflow="goToGenerateWorkflow"
|
@go-to-generate-workflow="goToGenerateWorkflow"
|
||||||
@click-open-list="openModal"
|
@click-open-list="openModal"
|
||||||
></pick-workflow>
|
></Pick-workflow>
|
||||||
|
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<modal v-if="modal.showModal"
|
<Modal v-if="modal.showModal"
|
||||||
:modalDialogClass="modal.modalDialogClass"
|
:modalDialogClass="modal.modalDialogClass"
|
||||||
@close="modal.showModal = false">
|
@close="modal.showModal = false">
|
||||||
|
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h2 class="modal-title">{{ $t('workflow_list') }}</h2>
|
<h2 class="modal-title">{{ trans(WORKFLOW_LIST) }}</h2>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
@ -38,24 +38,19 @@
|
|||||||
></pick-workflow>
|
></pick-workflow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</modal>
|
</Modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { ref, computed, defineProps, defineEmits } from 'vue';
|
||||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||||
import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
|
import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
|
||||||
import ListWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue';
|
import ListWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue';
|
||||||
|
import { trans, WORKFLOW_ASSOCIATED, WORKFLOW_LIST } from '../../../../../../../../../../../assets/translator'
|
||||||
|
|
||||||
export default {
|
// Define props
|
||||||
name: "ListWorkflowModal",
|
const props = defineProps({
|
||||||
components: {
|
|
||||||
Modal,
|
|
||||||
PickWorkflow,
|
|
||||||
ListWorkflowVue
|
|
||||||
},
|
|
||||||
emits: ['goToGenerateWorkflow'],
|
|
||||||
props: {
|
|
||||||
workflows: {
|
workflows: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
@ -83,44 +78,27 @@ export default {
|
|||||||
},
|
},
|
||||||
goToGenerateWorkflowPayload: {
|
goToGenerateWorkflowPayload: {
|
||||||
required: false,
|
required: false,
|
||||||
default: {}
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
data() {
|
|
||||||
return {
|
// Define emits
|
||||||
modal: {
|
const emit = defineEmits(['goToGenerateWorkflow']);
|
||||||
|
|
||||||
|
// Reactive data
|
||||||
|
const modal = ref({
|
||||||
showModal: false,
|
showModal: false,
|
||||||
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
||||||
},
|
});
|
||||||
}
|
|
||||||
},
|
// Computed properties
|
||||||
computed: {
|
const countWorkflows = computed(() => props.workflows.length);
|
||||||
countWorkflows() {
|
const hasWorkflow = computed(() => countWorkflows.value > 0);
|
||||||
return this.workflows.length;
|
|
||||||
},
|
// Methods
|
||||||
hasWorkflow() {
|
const openModal = () => modal.value.showModal = true;
|
||||||
return this.countWorkflows > 0;
|
|
||||||
}
|
const goToGenerateWorkflow = (data) => emit('goToGenerateWorkflow', data);
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
openModal() {
|
|
||||||
this.modal.showModal = true;
|
|
||||||
},
|
|
||||||
goToGenerateWorkflow(data) {
|
|
||||||
console.log('go to generate workflow intercepted', data);
|
|
||||||
this.$emit('goToGenerateWorkflow', data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
i18n: {
|
|
||||||
messages: {
|
|
||||||
fr: {
|
|
||||||
workflow_list: "Liste des workflows associés",
|
|
||||||
workflow: " workflow associé",
|
|
||||||
workflows: " workflows associés",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<slot name="body"></slot>
|
<slot name="body"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer" v-if="!hideFooter">
|
<div class="modal-footer" v-if="!hideFooter">
|
||||||
<button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
|
<button class="btn btn-cancel" @click="$emit('close')">{{ trans(MODAL_ACTION_CLOSE) }}</button>
|
||||||
<slot name="footer"></slot>
|
<slot name="footer"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -28,8 +28,7 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup>
|
||||||
import {defineComponent} from "vue";
|
|
||||||
/*
|
/*
|
||||||
* This Modal component is a mix between Vue3 modal implementation
|
* This Modal component is a mix between Vue3 modal implementation
|
||||||
* [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden
|
* [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden
|
||||||
@ -39,9 +38,11 @@ import {defineComponent} from "vue";
|
|||||||
* [+] using bootstrap css classes, the modal have a responsive behaviour,
|
* [+] using bootstrap css classes, the modal have a responsive behaviour,
|
||||||
* [+] modal design can be configured using css classes (size, scroll)
|
* [+] modal design can be configured using css classes (size, scroll)
|
||||||
*/
|
*/
|
||||||
export default defineComponent({
|
import { trans, MODAL_ACTION_CLOSE } from '../../../../../../../../../../assets/translator'
|
||||||
name: 'Modal',
|
import { defineProps, defineEmits } from 'vue';
|
||||||
props: {
|
|
||||||
|
// Define the props
|
||||||
|
const props = defineProps({
|
||||||
modalDialogClass: {
|
modalDialogClass: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@ -50,11 +51,11 @@ export default defineComponent({
|
|||||||
hideFooter: {
|
hideFooter: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: ['close']
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -1,34 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="{'btn-group btn-group-sm float-end': isButtonGroup }"
|
||||||
:class="{ 'btn-group btn-group-sm float-end': isButtonGroup }"
|
role="group" aria-label="Notification actions">
|
||||||
role="group"
|
|
||||||
aria-label="Notification actions"
|
<button v-if="isRead"
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="isRead"
|
|
||||||
class="btn"
|
class="btn"
|
||||||
:class="overrideClass"
|
:class="overrideClass"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('markAsUnread')"
|
:title="trans(NOTIFICATION_MARK_AS_UNREAD)"
|
||||||
@click="markAsUnread"
|
@click="markAsUnread"
|
||||||
>
|
>
|
||||||
<i class="fa fa-sm fa-envelope-o"></i>
|
<i class="fa fa-sm fa-envelope-o"></i>
|
||||||
<span v-if="!buttonNoText" class="ps-2">
|
<span v-if="!buttonNoText" class="ps-2">
|
||||||
{{ $t("markAsUnread") }}
|
{{ trans(NOTIFICATION_MARK_AS_UNREAD) }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button v-if="!isRead"
|
||||||
v-if="!isRead"
|
|
||||||
class="btn"
|
class="btn"
|
||||||
:class="overrideClass"
|
:class="overrideClass"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('markAsRead')"
|
:title="trans(NOTIFICATION_MARK_AS_READ)"
|
||||||
@click="markAsRead"
|
@click="markAsRead"
|
||||||
>
|
>
|
||||||
<i class="fa fa-sm fa-envelope-open-o"></i>
|
<i class="fa fa-sm fa-envelope-open-o"></i>
|
||||||
<span v-if="!buttonNoText" class="ps-2">
|
<span v-if="!buttonNoText" class="ps-2">
|
||||||
{{ $t("markAsRead") }}
|
{{ trans(NOTIFICATION_MARK_AS_READ) }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -37,7 +33,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="btn btn-outline-primary"
|
class="btn btn-outline-primary"
|
||||||
:href="showUrl"
|
:href="showUrl"
|
||||||
:title="$t('action.show')"
|
:title="trans(SEE)"
|
||||||
>
|
>
|
||||||
<i class="fa fa-sm fa-comment-o"></i>
|
<i class="fa fa-sm fa-comment-o"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -59,90 +55,65 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
|
import { ref, computed } from 'vue';
|
||||||
|
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.ts';
|
||||||
|
import { trans, NOTIFICATION_MARK_AS_READ, NOTIFICATION_MARK_AS_UNREAD, SEE } from '../../../../../../../../../../../assets/translator'
|
||||||
|
|
||||||
export default {
|
// Props
|
||||||
name: "NotificationReadToggle",
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
isRead: {
|
isRead: {
|
||||||
required: true,
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
notificationId: {
|
notificationId: {
|
||||||
required: true,
|
|
||||||
type: Number,
|
type: Number,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
// Optional
|
|
||||||
buttonClass: {
|
buttonClass: {
|
||||||
required: false,
|
|
||||||
type: String,
|
type: String,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
buttonNoText: {
|
buttonNoText: {
|
||||||
required: false,
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
showUrl: {
|
showUrl: {
|
||||||
required: false,
|
|
||||||
type: String,
|
type: String,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["markRead", "markUnread"],
|
|
||||||
computed: {
|
// Emits
|
||||||
/// [Option] override default button appearance (btn-misc)
|
const emit = defineEmits(['markRead', 'markUnread']);
|
||||||
overrideClass() {
|
|
||||||
return this.buttonClass ? this.buttonClass : "btn-misc";
|
// Computed
|
||||||
},
|
const overrideClass = computed(() => props.buttonClass || 'btn-misc');
|
||||||
/// [Option] don't display text on button
|
const buttonHideText = computed(() => props.buttonNoText);
|
||||||
buttonHideText() {
|
const isButtonGroup = computed(() => props.showUrl);
|
||||||
return this.buttonNoText;
|
|
||||||
},
|
// Methods
|
||||||
/// [Option] showUrl is href for show page second button.
|
const markAsUnread = () => {
|
||||||
// When passed, the component return a button-group with 2 buttons.
|
makeFetch('POST', `/api/1.0/main/notification/${props.notificationId}/mark/unread`, []).then(() => {
|
||||||
isButtonGroup() {
|
emit('markRead', { notificationId: props.notificationId });
|
||||||
return this.showUrl;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
markAsUnread() {
|
|
||||||
makeFetch(
|
|
||||||
"POST",
|
|
||||||
`/api/1.0/main/notification/${this.notificationId}/mark/unread`,
|
|
||||||
[]
|
|
||||||
).then((response) => {
|
|
||||||
this.$emit("markRead", {notificationId: this.notificationId});
|
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
markAsRead() {
|
|
||||||
makeFetch(
|
const markAsRead = () => {
|
||||||
"POST",
|
makeFetch('POST', `/api/1.0/main/notification/${props.notificationId}/mark/read`, []).then(() => {
|
||||||
`/api/1.0/main/notification/${this.notificationId}/mark/read`,
|
emit('markUnread', { notificationId: props.notificationId });
|
||||||
[]
|
|
||||||
).then((response) => {
|
|
||||||
this.$emit("markUnread", {
|
|
||||||
notificationId: this.notificationId,
|
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
},
|
|
||||||
markAllRead() {
|
// i18n setup
|
||||||
makeFetch(
|
/*const { t } = useI18n({
|
||||||
"POST",
|
|
||||||
`/api/1.0/main/notification/markallread`,
|
|
||||||
[]
|
|
||||||
).then((response) => {
|
|
||||||
this.$emit("markAllRead");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
i18n: {
|
|
||||||
messages: {
|
messages: {
|
||||||
fr: {
|
fr: {
|
||||||
markAsUnread: "Marquer comme non-lu",
|
markAsUnread: 'Marquer comme non-lu',
|
||||||
markAsRead: "Marquer comme lu",
|
markAsRead: 'Marquer comme lu',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});*/
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss"></style>
|
<style lang="scss"></style>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon"></i>
|
<i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon"></i>
|
||||||
|
|
||||||
<span v-if="!noText">
|
<span v-if="!noText">
|
||||||
{{ $t('online_edit_document') }}
|
{{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -24,17 +24,11 @@
|
|||||||
<span class="ms-auto me-3">
|
<span class="ms-auto me-3">
|
||||||
<span v-if="options.title">{{ options.title }}</span>
|
<span v-if="options.title">{{ options.title }}</span>
|
||||||
</span>
|
</span>
|
||||||
<!--
|
|
||||||
<a class="btn btn-outline-light">
|
|
||||||
<i class="fa fa-save fa-fw"></i>
|
|
||||||
{{ $t('save_and_quit') }}
|
|
||||||
</a>
|
|
||||||
-->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<div v-if="loading" class="loading">
|
<div v-if="loading" class="loading">
|
||||||
<i class="fa fa-circle-o-notch fa-spin fa-3x" :title="$t('loading')"></i>
|
<i class="fa fa-circle-o-notch fa-spin fa-3x" :title="trans(WOPI_LOADING)"></i>
|
||||||
</div>
|
</div>
|
||||||
<iframe
|
<iframe
|
||||||
:src="this.wopiUrl"
|
:src="this.wopiUrl"
|
||||||
@ -45,54 +39,56 @@
|
|||||||
</modal>
|
</modal>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<modal v-if="modal.showModal"
|
<Modal v-if="modal.showModal"
|
||||||
modalDialogClass="modal-sm"
|
modalDialogClass="modal-sm"
|
||||||
@close="modal.showModal = false">
|
@close="modal.showModal = false">
|
||||||
|
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h3>{{ $t('invalid_title') }}</h3>
|
<h3>{{ trans(WOPI_INVALID_TITLE) }}</h3>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<div class="alert alert-warning">{{ $t('invalid_message') }}</div>
|
<div class="alert alert-warning">{{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||||
import logo from 'ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png';
|
import logo from 'ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { trans, WOPI_ONLINE_EDIT_DOCUMENT, WOPI_INVALID_TITLE, WOPI_LOADING } from '../../../../../../../../../../assets/translator'
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "OpenWopiLink",
|
// Props
|
||||||
components: {
|
const props = defineProps({
|
||||||
Modal
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
wopiUrl: {
|
wopiUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false
|
required: false,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data() {
|
});
|
||||||
return {
|
|
||||||
modal: {
|
// data
|
||||||
|
const modal = ref({
|
||||||
showModal: false,
|
showModal: false,
|
||||||
modalDialogClass: "modal-fullscreen" //modal-dialog-scrollable
|
modalDialogClass: "modal-fullscreen",
|
||||||
},
|
});
|
||||||
logo: logo,
|
const logoImg = logo;
|
||||||
loading: false,
|
const loading = ref(false);
|
||||||
mime: [
|
|
||||||
|
// MIME types
|
||||||
|
const mime = [
|
||||||
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
|
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
|
||||||
'application/clarisworks',
|
'application/clarisworks',
|
||||||
'application/coreldraw',
|
'application/coreldraw',
|
||||||
@ -164,56 +160,26 @@ export default {
|
|||||||
'application/x-pagemaker',
|
'application/x-pagemaker',
|
||||||
'application/x-sony-bbeb',
|
'application/x-sony-bbeb',
|
||||||
'application/x-t602',
|
'application/x-t602',
|
||||||
]
|
]
|
||||||
}
|
|
||||||
},
|
// Computed
|
||||||
computed: {
|
const isOpenDocument = computed(() => mime.includes(props.type));
|
||||||
isOpenDocument() {
|
|
||||||
if (this.mime.indexOf(this.type) !== -1) {
|
const noText = computed(() => props.options?.noText === true);
|
||||||
return true;
|
|
||||||
}
|
const isChangeIcon = computed(() => !!props.options?.changeIcon);
|
||||||
return false;
|
|
||||||
},
|
const isChangeClass = computed(() => !!props.options?.changeClass);
|
||||||
noText() {
|
|
||||||
if (typeof this.options.noText !== 'undefined') {
|
// Methods
|
||||||
return this.options.noText === true;
|
const openModal = () => {
|
||||||
}
|
loading.value = true;
|
||||||
return false;
|
modal.value.showModal = true;
|
||||||
},
|
};
|
||||||
isChangeIcon() {
|
|
||||||
if (typeof this.options.changeIcon !== 'undefined') {
|
const loaded = () => {
|
||||||
return (!(this.options.changeIcon === null || this.options.changeIcon === ''))
|
loading.value = false;
|
||||||
}
|
};
|
||||||
return false;
|
|
||||||
},
|
|
||||||
isChangeClass() {
|
|
||||||
if (typeof this.options.changeClass !== 'undefined') {
|
|
||||||
return (!(this.options.changeClass === null || this.options.changeClass === ''))
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
openModal() {
|
|
||||||
this.loading = true;
|
|
||||||
this.modal.showModal = true;
|
|
||||||
},
|
|
||||||
loaded() {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
i18n: {
|
|
||||||
messages: {
|
|
||||||
fr: {
|
|
||||||
online_edit_document: "Éditer en ligne",
|
|
||||||
save_and_quit: "Enregistrer et quitter",
|
|
||||||
loading: "Chargement de l'éditeur en ligne",
|
|
||||||
invalid_title: "Format incompatible",
|
|
||||||
invalid_message: "Désolé, ce format de document n'est pas éditable en ligne.",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -114,3 +114,6 @@ services:
|
|||||||
Chill\MainBundle\Service\EntityInfo\ViewEntityInfoManager:
|
Chill\MainBundle\Service\EntityInfo\ViewEntityInfoManager:
|
||||||
arguments:
|
arguments:
|
||||||
$vienEntityInfoProviders: !tagged_iterator chill_main.entity_info_provider
|
$vienEntityInfoProviders: !tagged_iterator chill_main.entity_info_provider
|
||||||
|
|
||||||
|
Symfony\Component\Translation\Translator:
|
||||||
|
alias: translator.default
|
||||||
|
@ -74,3 +74,7 @@ services:
|
|||||||
Chill\MainBundle\Command\SynchronizeEntityInfoViewsCommand:
|
Chill\MainBundle\Command\SynchronizeEntityInfoViewsCommand:
|
||||||
tags:
|
tags:
|
||||||
- {name: console.command}
|
- {name: console.command}
|
||||||
|
|
||||||
|
Chill\MainBundle\Command\DetectTranslationDuplicatesCommand:
|
||||||
|
tags:
|
||||||
|
- { name: console.command }
|
||||||
|
@ -111,6 +111,12 @@ workflow:
|
|||||||
one {il reste encore une visualisation possible.}
|
one {il reste encore une visualisation possible.}
|
||||||
other {il reste encore # visualisations possibles.}
|
other {il reste encore # visualisations possibles.}
|
||||||
}
|
}
|
||||||
|
associated: >-
|
||||||
|
{aw, plural,
|
||||||
|
=0 {Aucun workflow associé}
|
||||||
|
one {# Workflow associé}
|
||||||
|
other {# Workflows associés}
|
||||||
|
}
|
||||||
|
|
||||||
duration:
|
duration:
|
||||||
minute: >-
|
minute: >-
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
modal:
|
||||||
|
action:
|
||||||
|
close: Fermer
|
||||||
|
|
||||||
"This program is free software: you can redistribute it and/or modify it under the terms of the <strong>GNU Affero General Public License</strong>": "Ce programme est un logiciel libre: vous pouvez le redistribuer et/ou le modifier selon les termes de la licence <strong>GNU Affero GPL</strong>"
|
"This program is free software: you can redistribute it and/or modify it under the terms of the <strong>GNU Affero General Public License</strong>": "Ce programme est un logiciel libre: vous pouvez le redistribuer et/ou le modifier selon les termes de la licence <strong>GNU Affero GPL</strong>"
|
||||||
User manual: Manuel d'utilisation
|
User manual: Manuel d'utilisation
|
||||||
Search: Rechercher
|
Search: Rechercher
|
||||||
@ -124,6 +128,48 @@ address:
|
|||||||
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
|
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
|
||||||
real address: Adresse d'un domicile
|
real address: Adresse d'un domicile
|
||||||
consider homeless: Cette adresse est incomplète
|
consider homeless: Cette adresse est incomplète
|
||||||
|
add_an_address_title: Créer une adresse
|
||||||
|
edit_an_address_title: Modifier une adresse
|
||||||
|
create_a_new_address: Créer une nouvelle adresse
|
||||||
|
edit_address: Modifier l'adresse
|
||||||
|
select_an_address_title: Sélectionner une adresse
|
||||||
|
fill_an_address: Compléter l'adresse
|
||||||
|
select_country: Choisir le pays
|
||||||
|
country: Pays
|
||||||
|
select_city: Choisir une localité
|
||||||
|
city: Localité
|
||||||
|
other_city: Autre localité
|
||||||
|
select_address: Choisir une adresse
|
||||||
|
address: Adresse
|
||||||
|
other_address: Autre adresse
|
||||||
|
create_address: Adresse inconnue. Cliquez ici pour créer une nouvelle adresse
|
||||||
|
isNoAddress: Pas d'adresse complète
|
||||||
|
isConfidential: Adresse confidentielle
|
||||||
|
street: Nom de rue
|
||||||
|
streetNumber: Numéro
|
||||||
|
floor: Étage
|
||||||
|
corridor: Couloir
|
||||||
|
steps: Escalier
|
||||||
|
flat: Appartement
|
||||||
|
buildingName: Résidence
|
||||||
|
extra: Complément d'adresse
|
||||||
|
distribution: Cedex
|
||||||
|
create_postal_code: Localité inconnue. Cliquez ici pour créer une nouvelle localité
|
||||||
|
postalCode_name: Nom
|
||||||
|
postalCode_code: Code postal
|
||||||
|
date: Date de la nouvelle adresse
|
||||||
|
valid_from: L'adresse est valable à partir du
|
||||||
|
valid_to: L'adresse est valable jusqu'au
|
||||||
|
back_to_the_list: Retour à la liste
|
||||||
|
loading: chargement en cours...
|
||||||
|
address_suggestions: Suggestion d'adresses
|
||||||
|
address_new_success: La nouvelle adresse est enregistrée.
|
||||||
|
address_edit_success: L'adresse a été mise à jour.
|
||||||
|
wait_redirection: La page est redirigée.
|
||||||
|
not_yet_address: Il n'y a pas encore d'adresse. Cliquez sur '+ Créer une adresse'
|
||||||
|
use_this_address: Utiliser cette adresse
|
||||||
|
household:
|
||||||
|
move_date: Date du déménagement
|
||||||
address more:
|
address more:
|
||||||
floor: ét
|
floor: ét
|
||||||
corridor: coul
|
corridor: coul
|
||||||
@ -508,6 +554,8 @@ Follow workflow: Suivre la décision
|
|||||||
Workflow history: Historique de la décision
|
Workflow history: Historique de la décision
|
||||||
|
|
||||||
workflow:
|
workflow:
|
||||||
|
list: Liste des workflows associés
|
||||||
|
associated: workflow associé
|
||||||
Created by: Créé par
|
Created by: Créé par
|
||||||
My decision: Ma décision
|
My decision: Ma décision
|
||||||
Next step: Prochaine étape
|
Next step: Prochaine étape
|
||||||
@ -555,6 +603,7 @@ workflow:
|
|||||||
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
|
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
|
||||||
For: Pour
|
For: Pour
|
||||||
Cc: Cc
|
Cc: Cc
|
||||||
|
At: Le
|
||||||
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
|
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
|
||||||
An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces adresses
|
An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces adresses
|
||||||
Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtenu l'accès grâce au lien reçu par email
|
Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtenu l'accès grâce au lien reçu par email
|
||||||
@ -612,6 +661,11 @@ workflow:
|
|||||||
reject_signature_of: Rejet de la signature de %signer%
|
reject_signature_of: Rejet de la signature de %signer%
|
||||||
reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer%
|
reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer%
|
||||||
|
|
||||||
|
subscribe_final: Recevoir une notification à l'étape finale
|
||||||
|
unsubscribe_final: Ne plus recevoir de notification à l'étape finale
|
||||||
|
subscribe_all_steps: Recevoir une notification à chaque étape du suivi
|
||||||
|
unsubscribe_all_steps: Ne plus recevoir de notification à chaque étape du suivi
|
||||||
|
|
||||||
Subscribe final: Recevoir une notification à l'étape finale
|
Subscribe final: Recevoir une notification à l'étape finale
|
||||||
Subscribe all steps: Recevoir une notification à chaque étape
|
Subscribe all steps: Recevoir une notification à chaque étape
|
||||||
CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION: Appliquer les transitions sur tous les workflows
|
CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION: Appliquer les transitions sur tous les workflows
|
||||||
@ -655,6 +709,8 @@ notification:
|
|||||||
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Un compte utilisateur sera toujours nécessaire.
|
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Un compte utilisateur sera toujours nécessaire.
|
||||||
Remove an email: Supprimer l'adresse email
|
Remove an email: Supprimer l'adresse email
|
||||||
Email with access link: Adresse email ayant reçu un lien d'accès
|
Email with access link: Adresse email ayant reçu un lien d'accès
|
||||||
|
mark_as_read: Marquer comme lu
|
||||||
|
mark_as_unread: Marquer comme non-lu
|
||||||
|
|
||||||
export:
|
export:
|
||||||
address_helper:
|
address_helper:
|
||||||
@ -788,6 +844,33 @@ news:
|
|||||||
read_more: Lire la suite
|
read_more: Lire la suite
|
||||||
show_details: Voir l'actualité
|
show_details: Voir l'actualité
|
||||||
|
|
||||||
|
wopi:
|
||||||
|
online_edit_document: Éditer en ligne
|
||||||
|
save_and_quit: Enregistrer et quitter
|
||||||
|
loading: Chargement de l'éditeur en ligne
|
||||||
|
invalid_title: Format incompatible
|
||||||
|
invalid_message: Désolé, ce format de document n'est pas éditable en ligne.
|
||||||
|
|
||||||
|
onthefly:
|
||||||
|
show:
|
||||||
|
person: Détails de l'usager
|
||||||
|
thirdparty: Détails du tiers
|
||||||
|
file_person: Ouvrir la fiche de l'usager
|
||||||
|
file_thirdparty: Voir le Tiers
|
||||||
|
edit:
|
||||||
|
person: Modifier un usager
|
||||||
|
thirdparty: Modifier un tiers
|
||||||
|
create:
|
||||||
|
button: Créer {q}
|
||||||
|
title:
|
||||||
|
default: Création d'un nouvel usager ou d'un tiers professionnel
|
||||||
|
person: Création d'un nouvel usager
|
||||||
|
thirdparty: Création d'un nouveau tiers professionnel
|
||||||
|
person: un nouvel usager
|
||||||
|
thirdparty: un nouveau tiers professionnel
|
||||||
|
addContact:
|
||||||
|
title: Créer un contact pour {q}
|
||||||
|
resource_comment_title: Un commentaire est associé à cet interlocuteur
|
||||||
gender:
|
gender:
|
||||||
genderTranslation: traduction grammaticale
|
genderTranslation: traduction grammaticale
|
||||||
not defined: Non défini
|
not defined: Non défini
|
||||||
@ -796,3 +879,12 @@ gender:
|
|||||||
Select gender icon: Icône à utiliser
|
Select gender icon: Icône à utiliser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
multiselect:
|
||||||
|
placeholder: Choisir
|
||||||
|
tag_placeholder: Créer un nouvel élément
|
||||||
|
select_label: Entrée ou cliquez pour sélectionner
|
||||||
|
deselect_label: Entrée ou cliquez pour désélectionner
|
||||||
|
select_group_label: Appuyer sur "Entrée" pour sélectionner ce groupe
|
||||||
|
deselect_group_label: Appuyer sur "Entrée" pour désélectionner ce groupe
|
||||||
|
selected_label: Sélectionné'
|
||||||
|
24
symfony.lock
24
symfony.lock
@ -324,6 +324,15 @@
|
|||||||
"config/packages/security.yaml"
|
"config/packages/security.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/stimulus-bundle": {
|
||||||
|
"version": "2.21",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.8",
|
||||||
|
"ref": "9e33a8a3794b603fb4be6c04ee5ecab901ce549e"
|
||||||
|
}
|
||||||
|
},
|
||||||
"symfony/translation": {
|
"symfony/translation": {
|
||||||
"version": "5.4",
|
"version": "5.4",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@ -350,6 +359,21 @@
|
|||||||
"templates/base.html.twig"
|
"templates/base.html.twig"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/ux-translator": {
|
||||||
|
"version": "2.21",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.9",
|
||||||
|
"ref": "bc396565cc4cab95692dd6df810553dc22e352e1"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"assets/translator.js",
|
||||||
|
"config/packages/ux_translator.yaml",
|
||||||
|
"var/translations/configuration.js",
|
||||||
|
"var/translations/index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/validator": {
|
"symfony/validator": {
|
||||||
"version": "5.4",
|
"version": "5.4",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user