From b82ff516dde2e8d8ba42d59b3fd2d07a180713f7 Mon Sep 17 00:00:00 2001 From: Arnaud Levy Date: Wed, 24 Jun 2026 07:12:40 +0200 Subject: [PATCH 1/2] Passage en statique du i18n de Vue --- .gitignore | 3 +++ app/controllers/admin/vue_i18n_controller.rb | 7 ------- .../communication/blocks/group/_editor.html.erb | 7 ++----- config/initializers/vue_i18n.rb | 15 +++++++++++++++ config/routes.rb | 1 - 5 files changed, 20 insertions(+), 13 deletions(-) delete mode 100644 app/controllers/admin/vue_i18n_controller.rb create mode 100644 config/initializers/vue_i18n.rb diff --git a/.gitignore b/.gitignore index df4b755d6..197cbf587 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ /public/packs /public/packs-test +# Vue i18n files generated at build time (cf. esbuild.config.mjs) +/public/vue/*.json + # Ignore some JS stuff /node_modules /yarn-error.log diff --git a/app/controllers/admin/vue_i18n_controller.rb b/app/controllers/admin/vue_i18n_controller.rb deleted file mode 100644 index ca1339f0a..000000000 --- a/app/controllers/admin/vue_i18n_controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Admin::VueI18nController < Admin::ApplicationController - layout false - - def index - render json: I18n.t('vue') - end -end diff --git a/app/views/admin/communication/blocks/group/_editor.html.erb b/app/views/admin/communication/blocks/group/_editor.html.erb index 3ba9ec4e9..a77fcf4a3 100644 --- a/app/views/admin/communication/blocks/group/_editor.html.erb +++ b/app/views/admin/communication/blocks/group/_editor.html.erb @@ -1,9 +1,6 @@ <% -# L'url contenant les locales (TODO doit être mise en cache) -i18n_url = admin_vue_i18n_path( - website_id: nil, - extranet_id: nil, - journal_id: nil) +# Fichier statique de traductions, généré à la compilation (cf. esbuild.config.mjs) +i18n_url = "/vue/#{I18n.locale}.json" # La liste des blocs data_url = root_admin_communication_group_blocks_path( about_type: about.class.polymorphic_name, diff --git a/config/initializers/vue_i18n.rb b/config/initializers/vue_i18n.rb new file mode 100644 index 000000000..c13423e76 --- /dev/null +++ b/config/initializers/vue_i18n.rb @@ -0,0 +1,15 @@ +# Génère les fichiers de traduction statiques des apps Vue dans +# public/vue/.json, afin qu'ils soient chargés sans aucun traitement +# côté serveur (cf. data-i18n-url dans l'éditeur de blocs). +# +# after_initialize garantit que tous les fichiers config/locales/**/*.yml sont +# bien ajoutés à I18n.load_path. En dev, redémarrer le serveur régénère les +# fichiers après modification d'une traduction. +Rails.application.config.after_initialize do + output_dir = Rails.root.join('public', 'vue') + FileUtils.mkdir_p(output_dir) + I18n.available_locales.each do |locale| + translations = I18n.t('vue', locale: locale, default: {}) + File.write(output_dir.join("#{locale}.json"), translations.to_json) + end +end diff --git a/config/routes.rb b/config/routes.rb index a9d8aa5ad..722aaba3a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -47,7 +47,6 @@ post 'profile/optin_newsletter' => 'profile#optin_newsletter', as: :optin_newsletter # libre_translate route post 'translate/from/:source/to/:target' => 'translation#translate', as: :translate - get 'vue_i18n' => 'vue_i18n#index', as: :vue_i18n, defaults: { format: :json } put 'favorite' => 'users#favorite', as: :favorite get 'search(/website/:website_id)(/extranet/:extranet_id)' => 'search#index', as: :search draw 'admin/administration' From af752ac0b8d14857997f4527399e13e6e072b806 Mon Sep 17 00:00:00 2001 From: Arnaud Levy Date: Wed, 24 Jun 2026 07:42:29 +0200 Subject: [PATCH 2/2] Done! --- .gitignore | 2 +- .../apps/blocks-editor/BlocksEditorApp.vue | 19 +++----- .../apps/blocks-editor/components/Blocks.vue | 15 +++--- app/javascript/apps/components/Changes.vue | 7 ++- .../apps/components/CropperModal.vue | 13 ++--- app/javascript/apps/index.js | 40 +++++++++++----- .../apps/media-picker/MediaPickerApp.vue | 19 ++++---- .../apps/media-picker/components/Cloud.vue | 24 +++++----- .../media-picker/components/ImageUploader.vue | 16 +++---- .../apps/media-picker/components/Medias.vue | 20 ++++---- .../apps/time-slots/TimeSlotsApp.vue | 8 +--- .../apps/time-slots/components/Duration.vue | 7 ++- .../blocks/group/_editor.html.erb | 3 -- .../communication/medias/_picker.html.erb | 1 - .../agenda/events/show/_time_slots.html.erb | 1 - app/views/layouts/application.html.erb | 2 +- config/initializers/vue_i18n.rb | 11 +++-- config/locales/vue/en.yml | 2 +- config/locales/vue/fr.yml | 2 +- esbuild.config.mjs | 7 ++- package.json | 3 +- yarn.lock | 47 ++++++++++++++++++- 22 files changed, 154 insertions(+), 115 deletions(-) diff --git a/.gitignore b/.gitignore index 197cbf587..7a0e072c2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ /public/packs /public/packs-test -# Vue i18n files generated at build time (cf. esbuild.config.mjs) +# Vue i18n messages generated at boot (cf. config/initializers/vue_i18n.rb) /public/vue/*.json # Ignore some JS stuff diff --git a/app/javascript/apps/blocks-editor/BlocksEditorApp.vue b/app/javascript/apps/blocks-editor/BlocksEditorApp.vue index 7b71cec29..51533004e 100644 --- a/app/javascript/apps/blocks-editor/BlocksEditorApp.vue +++ b/app/javascript/apps/blocks-editor/BlocksEditorApp.vue @@ -11,12 +11,10 @@ export default { loading: true, csrfToken: '', url: { - i18n: '', new: '', reorder: '', data: '', }, - i18n: {}, data: {}, planMode: false, offcanvasState: 'closed', // closed, picking, editing @@ -26,11 +24,9 @@ export default { beforeMount() { this.csrfToken = document.querySelector('[name="csrf-token"]').content; const dataset = document.getElementById('blocks-editor-app').dataset; - this.url.i18n = dataset.i18nUrl; this.url.data = dataset.dataUrl; this.url.new = dataset.newUrl; this.url.reorder = dataset.reorderUrl; - this.loadJson(this.url.i18n, 'i18n'); this.refresh(); }, methods: { @@ -38,7 +34,7 @@ export default { const res = await fetch(url, { headers: { Accept: 'application/json' } }); if (!res.ok) return; this[target] = await res.json(); - if (this.i18n.blocksEditor && this.data.blocks) { + if (this.data.blocks) { this.loading = false; } }, @@ -77,7 +73,7 @@ export default { notyf.open({ type: 'success', position: { x: 'left', y: 'bottom' }, - message: this.i18n.blocksEditor.confirm.copy, + message: this.$t('blocksEditor.confirm.copy'), duration: 9000, ripple: true, dismissible: true, @@ -137,19 +133,18 @@ export default { - {{ i18n.blocksEditor.actions.addBlock }} + {{ $t('blocksEditor.actions.addBlock') }} - {{ i18n.blocksEditor.planMode.button }} + {{ $t('blocksEditor.planMode.button') }} - {{ i18n.blocksEditor.actions.addBlock }} + {{ $t('blocksEditor.actions.addBlock') }} -  {{ i18n.blocksEditor.actions.move }} +  {{ $t('blocksEditor.actions.move') }} @@ -91,23 +90,23 @@ export default { href="#" class="action text-danger ms-2" @click="onDelete($event, block)"> - {{ i18n.blocksEditor.actions.delete }} + {{ $t('blocksEditor.actions.delete') }} - {{ i18n.blocksEditor.actions.copy }} + {{ $t('blocksEditor.actions.copy') }} - {{ i18n.blocksEditor.actions.duplicate }} + {{ $t('blocksEditor.actions.duplicate') }} - {{ i18n.blocksEditor.actions.edit }} + {{ $t('blocksEditor.actions.edit') }}
- {{ i18n.cancel }} + {{ $t('changes.cancel') }}
diff --git a/app/javascript/apps/components/CropperModal.vue b/app/javascript/apps/components/CropperModal.vue index ccba152cb..633a410d3 100644 --- a/app/javascript/apps/components/CropperModal.vue +++ b/app/javascript/apps/components/CropperModal.vue @@ -23,7 +23,6 @@ export default { width: null, height: null, }, - i18n: {}, } }, methods: { @@ -74,10 +73,6 @@ export default { xhr.send(JSON.stringify(this.data)); }, }, - beforeMount() { - this.dataset = document.getElementById('media-picker-app').dataset; - this.i18n = JSON.parse(this.dataset.i18n).cropperModal; - }, }; // On utilise canvas=false et check-orientation=false pour éviter les problèmes de CORS @@ -94,7 +89,7 @@ export default {