Просмотр исходного кода

Merge branch 'master' into feat/144301-144437-select-unrelated-group-inheritance-on-normal-child-page-create

Futa Arai 1 год назад
Родитель
Сommit
302fafe16f
94 измененных файлов с 3262 добавлено и 479 удалено
  1. 53 1
      CHANGELOG.md
  2. 1 1
      apps/app/docker/README.md
  3. 2 2
      apps/app/package.json
  4. 1135 0
      apps/app/public/static/locales/fr_FR/admin.json
  5. 161 0
      apps/app/public/static/locales/fr_FR/commons.json
  6. 867 0
      apps/app/public/static/locales/fr_FR/translation.json
  7. 14 0
      apps/app/resource/locales/fr_FR/admin/userInvitation.ejs
  8. 11 0
      apps/app/resource/locales/fr_FR/admin/userResetPassword.ejs
  9. 20 0
      apps/app/resource/locales/fr_FR/admin/userWaitingActivation.ejs
  10. 9 0
      apps/app/resource/locales/fr_FR/notifications/comment.ejs
  11. 13 0
      apps/app/resource/locales/fr_FR/notifications/notActiveUser.ejs
  12. 5 0
      apps/app/resource/locales/fr_FR/notifications/pageCreate.ejs
  13. 5 0
      apps/app/resource/locales/fr_FR/notifications/pageDelete.ejs
  14. 5 0
      apps/app/resource/locales/fr_FR/notifications/pageEdit.ejs
  15. 5 0
      apps/app/resource/locales/fr_FR/notifications/pageLike.ejs
  16. 5 0
      apps/app/resource/locales/fr_FR/notifications/pageMove.ejs
  17. 12 0
      apps/app/resource/locales/fr_FR/notifications/passwordReset.ejs
  18. 8 0
      apps/app/resource/locales/fr_FR/notifications/passwordResetSuccessful.ejs
  19. 12 0
      apps/app/resource/locales/fr_FR/notifications/userActivation.ejs
  20. 169 0
      apps/app/resource/locales/fr_FR/sandbox-bootstrap5.md
  21. 7 0
      apps/app/resource/locales/fr_FR/sandbox-diagrams.md
  22. 71 0
      apps/app/resource/locales/fr_FR/sandbox-math.md
  23. 158 0
      apps/app/resource/locales/fr_FR/sandbox.md
  24. 48 0
      apps/app/resource/locales/fr_FR/welcome.md
  25. 2 0
      apps/app/src/client/util/locale-utils.ts
  26. 1 1
      apps/app/src/components/Admin/AuditLog/ActivityTable.tsx
  27. 1 1
      apps/app/src/components/Admin/AuditLogManagement.tsx
  28. 1 1
      apps/app/src/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx
  29. 3 4
      apps/app/src/components/Admin/UserGroup/UserGroupForm.tsx
  30. 1 1
      apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  31. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx
  32. 1 1
      apps/app/src/components/Admin/Users/ExternalAccountTable.tsx
  33. 1 1
      apps/app/src/components/Admin/Users/UserTable.tsx
  34. 1 1
      apps/app/src/components/AuthorInfo/AuthorInfo.tsx
  35. 4 8
      apps/app/src/components/Comments.tsx
  36. 0 1
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx
  37. 1 1
      apps/app/src/components/Me/ExternalAccountRow.jsx
  38. 3 3
      apps/app/src/components/Page/DisplaySwitcher.tsx
  39. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLink.tsx
  40. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkForm.tsx
  41. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx
  42. 1 1
      apps/app/src/components/PageAlert/TrashPageAlert.tsx
  43. 8 3
      apps/app/src/components/PageComment.tsx
  44. 3 3
      apps/app/src/components/PageComment/Comment.tsx
  45. 7 0
      apps/app/src/components/PageComment/CommentEditor.module.scss
  46. 173 189
      apps/app/src/components/PageComment/CommentEditor.tsx
  47. 1 2
      apps/app/src/components/PageComment/DeleteCommentModal.tsx
  48. 4 4
      apps/app/src/components/PageCreateModal.tsx
  49. 1 1
      apps/app/src/components/PageEditor/ConflictDiffModal.tsx
  50. 1 1
      apps/app/src/components/PageList/PageListItemL.tsx
  51. 11 12
      apps/app/src/components/PageRenameModal.tsx
  52. 2 3
      apps/app/src/components/SearchPage/SearchResultContent.tsx
  53. 1 1
      apps/app/src/components/Sidebar/PageCreateButton/hooks/use-create-todays-memo.tsx
  54. 1 1
      apps/app/src/components/TemplateModal/use-formatter.tsx
  55. 1 1
      apps/app/src/components/User/UserDate.jsx
  56. 1 1
      apps/app/src/server/models/attachment.ts
  57. 1 1
      apps/app/src/server/models/obsolete-page.js
  58. 1 1
      apps/app/src/server/models/page-operation.ts
  59. 3 2
      apps/app/src/server/models/password-reset-order.ts
  60. 3 2
      apps/app/src/server/models/user-registration-order.ts
  61. 4 0
      apps/app/src/server/routes/comment.js
  62. 1 1
      apps/app/src/server/service/config-loader.ts
  63. 3 2
      apps/app/src/server/service/config-manager.ts
  64. 7 6
      apps/app/src/server/service/in-app-notification.ts
  65. 1 1
      apps/app/src/server/service/installer.ts
  66. 11 28
      apps/app/src/stores/comment.tsx
  67. 57 2
      apps/app/src/stores/ui.tsx
  68. 0 4
      apps/app/src/styles/_layout.scss
  69. 1 1
      apps/app/test/integration/service/v5.page.test.ts
  70. 3 3
      apps/slackbot-proxy/package.json
  71. 1 1
      apps/slackbot-proxy/src/controllers/growi-to-slack.ts
  72. 1 1
      apps/slackbot-proxy/src/entities/relation.ts
  73. 1 1
      apps/slackbot-proxy/src/services/RelationsService.ts
  74. 1 1
      package.json
  75. 1 1
      packages/core/package.json
  76. 1 0
      packages/core/src/interfaces/lang.ts
  77. 1 1
      packages/editor/package.json
  78. 11 6
      packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx
  79. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.tsx
  80. 15 13
      packages/editor/src/components/CodeMirrorEditorComment.tsx
  81. 1 1
      packages/editor/src/components/CodeMirrorEditorMain.tsx
  82. 1 1
      packages/editor/src/consts/global-code-mirror-editor-key.ts
  83. 1 1
      packages/presentation/package.json
  84. 1 1
      packages/preset-templates/package.json
  85. 1 1
      packages/preset-themes/package.json
  86. 1 1
      packages/remark-attachment-refs/package.json
  87. 1 1
      packages/remark-drawio/package.json
  88. 1 1
      packages/remark-growi-directive/package.json
  89. 1 1
      packages/remark-lsx/package.json
  90. 2 2
      packages/slack/package.json
  91. 1 1
      packages/slack/src/utils/generate-last-update-markdown.ts
  92. 2 1
      packages/ui/package.json
  93. 1 1
      packages/ui/src/components/Attachment.tsx
  94. 81 130
      yarn.lock

+ 53 - 1
CHANGELOG.md

@@ -1,9 +1,61 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.0.1...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.0.2...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v7.0.2](https://github.com/weseek/growi/compare/v7.0.1...v7.0.2) - 2024-04-17
+
+### 💎 Features
+
+* feat: fr_FR locale
+
+### 🚀 Improvement
+
+* imprv: Hide title of untitled page when creating newly (#8536) @WNomunomu
+* imprv: Comment editor performance (#8731) @yuki-takei
+* imprv: Truncate page path title in view (#8707) @reiji-h
+* imprv: Show unrelated groups and disable ungrantable groups in grant select (#8695) @arafubeatbox
+* imprv: Bookmark sidebar layout (#8703) @satof3
+* imprv: Responsive layout (#8710) @yuki-takei
+* imprv: Sm size dropdown menu (#8711) @satof3
+* imprv: search results form improvements (#8705) @maeshinshin
+* imprv: WIP Page behavior (#8700) @miya
+* imprv: Disable groups not assignable to page due to parent grant (#8570) @arafubeatbox
+* imprv: Border delete in search modal (#8629) @kazutoweseek
+* imprv: Border in search modal (#8629) @kazutoweseek
+* imprv: New Comment style (#8631) @satof3
+* imprv: Hide title of untitled page when creating newly (#8536) @WNomunomu
+* imprv: Render lightbox above the sidebar (#8673) @yuki-takei
+* imprv: Add border to the page title header when editing (#8671) @yuki-takei
+* imprv: Set font-display to icon fonts (#8670) @yuki-takei
+* imprv: Enable box-shadow (#8668) @yuki-takei
+
+### 🐛 Bug Fixes
+
+* fix: Login layout when external auth only (#8717) @satof3
+* fix: Initialize grant correctly after when page transition (#8633) @yukendev
+* fix: lsx icon layout and color (#8691) @satof3
+* fix: Tags are unintentionally created when a conversion is finalized (#8682) @miya
+* fix: Footnotes making page scrollable. (#8678) @reiji-h
+* fix: Automatic installation failure (#8685) @miya
+* fix: Page content preview not shown in duplicate alert page (#8645) @arafubeatbox
+* fix: react-toastify colors (#8672) @yuki-takei
+* fix: Add filePath to the schema of attachement for backward compatibility for v3.3 or earlier (#8679) @shield-9
+* fix: Questionnaire modal styles in dark mode (#8669) @yuki-takei
+* fix: Cannot display error when page path is duplicated (#8664) @TatsuyaIse
+
+### 🧰 Maintenance
+
+* ci(deps): bump express from 4.17.1 to 4.19.2 (#8617) @dependabot
+* support: Localize in app (#8716) @satof3
+* ci(deps): bump tar from 6.2.0 to 6.2.1 (#8712) @dependabot
+* support: Localize comment components (#8694) @satof3
+* support: Upgrade actions from v3 to v4 (2) (#8688) @yuki-takei
+* support: Upgrade actions from v3 to v4 (#8684) @yuki-takei
+* ci(deps): bump undici from 5.21.2 to 5.28.4 (#8686) @dependabot
+* ci(deps-dev): bump vite from 4.5.2 to 4.5.3 (#8675) @dependabot
+
 ## [v7.0.1](https://github.com/weseek/growi/compare/v7.0.0...v7.0.1) - 2024-04-02
 
 ### 🚀 Improvement

+ 1 - 1
apps/app/docker/README.md

@@ -10,7 +10,7 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`7.0.1`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.1/apps/app/docker/Dockerfile)
+* [`7.0.2`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.2/apps/app/docker/Dockerfile)
 * [`6.3.2`, `6.3`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.3.2/apps/app/docker/Dockerfile)
 * [`6.2.4`, `6.2` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.4/apps/app/docker/Dockerfile)
 * [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)

+ 2 - 2
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -103,7 +103,7 @@
     "cookie-parser": "^1.4.5",
     "csurf": "^1.11.0",
     "csv-to-markdown-table": "^1.4.1",
-    "date-fns": "^2.23.0",
+    "date-fns": "^3.6.0",
     "dayjs": "^1.11.7",
     "detect-indent": "^7.0.0",
     "diff": "^5.0.0",

+ 1135 - 0
apps/app/public/static/locales/fr_FR/admin.json

@@ -0,0 +1,1135 @@
+{
+  "meta": {
+    "display_name": "Français"
+  },
+  "last_login": "Dernière connexion",
+  "wiki_management_homepage": "Gestion du wiki",
+  "public": "Public",
+  "anyone_with_the_link": "Tous les utilisateurs disposant du lien",
+  "specified_users": "Utilisateurs spécifiés",
+  "only_me": "Seulement moi",
+  "only_inside_the_group": "Utilisateurs du groupe",
+  "optional": "Optionnel",
+  "security_settings": {
+    "security_settings": "Paramètres de sécurité",
+    "scope_of_page_disclosure": "Confidentialité de la page",
+    "set_point": "Valeur",
+    "Guest Users Access": "Accès invité",
+    "always_hidden": "Toujours caché",
+    "always_displayed": "Toujours affiché",
+    "displayed_or_hidden": "Caché / Affiché",
+    "Fixed by env var": "Configuré par la variable d'environnement <code>{{key}}={{value}}</code>.",
+    "register_limitation": "Paramètres d'inscription",
+    "register_limitation_desc": "Restreindre l'inscription de nouveaux utilisateurs",
+    "The whitelist of registration permission E-mail address": "Autoriser l'inscription à ces adresses courriel",
+    "users_without_account": "Inaccessible aux utilisateurs sans compte",
+    "example": "Exemple",
+    "restrict_emails": "L'inscription par courriel peut-être restreinte par domaine (démarrant par @). ",
+    "for_example": " Par exemple, pour restreindre l'inscription aux utilisateurs dans le domaine growi.org, ajouter ",
+    "in_this_case": "; dans ce cas particulier, seul les utilisateurs du domaine growi.org peuvent s'inscrire.",
+    "insert_single": "Insérer une adresse courriel par ligne",
+    "page_list_and_search_results": "Liste et recherche de pages",
+    "page_listing_1": "Liste et recherche de pages<br>restreint à 'Seulement moi'",
+    "page_listing_1_desc": "Voir les pages restreintes à 'Seulement moi' lors de la recherche",
+    "page_listing_2": "Liste et recherche de pages<br>restreint au groupe utilisateur",
+    "page_listing_2_desc": "Voir les pages restreintes au groupe utilisateur lors de la recherche",
+    "page_access_rights": "Droits de lecture",
+    "page_delete_rights": "Droits de suppression",
+    "page_delete": "Suppression de page",
+    "page_delete_completely": "Suppression complète de page",
+    "other_options": "Paramètres supplémentaires",
+    "deletion_explanation": "Restreindre les utilisateurs pouvant supprimer une page.",
+    "complete_deletion_explanation": "Restreindre les utilisateurs pouvant supprimer complètement une page.",
+    "recursive_deletion_explain": "Restreindre les utilisateurs pouvant récursivement supprimer une page.",
+    "recursive_complete_deletion_explain": "Restreindre les utilisateurs pouvant récursivement supprimer complètement une page.",
+    "is_all_group_membership_required_for_page_complete_deletion": "L'utilisateur doit faire partie de tout les groupes ayant l'accès à la page",
+    "is_all_group_membership_required_for_page_complete_deletion_explanation": "Effectif lorsque les paramètres de page sont \"Seulement groupes spécifiés\".",
+    "inherit": "Hériter(Utilise le même paramètre que pour une page)",
+    "admin_only": "Administrateur seulement",
+    "admin_and_author": "Administrateur et auteur",
+    "anyone": "Tout le monde",
+    "user_homepage_deletion": {
+      "user_homepage_deletion": "Suppression de page d'accueil utilisateur",
+      "enable_user_homepage_deletion": "Activer la suppression de page d'accueil utilisateur",
+      "enable_force_delete_user_homepage_on_user_deletion": "Lorsqu'un utilisateur est supprimé, sa page d'accueil et ses sous-pages sont supprimées.",
+      "desc": "Les pages d'accueil utilisateurs pourront être supprimées."
+    },
+    "session": "Session",
+    "max_age": "Âge maximal (ms)",
+    "max_age_desc": "Spécifie (en milliseconde) l'âge maximal d'une session <br>Par défaut: 2592000000 (30 jours)",
+    "max_age_caution": "Un rédemarrage du serveur est nécessaire lorsque cette valeur est modifiée",
+    "forced_update_desc": "Ce paramètre à été modifié. Valeur précedente: ",
+    "page_delete_rights_caution": "Lorsque \"Supprimer / Supprimer récursivement\" est activé, le paramètre is \"Supprimer / Supprimer complètement\" est écrasé. <br> <br> Administrateur seulement > Administrateur et auteur > Tout le monde",
+    "Authentication mechanism settings": "Mécanisme d'authentification",
+    "setup_is_not_yet_complete": "Configuration incomplète",
+    "xss_prevent_setting": "Prévenir les attaques XSS(Cross Site Scripting)",
+    "xss_prevent_setting_link": "Paramètres Markdown",
+    "callback_URL": "URL de Callback",
+    "providerName": "Nom du fournisseur",
+    "issuerHost": "Hôte de l'émetteur",
+    "scope": "Permissions",
+    "desc_of_callback_URL": "Utilisé dans les paramètres du fournisseur {{AuthName}}",
+    "authorization_endpoint": "Adresse d'autorisation",
+    "token_endpoint": "Adresse de jeton",
+    "revocation_endpoint": "Adresse de révocation",
+    "introspection_endpoint": "Adresse d'introspection",
+    "userinfo_endpoint": "Adresse UserInfo",
+    "end_session_endpoint": "Adresse EndSession",
+    "registration_endpoint": "Adresse d'inscription",
+    "jwks_uri": "URL JSON Web Key Set",
+    "clientID": "ID du client",
+    "client_secret": "Secret du client",
+    "updated_general_security_setting": "Paramètres mis à jour",
+    "setup_not_completed_yet": "Configuration incomplète",
+    "guest_mode": {
+      "deny": "Refuser (Utilisateurs inscrits seulement)",
+      "readonly": "Autoriser (Lecture seule)"
+    },
+    "registration_mode": {
+      "open": "Ouvert (Tout le monde peut s'inscrire)",
+      "restricted": "Restreint (Requiert l'approbation d'administrateurs)",
+      "closed": "Fermé (Invitation seulement)"
+    },
+    "share_link_management": "Gestion des liens de partage",
+    "No_share_links":"Aucun liens de partage",
+    "share_link_notice":"Retirer les liens de partage",
+    "delete_all_share_links":"Supprimer tout les liens de partage",
+    "share_link_rights": "Permissions de liens de partage",
+    "enable_link_sharing": "Activer les liens de partage",
+    "all_share_links": "Liens de partage",
+    "configuration": " Configuration",
+    "Treat username matching as identical": "Lier les nouveaux comptes externes automatiquement lorsque <code>username</code> correspond",
+    "Treat username matching as identical_warn": "ATTENTION: Le système considère ne peut différencier un utilisateur lorsque <code>username</code> correspond.",
+    "Treat email matching as identical": "Lier les nouveaux comptes externes automatiquement lorsque <code>email</code> correspond",
+    "Treat email matching as identical_warn": "ATTENTION: Le système considère ne peut différencier un utilisateur lorsque <code>email</code> correspond.",
+    "Use env var if empty": "Utiliser la variable d'environnement <code>{{env}}</code> si vide",
+    "Use default if both are empty": "Si les deux sont vides, la valeur par défaut <code>{{target}}</code> est utilisée.",
+    "missing mandatory configs": "Les paramètres suivants obligatoires ne sont pas configurés.",
+    "Local": {
+      "name": "ID/Mot de passe",
+      "note for the only env option": "The LOCAL authentication is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
+      "enable_local": "Activer ID/Mot de passe",
+      "password_reset_by_users": "Réinitialisation de mot passe",
+      "enable_password_reset_by_users": "Autoriser la réinitialisation de mot de passe",
+      "password_reset_desc": "Autorise l'utilisateur à réinitialiser son mot de passe",
+      "email_authentication": "Vérification d'adresse courriel",
+      "enable_email_authentication": "Activer la vérification d'adresse courriel",
+      "enable_email_authentication_desc": "Un courriel de vérification sera envoyé à l'inscription d'utilisateur."
+    },
+    "ldap": {
+      "enable_ldap": "Activer le LDAP",
+      "server_url_detail": "L'URL du répertoire LDAP  <code>ldap://host:port/DN</code> ou <code>ldaps://host:port/DN</code>.",
+      "bind_mode": "Mode d'authentification",
+      "bind_manager": "Manager",
+      "bind_user": "Utilisateur",
+      "bind_DN_manager_detail": "Le DN du compte s'authentifiant",
+      "bind_DN_user_detail1": "Le filtre pour s'authentifier avec le répertoire",
+      "bind_DN_user_detail2": "Utiliser <code>&#123;&#123;username&#125;&#125;</code> pour lier avec le nom d'utilisateur de la page de connexion.",
+      "bind_DN_password": "Lier le mot de passe DN",
+      "bind_DN_password_manager_detail": "Le mot de passe pour le compte DN",
+      "bind_DN_password_user_detail": "Le mot de passe utilisé sur la page de connexion sera lié",
+      "search_filter": "Filtre de recherche",
+      "search_filter_detail1": "Filtre de l'utilisateur authentifié",
+      "search_filter_detail2": "Utiliser <code>&#123;&#123;username&#125;&#125;</code> pour lier avec le nom d'utilisateur de la page de connexion.",
+      "search_filter_detail3": "Si vide, le filtre <code>(uid=&#123;&#123;username&#125;&#125;)</code> est utilisé.",
+      "search_filter_example1": "Faire correspondre avec 'uid' ou 'mail'",
+      "search_filter_example2": "Faire correspondre 'sAMAccountName' pour Active Directory",
+      "username_detail": "Spécifications des liaisons <code>username</code> lors de la création de nouveaux utilisateurs",
+      "name_detail": "Spécifications des liaisons pour le nom complet lors de la création de nouveaux utilisateurs",
+      "mail_detail": "Spécifications des liaisons pour l'adresse courriel lors de la création de nouveaux utilisateurs",
+      "group_search_base_DN": "Group Search Base DN",
+      "group_search_base_DN_detail": "Le DN de base pour rechercher les groupes. Si défini, <code>Filtre de recherche de groupe</code> doit être défini.",
+      "group_search_filter": "Filtre de recherche de groupe",
+      "group_search_filter_detail1": "Le filtre utilisé pour rechercher les groupes",
+      "group_search_filter_detail2": "La connexion par LDAP est acceptée seulement lorsque le filtre trouve un ou plusieurs groupes correspondants.",
+      "group_search_filter_detail3": "Utiliser <code>&#123;&#123;dn&#125;&#125;</code> pour le remplacer de l'objet utilisateur trouvé",
+      "group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> hits the groups which has <code>cn=group1</code> and <code>memberUid</code> includes the user's <code>uid</code>(when <code>Group DN Property</code> is not changed from the default value.)",
+      "group_search_user_DN_property": "Propriété DN de l'utilisateur",
+      "group_search_user_DN_property_detail": "La propriété de l'objet utilisateur à utiliser pour l'interpolation <code>&#123;&#123;dn&#125;&#125;</code> de <code>Filtre de recherche de groupe</code>.",
+      "test_config": "Tester la configuration",
+      "updated_ldap": "Paramètres mis à jour"
+    },
+    "SAML": {
+      "name": "SAML",
+      "enable_saml": "Activer le SAML",
+      "id_detail": "Spécifications de l'attribut de nom pour identifier l'utilisateur dans le fournisseur d'identité SAML",
+      "username_detail": "Spécifications des liaisons <code>username</code> lors de la création de nouveaux utilisateurs",
+      "mapping_detail": "Spécifications des liaisons {{target}} lors de la création de nouveaux utilisateurs",
+      "cert_detail": "Certificat PEM encodé X.509 pour validation de la réponse du fournisseur d'identité",
+      "Use env var if empty": "Si vide, la valeur de la variable d'environnement <code>{{env}}</code> est utilisé.",
+      "note for the only env option": "Utilise uniquement les valeurs de variables d'environnement.",
+      "attr_based_login_control_detail": "Restreindre qui peut s'inscrire avec <code>&lt;saml: Attribute&gt;</code> inclut dans <code>&lt;saml: AttributeStatement&gt;</code> et <code>&lt;saml: AttributeValue&gt;</code>.",
+      "attr_based_login_control_rule_help": "<h5>Filtres valides:</h5><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h5>Filtres invalides:</h5><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul><h5>Escaping special characters</h5>Il est nécessaire d'échapper les caractères suivants:<br><code>+ - && || ! ( ) { } [ ] ^ &quot; &tilde; * ? : &#92;</code> et <code>/</code>",
+      "attr_based_login_control_rule_example1": "<h5>Exemple de conditions</h5>Si un filtre est <code>(Department: A || Department: B) && Position: Leader</code>, les utilisateurs ayant <code>Department: A</code> ou <code>Department: B</code> et <code>Position: Leader</code> <strong>peuvent</strong> se connecter.",
+      "attr_based_login_control_rule_example2": "<h5>Exemple d'échappement</h5>Si une URL est utilisé comme filtre, échapper les caractères suivants:<br><code>http&#92;:&#92;/&#92;/schemas.example.com&#92;/ws&#92;/2005&#92;/05&#92;/identity&#92;/claims&#92;/emailaddress: &quot;myname@example.com&quot;</code>",
+      "updated_saml": "Paramètres mis à jour"
+    },
+    "OAuth": {
+      "enable_oidc": "Activer le OIDC",
+      "register": "Register for %s",
+      "change_redirect_url": "Entrer <code>%s</code> <br>(où <code>%s</code> est le nom d'hôte) pour \"URL de redirection autorisés\".",
+      "Google": {
+        "enable_google": "Activer Google OAuth",
+        "name": "Google OAuth",
+        "register_1": "Accéder à {{link}}",
+        "register_2": "Créer un nouveau projet",
+        "register_3": "Créer un identifiant &rightarrow; OAuth client ID &rightarrow; Sélectionner \"Web application\"",
+        "register_4": "Configurer l'application OAuth avec l'un des URL de redirection autorisés avec <code>{{url}}</code>",
+        "register_5": "Copier l'ID client et Secret client ci-dessus",
+        "updated_google": "Paramètres mis à jour"
+      },
+      "Facebook": {
+        "name": "Facebook OAuth"
+      },
+      "GitHub": {
+        "enable_github": "Activer GitHub OAuth",
+        "name": "GitHub OAuth",
+        "register_1": "Accéder à {{link}}",
+        "register_2": "Configurer l'application OAuth avec l'un des URL de redirection autorisés avec <code>{{url}}</code>",
+        "register_3": "Copier l'ID client et Secret client ci-dessus",
+        "updated_github": "Paramètres mis à jour"
+      },
+      "OIDC": {
+        "name": "OpenID Connect",
+        "id_detail": "Specification of the name of attribute which can identify the user in OIDC claims",
+        "username_detail": "Spécifications des liaisons <code>username</code> lors de la création de nouveaux utilisateurs",
+        "name_detail": "Spécifications des liaisons <code>name</code> lors de la création de nouveaux utilisateurs",
+        "mapping_detail": "Spécifications des liaisons pour %s lors de la création de nouveaux utilisateurs",
+        "register_1": "Contacter votre administrateur OIDC",
+        "register_2": "Configurer l'application OAuth avec l'un des URL de redirection autorisés avec <code>%s</code>",
+        "register_3": "Copier l'ID client et Secret client ci-dessus",
+        "updated_oidc": "Paramètres mis à jour",
+        "Use discovered URL if empty": "Use discovered URL from \"Issuer Host\" if empty"
+      },
+      "how_to": {
+        "google": "Comment configurer Google OAuth?",
+        "github": "Comment configurer GitHub OAuth?",
+        "oidc": "Comment configurer OIDC?"
+      }
+    },
+    "form_item_name": {
+      "entryPoint": "Point d'entrée",
+      "issuer": "Émetteur",
+      "cert": "Certificat",
+      "attrMapId": "ID",
+      "attrMapUsername": "Nom d'utilisateur",
+      "attrMapMail": "Adresse courriel",
+      "attrMapFirstName": "Prénom",
+      "attrMapLastName": "Nom",
+      "ABLCRule": "Règle"
+    }
+  },
+  "notification_settings": {
+    "notification_settings": "Paramètres de notification",
+    "slack_incoming_configuration": "Configuration de webhook entrants Slack",
+    "prioritize_webhook": "Prioriser le webhook entrant plutôt que Slack",
+    "prioritize_webhook_desc": "Activer cette option utilisera les webhook entrants plutôt que Slack.",
+    "slack_app_configuration": "Configuration de l'application Slack",
+    "slack_app_configuration_desc": "Cette méthode n'est pas recommandée, car trop complexe.",
+    "use_instead":"Utiliser plutôt les webhook entrants Slack",
+    "how_to": {
+      "header": "Comment configurer un webhook entrant?",
+      "workspace": "(Dans le Workspace) Ajouter un webhook",
+      "workspace_desc1": "Se diriger vers la page de <a href='https://slack.com/services/new/incoming-webhook'>configuration de webhooks entrants</a>.",
+      "workspace_desc2": "Choisir un canal par défaut",
+      "workspace_desc3": "Ajouter.",
+      "at_growi": "(Page d'administration GROWI) Ajouter l'URL du webhook",
+      "at_growi_desc": "Ajouter &rdquo;Webhook URL&rdquo; et sauvegarder."
+    },
+    "user_trigger_notification_header": "Paramètres de notifications par défaut",
+    "pattern": "Schéma",
+    "channel": "Canal",
+    "pattern_desc": "Chemin du wiki. Un schéma avec <code>*</code> peut être utilisé.",
+    "channel_desc": "Nom du canal Slack. Omettre le <code>#</code>.",
+    "valid_page": "Activer/désactiver les notifications",
+    "link_notification_help": "<strong>Les pages accessibles uniquement par le lien 'Tout le monde avec lien'</strong> n'envoient pas toujours de notifications.",
+    "just_me_notification_help": "<strong>Les pages restreintes à 'Seulement moi'</strong> envoient des notifications lors de modifications.",
+    "group_notification_help": "<strong>Les pages restreintes à 'Groupe d'utilisateur'</strong> envoient des notifications lors de modifications.",
+    "notification_list": "Paramètres de notifications",
+    "add_notification": "Ajouter",
+    "trigger_path": "Chemin déclencheur",
+    "trigger_path_help": "(les expressions avec <code>*</code> sont acceptées)",
+    "trigger_events": "Déclencheur",
+    "notify_to": "Notifier à",
+    "back_to_list": "Retour à la liste",
+    "notification_detail": "Détails",
+    "event_pageCreate": "Lors de la \"création\"",
+    "event_pageEdit": "Lors de l'\"édition\"",
+    "event_pageDelete": "Lors de la \"suppression\"",
+    "event_pageMove": "Lors d'un \"déplacement\" (page renommée)",
+    "event_pageLike": "Lorsqu'une page est \"aimé\"",
+    "event_comment": "Lors d'un \"commentaire\"",
+    "email": {
+      "ifttt_link": "Créer un nouvel applet IFTTT lors de l'envoi de courriels"
+    },
+    "updated_slackApp": "Paramètres mis à jour",
+    "add_notification_pattern": "Ajouter un schéma de notification",
+    "delete_notification_pattern": "Supprimer un schéma de notification",
+    "delete_notification_pattern_desc1": "Chemin de suppression: {{path}}",
+    "delete_notification_pattern_desc2": "Sera supprimé définitivement",
+    "toggle_notification": "Paramètres mis à jour pour {{path}}",
+    "not_found_global_notification_triggerid": "ID global de notification introuvable"
+  },
+  "full_text_search_management": {
+    "full_text_search_management": "Configuration de la recherche",
+    "elasticsearch_management": "Configuration Elasticsearch",
+    "connection_status": "Statut",
+    "connection_status_label_unconfigured": "UNCONFIGURED",
+    "connection_status_label_connected": "CONNECTED",
+    "connection_status_label_disconnected": "DISCONNECTED",
+    "connection_status_label_erroroccured": "ERROR OCCURED ON SEARCH SERVICE",
+    "indices_status": "Statut des indices",
+    "indices_status_label_normalized": "NORMALIZED",
+    "indices_status_label_unnormalized": "REBUILDING ou BROKEN",
+    "indices_summary": "Résumé des indices",
+    "reconnect": "Reconnexion",
+    "reconnect_button": "Reconnecter",
+    "reconnect_description": "Faire un essai de connexion vers Elasticsearch.",
+    "normalize": "Normaliser",
+    "normalize_button": "Normaliser les indices",
+    "normalize_description": "Réparer les indices cassés.",
+    "rebuild": "Reconstruire",
+    "rebuild_button": "Reconstruire",
+    "rebuild_description_1": "Reconstruire l'index est les données de pages",
+    "rebuild_description_2": "Cela peut prendre un certain temps."
+  },
+  "mailer_setup_required":"<a href='/admin/app'>Configuration Email</a> sont requis pour envoyer.",
+  "admin_top": {
+    "management_wiki": "Configuration du wiki",
+    "system_information": "Information système",
+    "wiki_administrator": "Seuls les administrateurs peuvent accéder à cette page",
+    "assign_administrator": "Il est possible d'assigner l'accès administrateur en utilisant le bouton 'Ajouter accès administrateur'",
+    "package_name": "Nom du paquet",
+    "specified_version": "Version spécifiée",
+    "installed_version": "Version installée",
+    "list_of_env_vars":"Variables d'environnement",
+    "env_var_priority": "Les valeurs de la base de données sont priorisées.",
+    "about_security": "Voir les <a href='/admin/security'>paramètres de sécurité</a> pour les variables d'environnement de sécurité.",
+    "copy_prefilled_host_information": {
+      "default": "Copier les informations",
+      "done": "Copié dans le presse-papier!"
+    },
+    "bug_report": "Soumettre un rapport de bogue",
+    "submit_bug_report": "<a href='https://github.com/weseek/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>soummettre ensuite sur GitHub.</a>"
+  },
+  "v5_page_migration": {
+    "migration_desc": "Des pages sont encore en V4. Pour profiter des nouvelles fonctionnalitées, convertir toutes les pages vers la V5.",
+    "migration_note": "Note: Les contraintes uniques seront écrasées.",
+    "upgrade_to_v5": "Convertir vers la V5",
+    "modal_migration_warning": "La conversion peut prendre un certain. Il est recommandé d'empêcher la création, modification et suppression de pages durant ce processus.",
+    "start_upgrading": "Convertir vers la V5",
+    "successfully_started": "Conversion réussie.",
+    "already_upgraded": "Conversion déjà effectuée.",
+    "header_upgrading_progress": "Progression de la conversion",
+    "migration_succeeded": "Conversion réussie! Le mode maintenance peut être désactivée et GROWI utilisé.",
+    "migration_failed": "Conversion échouée. Lire la documentation GROWI pour des informations supplémentaires."
+  },
+  "maintenance_mode": {
+    "maintenance_mode": "Mode maintenance",
+    "under_maintenance_mode": "Mode maintenance activé",
+    "failed_to_start_maintenance_mode": "Échec de démarrage du mode maintenance.",
+    "failed_to_end_maintenance_mode": "Échec de fin du mode maintenance.",
+    "successfully_started_maintenance_mode": "Mode maintenance activé",
+    "successfully_ended_maintenance_mode": "Mode maintenance désactivé",
+    "warning_message_to_start": "Le mode maintenance restreint l'utilisation de GROWI. Seul la page d'administration sera accessible.",
+    "warning_message_to_end": "Vérifier que l'\"import de données\" ou la \"conversion vers la V5\" soit effectué. Sinon, laisser le mode maintenance activé.",
+    "supplymentary_message_to_start": "Seul l'API d'administration sera actif.",
+    "start_maintenance_mode": "Activer le mode maitenance",
+    "end_maintenance_mode": "Désactiver le mode maitenance",
+    "description": "Le mode maintenance restreint l'utilisation de GROWI. Toujours démarrer le mode maintenance avant l'\"import de données\" et la \"conversion vers la V5\"."
+  },
+  "app_setting": {
+    "site_name": "Nom du site",
+    "sitename_change": "Le nom du site utilisé dans l'en-tête et le titre HTML.",
+    "header_content": "Le contenu entré ici sera affiché dans l'en-tête, etc. ",
+    "site_url": {
+      "title": "Configuration de l'URL du site",
+      "desc": "Configuration de l'URL du site",
+      "warn": "Certaines fonctionnalitées peuvent ne pas fonctionner tant que l'URL du site n'est pas définie.",
+      "help": "URL complet du site démarrant par <code>http://</code> ou <code>https://</code>.",
+      "note_for_the_only_env_option": "Les paramètres sont définis par des variables d'environnement.<br>Pour modifier ce paramètre, supprimer la variable d'environnement <code>{{env}}</code> ."
+    },
+    "confidential_name": "Nom confidentiel",
+    "confidential_example": "ex): usage interne seulement",
+    "default_language": "Langue par défaut",
+    "default_mail_visibility": "Afficher l'adresse courriel pour les nouveaux utilisateurs",
+    "file_uploading": "Téléversement de fichiers",
+    "enable_files_except_image": "Autorise le téléversement de fichiers de n'importe quel type. Lorsque désactivé, seul les fichiers de type image sont autorisés.",
+    "attach_enable": "Autorise le téléversement de fichiers de n'importe quel type",
+    "update": "Sauvegarder",
+    "mail_settings": "Configuration e-mail",
+    "mailer_is_not_set_up": "Paramètres e-mail non configurés.",
+    "from_e-mail_address": "Adresse courriel <code>from</code>",
+    "transmission_method":"Méthode de transmission",
+    "smtp_label":"SMTP",
+    "ses_label":"SES(AWS)",
+    "send_test_email": "Envoi d'un courriel d'essai",
+    "success_to_send_test_email": "Courriel d'essai envoyé",
+    "smtp_settings": "Configuration SMTP",
+    "host": "Hôte",
+    "port": "Port",
+    "user": "Utilisateur",
+    "initialize_mail_settings": "réinitialiser les paramètres e-mail",
+    "initialize_mail_modal_header": "Réinitialiser les paramètres e-mail",
+    "confirm_to_initialize_mail_settings": "Les valeurs existantes seront écrasées. Réinitialiser les paramètres e-mail?",
+    "file_upload_settings":"Configuration du téléversement",
+    "file_upload_method":"Méthode de téléversement",
+    "file_delivery_method":"Méthode de récupération",
+    "file_delivery_method_redirect":"Rediriger",
+    "file_delivery_method_relay":"Relai interne du système",
+    "file_delivery_method_redirect_info":"Rediriger: Redirige vers une URL signé, performance excellente.",
+    "file_delivery_method_relay_info":"Relai interne du système: Le serveur GROWI sert les fichiers directement au client, sécurité complète.",
+    "fixed_by_env_var": "Défini par une variable d'environnement <code>FILE_UPLOAD={{fileUploadType}}</code>.",
+    "gcs_label": "GCP(GCS)",
+    "aws_label": "AWS(S3)",
+    "local_label": "Local",
+    "gridfs_label": "MongoDB(GridFS)",
+    "azure_label": "Azure(Blob)",
+    "azure_tenant_id": "ID tenant",
+    "azure_client_id": "ID client",
+    "azure_client_secret": "Secret client",
+    "azure_storage_account_name": "Nom du compte de stockage",
+    "azure_storage_container_name": "Nom du conteneur",
+    "azure_note_for_the_only_env_option": "Les paramètres sont définis par des variables d'environnement.<br>Pour modifier ce paramètre, supprimer la variable d'environnement <code>{{env}}</code> .",
+    "file_upload": "Téléversement de fichiers",
+    "test_connection": "Essai de la connection e-mail",
+    "change_setting": "Si ce paramètre n'est pas complètement configuré, les fichiers existants seront inaccessibles.",
+    "region": "Région",
+    "bucket_name": "Nom du bucket",
+    "custom_endpoint": "URL personnalisée",
+    "custom_endpoint_change": "URL d'un service de stockage d'objet tel que MinIO qui a un API compatible S3. Par défaut, Amazon S3 est utilisé",
+    "s3_secret_access_key_input_description": "Valeur cachée",
+    "load_plugins": "Charger les plugins",
+    "enable": "Activer",
+    "disable": "Désactiver",
+    "use_env_var_if_empty": "Si la valeur dans la base de données est vide, la valeur de variable d'environnement <code>{{variable}}</code> est utilisé.",
+    "note_for_the_only_env_option": "Les paramètres sont définis par des variables d'environnement.<br>Pour modifier ce paramètre, supprimer la variable d'environnement <code>{{env}}</code> .",
+    "questionnaire_settings": "Données analytiques",
+    "questionnaire_settings_explanation": "Paramètres d'activation des données analytiques. L'utilisateur peut choisir ce paramètre individuellement dans \"Autres paramètres\".",
+    "about_data_sent": "À propos",
+    "docs_link": "https://docs.growi.org/en/admin-guide/management-cookbook/app-settings.html#questionnaire-settings",
+    "learn_more": "En savoir plus",
+    "other_info_will_be_sent": "En plus des données analytiques, des données diagnostiques pour améliorer GROWI sont envoyées. Les données personnelles ne sont pas incluses.",
+    "we_will_use_the_data_to_improve_growi": "Les données seront utilisées pour améliorer au mieux GROWI",
+    "anonymize_app_site_url": "Ne pas inclure l'URL du site",
+    "url_anonymization_explanation": "L'URL du site configurée ne sera pas inclue dans les données envoyées.",
+    "enable_questionnaire": "Activer les données analytiques"
+  },
+  "markdown_settings": {
+    "markdown_settings": "Configuration Markdown",
+    "lineBreak_header": "Configuration du saut de ligne",
+    "lineBreak_desc": "Configuration du saut de ligne.",
+    "lineBreak_options": {
+      "enable_lineBreak": "Activer le saut de ligne",
+      "enable_lineBreak_desc": "Convertir le saut de ligne<code>&lt;br&gt;</code>en HTML",
+      "enable_lineBreak_for_comment": "Activer le saut de ligne dans les commentaires",
+      "enable_lineBreak_for_comment_desc": "Convertir le saut de ligne dans les commentaires<code>&lt;br&gt;</code>en HTML"
+    },
+    "indent_header": "Configuration de l'indentation",
+    "indent_desc": "Configuration de l'indentation",
+    "indent_options": {
+      "indentSize": "Taille par défaut",
+      "indentSize_desc": "Taille par défaut de l'indentation dans l'éditeur Markdown",
+      "disallow_indent_change": "Empêcher le changement de taille d'indentation",
+      "disallow_indent_change_desc": "Forcer l'usage de la taille par défaut"
+    },
+    "xss_header": "Configuration prévention XSS",
+    "xss_desc": "Configuration de la prévention des attaques XSS (cross-site scripting).",
+    "xss_options": {
+      "enable_xss_prevention": "Activer prévention XSS",
+      "remove_all_tags": "Retirer tout les tags",
+      "remove_all_tags_desc": "Retire tout les tags HTML et CSS",
+      "recommended_setting": "Paramètres recommandés",
+      "custom_whitelist": "Liste autorisée",
+      "tag_names": "Nom de tags",
+      "tag_attributes": "Attributs de tags",
+      "import_recommended": "Importer les recommendations {{target}}"
+    }
+  },
+  "customize_settings": {
+    "customize_settings": "Personnalisation",
+    "default_sidebar_mode": {
+      "title": "Mode par défaut de la barre latérale",
+      "desc": "Le mode d'affichage par défaut de la barre latérale pour les utilisateurs.",
+      "dock_mode_default_desc": "État initial de la barre latérale lorsque le mode Dock est sélectionné.",
+      "dock_mode_default_open": "Afficher la page comme si elle était ouverte",
+      "dock_mode_default_close": "Afficher la page comme si elle était fermée"
+    },
+    "layout": "Agencement",
+    "layout_options": {
+      "default": "Largeur par défaut",
+      "expanded": "100%"
+    },
+    "theme": "Thème",
+    "theme_desc": {
+      "light_and_dark": "Modes hybrides",
+      "unique": "Foncé ou clair"
+    },
+    "function": "Fonctionnalités",
+    "function_desc": "Des fonctionnalitées additionnelles peuvent être activées",
+    "function_options": {
+      "timeline": "Chronologie",
+      "timeline_desc1": "Afficher la chronologie des pages",
+      "timeline_desc2": "Peut affecter la performance si beaucoup de pages sont présentes",
+      "timeline_desc3": "Le chargement peut être améliorés en invalidant.",
+      "tab_switch": "Sauvegarder le changement d'onglets",
+      "tab_switch_desc1": "Sauvegarde l'état de navigation dans le navigateur de l'utilisateur.",
+      "tab_switch_desc2": "Lorsque désactivé, la navigation est forcé par l'interface.",
+      "attach_title_header": "Ajouter automatiquement une section h1",
+      "attach_title_header_desc": "Ajoute le chemin de la page en tant que h1 lors de création d'une page.",
+      "list_num_s": "Nombre de pages modales",
+      "list_num_desc_s": "Nombre de pages affichées sur les modales",
+      "list_num_m": "Nombre de pages articles",
+      "list_num_desc_m": "Nombre de pages affichées dans les 'favoris' ou les pages crées récemment.",
+      "list_num_l": "Nombre de pages recherche",
+      "list_num_desc_l": "Nombre de pages affichées lors de la recherche",
+      "list_num_xl": "Nombre de pages articles",
+      "list_num_desc_xl": "Nombre de pages affichées dans la 'corbeille' ou '404'.",
+      "stale_notification": "Afficher les anciennes notifications",
+      "stale_notification_desc": "Affiche les notifications sur les pages mises à jour il y a plus d'un an",
+      "show_all_reply_comments": "Afficher tout les commentaires",
+      "show_all_reply_comments_desc": "Lorsque désactivé, seul les deux commentaires les plus récents sont affichés",
+      "select_search_scope_children_as_default": "'Seulement enfant de ce chemin' lors de la recherche",
+      "select_search_scope_children_as_default_desc": "Lorsque désactivé, utilise 'Toutes les pages' en portée de recherche."
+    },
+      "presentation": "Présentation",
+    "presentation_options": {
+      "enable_marp": "Activer Marp",
+      "enable_marp_desc": "Marp est utilisable dans la visualisation de présentation. Potentiellement vulnérable aux attaques XSS.",
+      "marp_official_site": "Site officiel Marp",
+      "marp_official_site_link": "https://marp.app",
+      "marp_in_growi" : "GROWI Docs - Créer des présentations avec Marp",
+      "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html"
+    },
+    "custom_title": "Titre personnalisé",
+    "custom_title_detail": "Le tag <code>&lt;title&gt;</code> est personnalisable. Les variables suivantes seront automatiquement remplacées:",
+    "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - Nom du wiki.",
+    "custom_title_detail_placeholder2": "<code>&#123;&#123;pagename&#125;&#125;</code> - Nom de la page actuelle.",
+    "custom_title_detail_placeholder3": "<code>&#123;&#123;pagepath&#125;&#125;</code> - Chemin de la page actuelle.",
+    "custom_noscript": "Noscript personnalisé",
+    "custom_noscript_detail": "Il est possible d'ajouter du code Noscript. Il sera inséré dans le tag <code>&lt;noscript&gt;</code>.",
+    "custom_css": "CSS personnalisé",
+    "write_css": "CSS personnalisé.",
+    "ctrl_space": "Ctrl+Space pour l'autocomplétion",
+    "custom_script": "Script personnalisé",
+    "custom_presentation": "Presentation personnalisé",
+    "write_java": "Code javascript qui sera appliqué au système entier.",
+    "reflect_change": "Un rechargement de la page est nécessaire pour afficher les changements.",
+    "custom_logo" : "Logo personnalisé",
+    "default_logo": "Logo par défaut",
+    "upload_logo": "Téléverser un logo",
+    "current_logo": "Logo actuel",
+    "upload_new_logo": "Téléverser un nouveau logo",
+    "delete_logo": "Supprimer le logo"
+  },
+  "importer_management": {
+    "import_data": "Importer des données",
+    "article": "Article",
+    "category": "Catégorie",
+    "tag": "Tag",
+    "page": "Page",
+    "page_path": "Chemin de page",
+    "beta_warning": "Cette fonctionnalité est en beta.",
+    "import_from": "Importer depuis {{from}}",
+    "import_growi_archive": "Importer une archive GROWI",
+    "error": {
+      "only_upsert_available": "Seul l'option 'Upsert' est disponible pour les collections de pages"
+    },
+    "growi_settings": {
+      "description_of_import_mode": {
+        "about": "Lors de l'import de données et d'un conflit avec les données actuelles, choisir l'une des trois options",
+        "insert": "Insert: Passe l'import",
+        "upsert": "Upsert: crit et met à jour le contenu existant",
+        "flash_and_insert": "Flash and Insert: Supprime les données existantes et effectue l'import"
+      },
+      "growi_archive_file": "Fichier d'archive GROWI",
+      "uploaded_data": "Données téléversées",
+      "extracted_file": "Fichier extrait",
+      "collection": "Collection",
+      "upload": "Téléverser",
+      "discard": "Jeter les données téléversées",
+      "errors": {
+        "different_versions": "La version de GROWI ne correspond pas aux données téléversées",
+        "at_least_one": "Séléctionner au moins une collection.",
+        "page_and_revision": "Les 'Pages' et 'Revisions' doivent être importés ensemble",
+        "depends": "'{{target}}' doit être sélectionné lorsque '{{condition}}' est sélectionné."
+      },
+      "configuration": {
+        "pages": {
+          "overwrite_author": {
+            "label": "Écrase l'auteur de la page avec l'utilisateur actuel",
+            "desc": "Il est recommandé de <span class=\"text-danger\">NE PAS</span> activer cette option lorsque les utilisateurs sont également importés."
+          },
+          "set_public_to_page": {
+            "label": "Rend les pages de '{{from}}' 'publiques'",
+            "desc": "Rend toutes les pages de <b>'{{from}}'</b> accessibles en lecture par <span class=\"text-danger\">TOUT les utilisateurs</span>."
+          },
+          "initialize_meta_datas": {
+            "label": "Réinitialiser les compteurs de page",
+            "desc": "Il est recommandé de <span class=\"text-danger\">NE PAS</span> activer cette option lorsque les utilisateurs sont également importés"
+          }
+        },
+        "revisions": {
+          "overwrite_author": {
+            "label": "Écrase l'auteur de la révision avec l'utilisateur actuel",
+            "desc": "Il est recommandé de <span class=\"text-danger\">NE PAS</span> activer cette option lorsque les utilisateurs sont également importés"
+          }
+        }
+      }
+    },
+    "esa_settings": {
+      "team_name": "Nom de l'équipe",
+      "access_token": "Jeton d'accès",
+      "test_connection": "Essai de la connection esa"
+    },
+    "qiita_settings": {
+      "team_name": "Nom de l'équipe",
+      "access_token": "Jeton d'accès",
+      "test_connection": "Essai de la connection qiita:team"
+    },
+    "import": "Importer",
+    "skip_username_and_email_when_overlapped": "Passe le nom et adresse courriel exactes dans le nouvel environnement",
+    "prepare_new_account_for_migration":"Préparer le compte pour la migration",
+    "archive_data_import_detail":"En savoir plus",
+    "admin_archive_data_import_guide_url":"https://docs.growi.org/en/admin-guide/management-cookbook/import.html",
+    "page_skip": "Les pages ayant le nom d'une page déjà existante ne seront pas importées.",
+    "Directory_hierarchy_tag": "Tag de hiérarchie"
+  },
+  "export_management": {
+    "export_archive_data": "Archive de données d'export",
+    "exporting_collection_list": "Export des collections",
+    "exported_data_list": "Données exportées",
+    "export_collections": "Exporter les collections",
+    "check_all": "Activer tout",
+    "uncheck_all": "Désactiver tout",
+    "desc_password_seed": "<p>NE PAS OUBLIER de mettre à jour <code>PASSWORD_SEED</code> dans le nouveau système GROWI, ou les utilisateurs ne pourront pas accéder au système. <br><strong>HINT:</strong><br>The current <code>PASSWORD_SEED</code> will be stored in <code>meta.json</code> in exported ZIP.</p>",
+    "create_new_archive_data": "Créer une nouvelle archive",
+    "export": "Exporter",
+    "cancel": "Annuler",
+    "file": "Fichier",
+    "growi_version": "Version de GROWI",
+    "collections": "Collections",
+    "exported_at": "Date d'export",
+    "export_menu": "Menu d'export",
+    "download": "Télécharger",
+    "delete": "Supprimer"
+  },
+  "external_notification": {
+    "external_notification": "Notifications externes",
+    "enabled": "Activé",
+    "disabled": "Désactivé",
+    "header_status": "Statut de l'intégration Slack",
+    "caution_enabled": "Les notification s'enverront seulement au workspace primaire Slack."
+  },
+  "slack_integration": {
+    "slack_integration": "Intégration Slack",
+    "selecting_bot_types": {
+      "slack_bot": "Slack bot",
+      "official_bot": "Bot officiel",
+      "custom_bot": "Bot personnalisé",
+      "without_proxy": "sans proxy",
+      "with_proxy": "avec proxy",
+      "recommended": "Recommendé",
+      "set_up": "Configurer",
+      "multiple_workspaces_integration": "Intégration avec plusieurs workspaces",
+      "security_control": "Vérification de sécurité",
+      "easy": "Facile",
+      "normal": "Normal",
+      "hard": "Difficile",
+      "possible": "Possible",
+      "impossible": "Impossible"
+    },
+    "bot_reset_successful": "Les paramètres de bot ont été réinitialisés.",
+    "adding_slack_ws_integration_settings_successful": "Les paramètres ont été ajoutés.",
+    "bot_all_reset_successful": "Tout les paramètres de bot ont été réinitialisés",
+    "copied_to_clipboard": "Copié dans le presse-papier",
+    "set_scope": "Configurer les jetons d'accès du bot depuis Slack",
+    "modal": {
+      "warning": "Attention",
+      "sure_change_bot_type": "Modifier le type de ce bot?",
+      "changes_will_be_deleted": "Les paramètres existants seront écrasés",
+      "cancel": "Annuler",
+      "change": "Modifier"
+    },
+    "toastr": {
+      "delete_slack_integration_procedure": "Paramètres de l'intégration Slack supprimés."
+    },
+    "use_env_var_if_empty": "Si la valeur dans la base de données est vide, la valeur de variable d'environnement <code>{{variable}}</code> est utilisé.",
+    "access_token_settings": {
+      "regenerate": "Réinitialiser"
+    },
+    "delete": "Supprimer",
+    "integration_procedure": "Procédure d'intégration",
+    "custom_bot_without_proxy_settings": "Bot Personnalisé sans proxy",
+    "integration_failed":"Échec de l'intégration",
+    "reset": "Réinitialiser",
+    "reset_all_settings": "Réinitialiser tout les paramètres",
+    "delete_slackbot_settings": "Supprimer les paramètres du bot Slack",
+    "slackbot_settings_notice": "La procédure d'intégration dans Slack sera supprimée. <br> Continuer?",
+    "all_settings_of_the_bot_will_be_reset": "Les paramètres seront réinitialisés.<br>Continuer?",
+    "accordion": {
+      "create_bot": "Créer bot",
+      "how_to_create_a_bot": "Comment créer un bot",
+      "how_to_install": "Comment installer",
+      "install_bot_to_slack": "Installer dans Slack",
+      "install_now": "Installer",
+      "generate_access_token": "Générer jeton d'accès",
+      "register_for_growi_official_bot_proxy_service": "Ajouter le bot proxy GROWI officiel",
+      "register_for_growi_custom_bot_proxy": "Ajout le bot GROWI officiel",
+      "enter_growi_register_on_slack": "Remplir <b>/growi register</b> dans Slack",
+      "paste_growi_url": "Remplir l'URL suivante dans <b>GROWI URL</b>.",
+      "enter_access_token_for_growi_and_proxy": "Remplir <b>Access Token Proxy to GROWI</b> et <b>Access Token GROWI to Proxy</b>",
+      "set_proxy_url_on_growi": "Configurer l'URL du proxy GROWI",
+      "copy_proxy_url": "Lorsque les étapes ci-dessus sont complétées, l'URL du proxy sera affiché dans le canal Slack sélectionné.",
+      "enter_proxy_url_and_update": "Entrer l'URL du proxy récupérée lors de l'étape précédente dans <b>Proxy URL</b>.",
+      "dont_need_update": "※Si la valeur est déjà remplie, il n'est pas nécessaire de la modifier.",
+      "select_install_your_app": "Sélectionner \"Installer votre application\".",
+      "go-to-manage-distribution": "Aller dans \"Manage Settings\" > \"Manage distribution\" dans la page de l'application Slack",
+      "activate-public-distribution": "Dans \"Share Your App with Other Workspaces\", vérifier que tous les items soit sélectionnés, puis cliquer \"Activate Public Distribution\"",
+      "click-add-to-slack-button": "Cliquer \"Add to Slack\".",
+      "select_install_to_workspace": "Sélectionner \"Install to Workspace\".",
+      "register_proxy_url": "Enregistrer l'URL du proxy avec GROWI",
+      "click_allow": "Sélectionner \"Allow\".",
+      "install_complete_if_checked": "Confirmer que \"Install your app\" est sélectionné.",
+      "invite_bot_to_channel": "Inviter le bot GROWI au canal en mentionnant @example.",
+      "register_secret_and_token": "Secret de signature et jeton d'accès du bot",
+      "manage_permission": "Gestion des permissions",
+      "growi_commands": "Commandes GROWI",
+      "multiple_growi_command": "Commandes utilisables avec plusieurs instances GROWI",
+      "single_growi_command": "Commandes utilisables avec une seule instance GROWI",
+      "allowed_channels_description": "Ajouter les canaux autorisés pour la commande \"{{keyName}}\". Séparer chaque élément avec \",\" . Les utilisateurs pourront utiliser la commande \"{{keyName}}\" depuis les canaux spécifiés.",
+      "unfurl_description": "Montrer le contenu de la page GROWI lorsque son lien est partagé sur Slack",
+      "unfurl_allowed_channels_description": "Ajouter les ID des canaux autorisés pour \"déployer\" . Séparer chaque élément avec \",\" . Le contenu d'une page publique GROWI sera visible lorsque son lien est partagé sur Slack",
+      "allow_all": "Tout autoriser",
+      "deny_all": "Tout refuser",
+      "allow_specified": "Autoriser sélectionnés",
+      "allow_all_long": "Tout autoriser (Depuis tout les canaux)",
+      "deny_all_long": "Tout refuser (Depuis tout les canaux)",
+      "allow_specified_long": "Autoriser sélectionnés (Depuis les canaux sélectionnés)",
+      "test_connection": "Tester la connexion",
+      "test_connection_by_pressing_button": "Cliquer sur le bouton pour tester la connexion",
+      "test_connection_only_public_channel":"Testez la connexion dans un canal publique.",
+      "error_check_logs_below": "Une erreur est survenue.",
+      "send_message_to_slack_work_space": "Envoyer un message vers l'espace de travail Slack.",
+      "add_slack_workspace": "Ajouter un espace de travail Slack"
+    },
+    "custom_bot_without_proxy_integration": "Bot personnalisé sans proxy",
+    "integration_sentence": {
+      "integration_is_not_complete": "Intégration échouée. <br>Procéder avec les instructions suivantes.",
+      "integration_successful": "Intégration réussie",
+      "integration_some_ws_is_not_complete": "Certains espaces de travail ne sont pas connectés"
+    },
+    "custom_bot_with_proxy_integration": "Bot personnalisé avec proxy",
+    "official_bot_integration": "Intégration officiel du bot",
+    "docs_url": {
+      "slack_integration": "https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/",
+      "official_bot": "https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/#official-bot-%E3%80%90recommended%E3%80%91",
+      "custom_bot_without_proxy": "https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/#custom-bot-without-proxy",
+      "custom_bot_with_proxy": "https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/#custom-bot-with-proxy",
+      "official_bot_setting": "https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html",
+      "custom_bot_without_proxy_setting": "https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/custom-bot-without-proxy-settings.html",
+      "custom_bot_with_proxy_setting": "https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/custom-bot-with-proxy-settings.html"
+    }
+  },
+  "slack_integration_legacy": {
+    "slack_integration_legacy": "Ancienne intégration Slack",
+    "alert_disabled": "'Ancienne intégration Slack' est désactivée depuis que <a href='/admin/slack-integration'>les nouveaux paramètres</a> sont actifs",
+    "alert_deplicated": "'Ancienne intégration Slack' sera discontinué dans le futur. Utiliser plutôt <a href='/admin/slack-integration'>les nouveaux paramètres</a> "
+  },
+  "user_management": {
+    "user_management": "Configuration des utilisateurs",
+    "invite_users": "Créer un nouvel utilisateur temporaire",
+    "click_twice_same_checkbox": "Il est nécessaire de sélectionner une option.",
+    "status": "Statut",
+    "invite_modal": {
+      "emails": "Adresse Courriel (Supporte l'usage de plusieurs lignes)",
+      "description1":"Créer des utilisateurs temporaires avec une adresse courriel.",
+      "description2":"Un mot de passe temporaire sera généré..",
+      "invite_thru_email": "Envoyer courriel d'invitation",
+      "mail_setting_link":"<span className='material-symbols-outlined me-2'>settings</span><a href='/admin/app'>Paramètres courriel</a>",
+      "valid_email": "Adresse courriel valide requise",
+      "temporary_password": "Cette utilisateur a un mot de passe temporaire",
+      "send_new_password": "Envoyez le nouveau mot de passe à l'utilisateur.",
+      "send_temporary_password": "Si un courriel d'invitation n'est pas envoyé, copiez le mot de passe temporaire",
+      "send_email": "Le courriel d'invitation peut aussi être envoyé depuis la table utilisateur",
+      "existing_email": "Des comptes avec les adresses courriel suivantes sont déjà existants",
+      "issue": "Créer"
+    },
+    "user_table": {
+      "administrator": "Administrateur",
+      "read_only": "Lecture seule",
+      "edit_menu": "Modifier menu",
+      "reset_password": "Réinitialiser mot de passe",
+      "administrator_menu": "Menu administrateur",
+      "accept": "Accepter",
+      "deactivate_account": "Désactiver compte",
+      "your_own": "Vous ne pouvez pas désactiver votre propre compte",
+      "revoke_admin_access": "Révoquer permission d'administration",
+      "cannot_revoke": "Vous ne pouvez pas révoquer votre propre permission d'administration",
+      "grant_admin_access": "Ajouter permission administrateur",
+      "revoke_read_only_access": "Révoquer permission de lecture",
+      "grant_read_only_access": "Ajouter permission de lecture",
+      "send_invitation_email": "Envoyer courriel d'invitation",
+      "resend_invitation_email": "Renvoyer courriel d'invitation"
+    },
+    "reset_password": "Réinitialiser mot de passe",
+    "reset_password_modal": {
+      "password_never_seen": "Le mot de passe temporaire ne sera plus visible",
+      "password_reset_message": "Il est fortement recommandé de modifier le nouveau mot de passe immédiatement après la connexion.",
+      "send_new_password": "Envoyez le nouveau mot de passe à l'utilisateur.",
+      "target_user": "Utilisateur cible",
+      "new_password": "Nouveau mot de passe"
+    },
+    "external_account": "Configuration des comptes externes",
+    "external_accounts":"Comptes externes",
+    "create_external_account":"Créer compte externe",
+    "external_account_list": "Liste des comptes externes",
+    "external_account_none":"Pas de compte externe",
+    "invite": "Inviter",
+    "invited": "Utilisateur invité",
+    "back_to_user_management": "Gestion des utilisateurs",
+    "authentication_provider": "Fournisseur d'authentification",
+    "manage": "Gérer",
+    "password_setting": "Configuration mot de passe",
+    "password_setting_help": "Mot de passe configuré?",
+    "set": "Oui",
+    "unset": "Non",
+    "related_username": "Utilisateur ",
+    "cannot_invite_maximum_users": "La limite maximale d'utilisateurs invitables est atteinte.",
+    "current_users": "Utilisateurs:"
+  },
+  "user_group_management": {
+    "user_group_management": "Configuration des groupes",
+    "create_group": "Créer nouveau groupe",
+    "add_child_group": "Ajouter groupe enfant",
+    "remove_child_group": "Retirer",
+    "deny_create_group": "Les paramètres actuels ne permettent pas la création du groupe",
+    "group_name": "Nom du groupe",
+    "group_example": "e.g. : Group1",
+    "child_user_group": "Groupe utilisateur enfant",
+    "parent_group": "Groupe parent",
+    "select_parent_group": "Sélectionner groupe parent",
+    "release_parent_group": "Libérer groupe parent",
+    "add_modal": {
+      "description": "L'utilisateur sera ajouté au groupe parent.",
+      "add_user": "Ajouter utilisateur au groupe",
+      "search_option": "Rechercher paramètre",
+      "enable_option": "Activer {{option}}",
+      "forward_match": "Correspondance avant",
+      "partial_match": "Correspondance partielle",
+      "backward_match": "Correspondance inversée"
+    },
+    "group_list": "Liste des groupes",
+    "child_group_list": "Liste des groupes enfants",
+    "back_to_list": "Retour à la liste",
+    "basic_info": "Information de base",
+    "user_list": "Liste des utilisateurs",
+    "created_group": "Groupe crée",
+    "is_loading_data": "Chargement...",
+    "no_pages": "Le groupe n'a pas la permission de voir la page.",
+    "remove_from_group": "Retirer l'utilisateur",
+    "delete_modal": {
+      "header": "Supprimer groupe",
+      "desc": "Les groupes enfants seront également supprimés. Une fois supprimé, le groupe et ses pages correspondantes seront innaccessibles.",
+      "dropdown_desc": "Choisir une action pour les pages privées",
+      "select_group": "Sélectionner un groupe",
+      "no_groups": "Aucun groupe",
+      "publish_pages": "Publier",
+      "delete_pages": "Supprimer",
+      "transfer_pages": "Transférer vers le groupe"
+    },
+    "update_parent_confirm_modal": {
+      "header": "Le parent de ce groupe sera modifié",
+      "caution_change_parent": "Cette opération modifiera le parent du groupe \"{{groupName}}\".",
+      "danger_message": "Cette action affecte les permissions de visionnage associées au groupe.",
+      "force_update_parents_label": "Forcer l'ajout d'utilisateurs manquants",
+      "force_update_parents_description": "Ajoute les utilisateurs manquants au groupe ancêtre si ils existent lors d'un changement de groupe parent."
+    }
+  },
+  "audit_log_management": {
+    "audit_log": "Journal d'audit",
+    "audit_log_settings": "Configuration des journaux d'audit",
+    "user": "Utilisateur",
+    "username": "Nom d'utilisateur",
+    "date": "Date",
+    "action": "Action",
+    "ip": "Adresse IP",
+    "url": "URL",
+    "settings": "Paramètres",
+    "return": "Retour",
+    "clear": "Vider",
+    "activity_expiration_date": "Expiration des journaux d'audit",
+    "activity_expiration_date_explanation": "Les journaux d'audit sont supprimés après le nombre de seconde spécifiées",
+    "fixed_by_env_var": "Configuré par la variable d'environnement <code>{{key}}={{value}}</code>.",
+    "available_action_list": "Rechercher une action",
+    "available_action_list_explanation": "Liste des actions pouvant être recherchées/vues",
+    "action_list": "Liste d'actions",
+    "disable_mode_explanation": "Cette fonctionnalité est désactivée. Afin de l'activer, mettre à jour <code>AUDIT_LOG_ENABLED</code> pour true.",
+    "docs_url": {
+      "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
+    }
+  },
+  "g2g_data_transfer": {
+    "transfer_data_to_another_growi": "Transférer les données de ce GROWI vers un autre GROWI",
+    "advanced_options": "Paramètres avancés",
+    "start_transfer": "Démarrer transfert",
+    "paste_transfer_key": "Copier clé de transfert ici"
+  },
+  "plugins": {
+    "plugins": "Plugins",
+    "plugin_installer": "Configuration de plugins",
+    "form": {
+      "label_url": "URL du plugin",
+      "desc_url": "Les plugins sont installables par URL",
+      "label_branch": "Branche",
+      "desc_branch": "Spécification du nom de la branche. Par défaut: `main`"
+    },
+    "plugin_card": "Plugins",
+    "plugin_is_not_installed": "Aucun plugins installés",
+    "install": "Installer",
+    "confirm": "Supprimer le plugin?"
+  },
+  "cloud_setting_management": {
+    "to_cloud_settings": "Ouvrir paramètres GROWI.cloud"
+  },
+  "audit_log_action_category": {
+    "Page": "Page",
+    "Comment": "Commentaire",
+    "Tag": "Tag",
+    "Attachment": "Pièce jointe",
+    "ShareLink": "Lien de partage",
+    "Search": "Rechercher",
+    "User": "Utilisateur",
+    "Admin": "Administrateur"
+  },
+  "audit_log_action": {
+    "USER_REGISTRATION_SUCCESS": "Création d'utilisateur",
+    "USER_LOGIN_WITH_LOCAL": "Connexion avec ID/Mot de passe",
+    "USER_LOGIN_WITH_LDAP": "Connexion avec LDAP",
+    "USER_LOGIN_WITH_GOOGLE": "Connexion avec Google",
+    "USER_LOGIN_WITH_GITHUB": "Connexion avec GitHub",
+    "USER_LOGIN_WITH_OIDC": "Connexion avec OIDC",
+    "USER_LOGIN_WITH_SAML": "Connexion avec SAML",
+    "USER_LOGIN_FAILURE": "Échec de connexion",
+    "USER_LOGOUT": "Déconnexion",
+    "USER_FOGOT_PASSWORD": "Demander une réinitialisation de mot de passe",
+    "USER_RESET_PASSWORD": "Réinitialiser mon mot de passe",
+    "USER_PERSONAL_SETTINGS_UPDATE": "Modifier mes paramètres",
+    "USER_IMAGE_TYPE_UPDATE": "User image type update",
+    "USER_LDAP_ACCOUNT_ASSOCIATE": "Associer compte LDAP",
+    "USER_LDAP_ACCOUNT_DISCONNECT": "Dissocier compte LDAP",
+    "USER_PASSWORD_UPDATE": "Modifier mot de passe",
+    "USER_API_TOKEN_UPDATE": "Modifier jeton API",
+    "USER_EDITOR_SETTINGS_UPDATE": "Modifier paramètre éditeur",
+    "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "Paramètres de notifications",
+    "USER_REGISTRATION_APPROVAL_REQUEST": "Demande d'inscription pour ID/Mot de passe",
+    "PAGE_VIEW": "Vue page",
+    "PAGE_USER_HOME_VIEW": "Page d'accueil utilisateur",
+    "PAGE_FORBIDDEN": "Page inaccessible",
+    "PAGE_NOT_FOUND": "Page introuvable",
+    "PAGE_NOT_CREATABLE": "Page non ajoutable",
+    "PAGE_LIKE": "Aimer",
+    "PAGE_UNLIKE": "Retirer réaction",
+    "PAGE_BOOKMARK": "Favori",
+    "PAGE_UNBOOKMARK": "Retirer des favoris",
+    "PAGE_CREATE": "Créer page",
+    "PAGE_UPDATE": "Modifier page",
+    "PAGE_RENAME": "Renommer page",
+    "PAGE_DUPLICATE": "Dupliquer page",
+    "PAGE_DELETE": "Supprimer page",
+    "PAGE_DELETE_COMPLETELY": "Supprimer définitivement page",
+    "PAGE_REVERT": "Historique page",
+    "PAGE_EMPTY_TRASH": "Vider la corbeille",
+    "PAGE_RECURSIVELY_RENAME": "Renommage récursif de pages",
+    "PAGE_RECURSIVELY_DELETE": "Suppression récursive de pages",
+    "PAGE_RECURSIVELY_DELETE_COMPLETELY": "Suppression récursive complète de pages",
+    "PAGE_RECURSIVELY_REVERT": "Historique récursif de page",
+    "PAGE_SUBSCRIBE": "S'abonner",
+    "PAGE_UNSUBSCRIBE": "Se désabonner",
+    "PAGE_EXPORT": "Exporter",
+    "TAG_UPDATE": "Modifier les tags",
+    "IN_APP_NOTIFICATION_ALL_STATUSES_OPEN": "Marquer toutes les notifications comme lues",
+    "COMMENT_CREATE": "Ajouter un commentaire",
+    "COMMENT_UPDATE": "Modifier un commentaire",
+    "COMMENT_REMOVE": "Supprimer un commentaire",
+    "SHARE_LINK_CREATE": "Ajouter un lien de partage",
+    "SHARE_LINK_DELETE": "Supprimer un lien de partage",
+    "SHARE_LINK_DELETE_BY_PAGE": "Supprimer les liens de partage de la page",
+    "SHARE_LINK_ALL_DELETE": "Supprimer tout les liens de partage",
+    "SHARE_LINK_PAGE_VIEW": "Liens de partage",
+    "SHARE_LINK_EXPIRED_PAGE_VIEW": "Liens de partage expirés",
+    "SHARE_LINK_NOT_FOUND": "Liens de partage introuvables",
+    "ATTACHMENT_ADD": "Ajouter une pièce jointe",
+    "ATTACHMENT_REMOVE": "Supprimer une pièce jointe",
+    "ATTACHMENT_DOWNLOAD": "Télécharger une pièce jointe",
+    "SEARCH_PAGE": "Rechercher une page",
+    "SEARCH_PAGE_VIEW": "Résultats de recherche",
+    "ADMIN_APP_SETTING_UPDATE": "Modifier les paramètres d'application",
+    "ADMIN_SITE_URL_UPDATE": "Modifier les paramètres d'URL",
+    "ADMIN_MAIL_SMTP_UPDATE": "Modifier les paramètres d'e-mail",
+    "ADMIN_MAIL_SES_UPDATE": "Modifier les paramètres d'e-mail (SES)",
+    "ADMIN_MAIL_TEST_SUBMIT" : "Envoyer courriel de test",
+    "ADMIN_FILE_UPLOAD_CONFIG_UPDATE": "Modifier paramètres de téléversemetnt de fichiers",
+    "ADMIN_PLUGIN_UPDATE": "Mettre à jour les paramètres de plugins",
+    "ADMIN_MAINTENANCEMODE_ENABLED": "Activer mode maintenance",
+    "ADMIN_MAINTENANCEMODE_DISABLED": "Désactiver mode maintenance",
+    "ADMIN_SECURITY_SETTINGS_UPDATE": "Mettre à jour les paramètres de sécurité",
+    "ADMIN_PERMIT_SHARE_LINK": "Activer liens de partage",
+    "ADMIN_REJECT_SHARE_LINK": "Désactiver liens de partage",
+    "ADMIN_AUTH_ID_PASS_ENABLED": "Activer authentification ID/Password",
+    "ADMIN_AUTH_ID_PASS_DISABLED": "Désactiver authentification ID/Password",
+    "ADMIN_AUTH_ID_PASS_UPDATE": "Mettre à jour les paramètres d'authentification ID/Password",
+    "ADMIN_AUTH_LDAP_ENABLED": "Activer LDAP auth",
+    "ADMIN_AUTH_LDAP_DISABLED": "Désactiver LDAP auth",
+    "ADMIN_AUTH_LDAP_UPDATE": "Mettre à jour les paramètres d'authentification LDAP",
+    "ADMIN_AUTH_SAML_ENABLED": "Activer SAML auth",
+    "ADMIN_AUTH_SAML_DISABLED": "Désactiver SAML auth",
+    "ADMIN_AUTH_SAML_UPDATE": "Mettre à jour les paramètres d'authentification SAML",
+    "ADMIN_AUTH_OIDC_ENABLED": "Activer OIDC auth",
+    "ADMIN_AUTH_OIDC_DISABLED": "Désactiver OIDC auth",
+    "ADMIN_AUTH_OIDC_UPDATE": "Mettre à jour les paramètres d'authentification OIDC settings",
+    "ADMIN_AUTH_GOOGLE_ENABLED": "Activer Google auth",
+    "ADMIN_AUTH_GOOGLE_DISABLED": "Désactiver Google auth",
+    "ADMIN_AUTH_GOOGLE_UPDATE": "Mettre à jour les paramètres d'authentification Google",
+    "ADMIN_AUTH_GITHUB_ENABLED": "Activer GitHub auth",
+    "ADMIN_AUTH_GITHUB_DISABLED": "Désactiver GitHub auth",
+    "ADMIN_AUTH_GITHUB_UPDATE": "Mettre à jour les paramètres d'authentification GitHub",
+    "ADMIN_MARKDOWN_LINE_BREAK_UPDATE": "Mettre à jour les paramètres de casse de liens",
+    "ADMIN_MARKDOWN_INDENT_UPDATE": "Mettre à jour les paramètres d'indentation",
+    "ADMIN_MARKDOWN_PRESENTATION_UPDATE": "Mettre à jour les paramètres de présentation",
+    "ADMIN_MARKDOWN_XSS_UPDATE": "Mettre à jour les paramètres XSS",
+    "ADMIN_LAYOUT_UPDATE": "Mettre à jour les paramètres d'affichage",
+    "ADMIN_THEME_UPDATE": "Mettre à jour les paramètres de thème",
+    "ADMIN_SIDEBAR_UPDATE": "Mettre à jour le mode de barre de navigation",
+    "ADMIN_FUNCTION_UPDATE": "Mettre à jour les Function",
+    "ADMIN_CODE_HIGHLIGHT_UPDATE": "Mettre à jour les paramètres de surlignage de code",
+    "ADMIN_CUSTOM_TITLE_UPDATE": "Mettre à jour les paramètres de titres personnalisés",
+    "ADMIN_CUSTOM_NOSCRIPT_UPDATE": "Mettre à jour les paramètres noScript",
+    "ADMIN_CUSTOM_CSS_UPDATE": "Mettre à jour les paramètres CSS",
+    "ADMIN_CUSTOM_SCRIPT_UPDATE": "Mettre à jour les paramètres de script personnalisé",
+    "ADMIN_ARCHIVE_DATA_UPLOAD": "Téléverser les données d'archive",
+    "ADMIN_GROWI_DATA_IMPORTED": "Importer les données d'archive",
+    "ADMIN_UPLOADED_GROWI_DATA_DISCARDED": "Supprimer les données d'archive",
+    "ADMIN_ESA_DATA_IMPORTED": "Importer depuis esa.io",
+    "ADMIN_ESA_DATA_UPDATED": "Mettre à jour les paramètres d'import esa.io",
+    "ADMIN_CONNECTION_TEST_OF_ESA_DATA": "Tester la connexion esa",
+    "ADMIN_QIITA_DATA_IMPORTED": "Importer depuis Qiita:Team",
+    "ADMIN_QIITA_DATA_UPDATED": "Mettre à jour les paramètres d'import Qiita:Team",
+    "ADMIN_CONNECTION_TEST_OF_QIITA_DATA": "Tester la connexion Qiita:Team",
+    "ADMIN_ARCHIVE_DATA_CREATE": "Créer données d'archive",
+    "ADMIN_ARCHIVE_DATA_DOWNLOAD": "Télécharger les données d'archive",
+    "ADMIN_ARCHIVE_DATA_DELETE": "Supprimer les données d'archive",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_ADD": "Ajouter paramètres de déclencheur de notifications",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_DELETE": "Supprimer les paramètres de déclencheur de notifications",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD": "Ajouter paramètres de notifications globales",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_UPDATE": "Mettre à jour les paramètres de notifications globales",
+    "ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE": "Mettre à jour les permissions de notifications globales",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ENABLED": "Activer les notifications globales",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DISABLED": "Désactiver les notifications globales",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DELETE": "Supprimer les paramètres de notifications globales",
+    "ADMIN_SLACK_WORKSPACE_CREATE": "Ajouter espace de travail Slack",
+    "ADMIN_SLACK_WORKSPACE_DELETE": "Supprimer espace de travail Slack",
+    "ADMIN_SLACK_BOT_TYPE_UPDATE": "Changer le type de bot Slack",
+    "ADMIN_SLACK_BOT_TYPE_DELETE": "Supprimer le type de bot Slack",
+    "ADMIN_SLACK_ACCESS_TOKEN_REGENERATE": "Regénérer jeton d'accès Slack",
+    "ADMIN_SLACK_MAKE_APP_PRIMARY": "Rendre le bot primaire",
+    "ADMIN_SLACK_PERMISSION_UPDATE": "Mettre à jour les permissions du bot Slack",
+    "ADMIN_SLACK_PROXY_URI_UPDATE": "Mettre à jour l'URL du proxy pour le bot personnalisé",
+    "ADMIN_SLACK_RELATION_TEST": "Essai de connexion au bot Slack",
+    "ADMIN_SLACK_WITHOUT_PROXY_SETTINGS_UPDATE": "Mettre à jour les paramètres du bot Slack",
+    "ADMIN_SLACK_WITHOUT_PROXY_PERMISSION_UPDATE": "Mettre à jour les permissions du bot Slack",
+    "ADMIN_SLACK_WITHOUT_PROXY_TEST": "Essai de connexion au bot Slack sans proxy",
+    "ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE": "Modifier la configuration des webhooks entrants Slack",
+    "ADMIN_USERS_INVITE": "Invitation utilisateur",
+    "ADMIN_USERS_PASSWORD_RESET": "Réinitialiser le mot de passe",
+    "ADMIN_USERS_ACTIVATE": "Activer",
+    "ADMIN_USERS_DEACTIVATE": "Désactiver",
+    "ADMIN_USERS_GRANT_ADMIN": "Ajouter droit d'administration",
+    "ADMIN_USERS_REVOKE_ADMIN": "Révoquer droit d'administration",
+    "ADMIN_USERS_GRANT_READ_ONLY": "Ajouter droit de lecture",
+    "ADMIN_USERS_REVOKE_READ_ONLY": "Révoquer droit de lecture",
+    "ADMIN_USERS_SEND_INVITATION_EMAIL": "Envoyer le courriel d'invitation",
+    "ADMIN_USERS_REMOVE": "Supprimer l'utilisateur",
+    "ADMIN_USER_GROUP_CREATE": "Créer un groupe",
+    "ADMIN_USER_GROUP_UPDATE": "Mettre à jour le group",
+    "ADMIN_USER_GROUP_DELETE": "Supprimer le groupe",
+    "ADMIN_USER_GROUP_ADD_USER": "Ajouter l'utilisateur au groupe",
+    "ADMIN_SEARCH_CONNECTION": "Essai de reconnexion Elasticsearch",
+    "ADMIN_SEARCH_INDICES_NORMALIZE": "Nomarliser l'index Elasticsearch",
+    "ADMIN_SEARCH_INDICES_REBUILD": "Reconstruire l'index Elasticsearch"
+  },
+  "g2g": {
+    "transfer_success": "Transfert de GROWI vers GROWI complété!",
+    "error_generate_growi_archive": "Échec de la création du fichier d'archive GROWI",
+    "error_send_growi_archive": "Échec de l'envoi du fichier d'archive GROWI vers l'autre GROWI"
+  },
+  "external_user_group": {
+    "management": "Gestion des groupes externes",
+    "execute_sync": "Synchroniser",
+    "sync": "Synchroniser",
+    "invalid_sync_settings": "Paramètres invalides",
+    "update_sync_settings_failed": "Échec de la mise à jour des paramètres",
+    "description_form_detail": "Please note that edited value will be overwritten on next sync if description mapper is set in sync settings",
+    "only_description_edit_allowed": "Seule la description peut être modifiée pour les groupes externes",
+    "sync_being_executed": "Une synchronisation est actuellement en cours. Une synchronisation pourra être effectuée lorsque celle-ci sera terminée.",
+    "sync_succeeded": "Synchronisation réussie",
+    "sync_failed": "Synchronisation échouée",
+    "provider": "Fournisseur",
+    "confirmation_before_sync": "Confirmation préalable",
+    "execution_time_warning": "Le processus de synchronisation prend un certain temps, dépendemment de la quantité d'utilisateurs et de groupes.",
+    "parallel_sync_forbidden": "Il n'est pas possible de lancer plusieurs synchronisation en même temps.",
+    "ldap": {
+      "group_sync_settings": "Configuration synchronisation groupes LDAP",
+      "group_search_base_DN": "DN",
+      "group_search_base_dn_detail": "Le DN pour rechercher les groupes. La valeur écrite dans les paramètres de sécurité est utilisée par défaut.",
+      "membership_attribute": "Attribut d'appartenance",
+      "membership_attribute_detail": "Attribut de l'objet groupe qui indique l'appartenance de l'utilisateur.",
+      "membership_attribute_type": "Type d'attribut",
+      "membership_attribute_type_detail": "Le type d'attribut d'appartenance, soit DN ou UID.",
+      "child_group_attribute": "Attribut du groupe enfant",
+      "child_group_attribute_detail": "Attribut de l'objet groupe qui indique les informations du groupe enfant, soit son DN.",
+      "preserve_deleted_ldap_groups": "Préserver les groupes LDAP supprimés",
+      "name_mapper_detail": "Attribut relié au nom de groupe",
+      "updated_group_sync_settings": "Mettre à jour les paramètres de synchronisation de groupes LDAP",
+      "password": "Mot de passe",
+      "password_detail": "Le mot de passe de connexion est nécessaire, car le type de liaison est User Bind",
+      "auth_not_set": "L'authentification LDAP doit être activé avant le processus de synchronisation"
+    },
+    "keycloak": {
+      "group_sync_settings": "Configuration synchronisation groupes Keycloak",
+      "host": "Hôte",
+      "host_detail": "URL hôte Keycloak",
+      "group_realm": "Realm du groupe",
+      "group_realm_detail": "Realm contenant les groupes pour la synchronisation",
+      "group_sync_client_realm": "Realm du client utilisé pour interroger l'API Admin",
+      "group_sync_client_realm_detail": "Realm du client utilisé pour authentifier l'interrogation de l'API admin Keycloak",
+      "group_sync_client_id": "ID du client",
+      "group_sync_client_id_detail": "Identifiant du client utilisé pour authentifier l'interrogation de l'API admin Keycloak",
+      "group_sync_client_secret": "Secret du client",
+      "group_sync_client_secret_detail": "Secret du client utilisé pour authentifier l'interrogation de l'API admin Keycloak",
+      "updated_group_sync_settings": "Mettre à jour les paramètres de synchronisation de groupes Keycloak",
+      "preserve_deleted_keycloak_groups": "Préserver les groupes Keycloak supprimés",
+      "auth_not_set": "Activer OIDC or SAML host that includes 'Host' and 'Group Realm' of group sync settings"
+    },
+    "auto_generate_user_on_sync": "Générer l'utilisateur lors de la synchronisation",
+    "description_mapper_detail": "Attribut relié à la description du groupe. La description est modifiable après la synchronisation. L'attribut de liaison écrasera automatiquement la valeur modifié lors d'une synchronisation."
+  },
+  "toaster": {
+    "grant_user_admin": "{{username}}: droits d'administration ajoutés",
+    "revoke_user_admin": "{{username}}: droits d'administration retirés",
+    "grant_user_read_only": "{{username}}: droits de lecture ajoutés",
+    "revoke_user_read_only": "{{username}}: droits de lecture retirés",
+    "activate_user_success": "{{username}} activé",
+    "deactivate_user_success": "{{username}} désactivé",
+    "remove_user_success": "{{username}} supprimé",
+    "remove_external_user_success": "{{accountId}} supprimé",
+    "switch_disable_link_sharing_success": "Paramètres de lien de partage mis à jour",
+    "install_plugin_success": "{{pluginName}} installé",
+    "activate_plugin_success": "{{pluginName}} activé",
+    "deactivate_plugin_success": "{{pluginName}} désactivé",
+    "remove_plugin_success": "{{pluginName}} désinstallé"
+  },
+  "forbidden_page": {
+    "do_not_have_admin_permission": "Seul les administrateurs peuvent accéder à cette page."
+  }
+}

+ 161 - 0
apps/app/public/static/locales/fr_FR/commons.json

@@ -0,0 +1,161 @@
+{
+  "Show": "Afficher",
+  "Hide": "Cacher",
+  "Add": "Ajouter",
+  "Insert": "Insérer",
+  "Reset": "Réinitialiser",
+  "Sign out": "Se déconnecter",
+  "New": "Nouveau",
+  "Delete": "Supprimer",
+
+  "meta": {
+    "display_name": "Français"
+  },
+  "toaster": {
+    "add_succeeded": "Succès de l'ajout de {{target}} ",
+    "create_failed": "Échec de création de {{target}}",
+    "create_succeeded": "Succès de la création de {{target}}",
+    "delete_succeeded": "{{target}} supprimé",
+    "remove_share_link": "{{count}} liens de partage supprimés",
+    "remove_share_link_success": "Lien de partage {{shareLinkId}} supprimé",
+    "update_failed": "Échec de mise à jour de {{target}}",
+    "update_successed": "Succès de mise à jour de {{target}}"
+  },
+  "alert": {
+    "siteUrl_is_not_set": "'URL du site' n'est pas renseigné. Remplir depuis {{link}}",
+    "please_enable_mailer": "La configuration SMTP est requise.",
+    "password_reset_please_enable_mailer": "La configuration SMTP est requise.",
+    "email_is_already_in_use": "La configuration SMTP est déjà faite."
+  },
+  "headers": {
+    "app_settings": "Paramètres de l'application"
+  },
+
+  "header_search_box": {
+    "label": {
+      "All pages": "Toutes les pages",
+      "This tree": "Cette arbre"
+    },
+    "item_label": {
+      "All pages": "Toutes les pages",
+      "This tree": "Enfants de cette arbre"
+    }
+  },
+
+  "search_method_menu_item": {
+    "search_in_all": "Rechercher dans tout",
+    "only_children_of_this_tree": "Enfants de cet arbre",
+    "exact_mutch": "Correspondance exacte"
+  },
+
+  "share_links": {
+    "Share Link": "Liens de partage",
+    "Page Path": "Chemin de la page",
+    "expire": "Expiration",
+    "description": "Description"
+  },
+
+  "in_app_notification": {
+    "notification_list": "Notifications d'application",
+    "see_all": "Voir tout",
+    "no_notification": "Vous n'avez pas de notifications.",
+    "all": "Toutes",
+    "unopend": "Non-lues",
+    "mark_all_as_read": "Tout marquer comme lu"
+  },
+
+  "personal_dropdown": {
+    "home": "Accueil",
+    "settings": "Paramètres",
+    "color_mode": "Couleur",
+    "sidebar_mode": "Navigation latérale",
+    "sidebar_mode_editor": "Navigation latérale dans l'éditeur",
+    "use_os_settings": "Utiliser les paramètres système",
+    "feedback": "Avis"
+  },
+
+
+  "create_page_dropdown": {
+    "new_page": "Créer nouvelle page",
+    "todays": {
+      "desc": "Créer le mémo du jour",
+      "memo": "mémo"
+    },
+    "template": {
+      "desc": "Créer/modifier page modèle",
+      "children": "Modèle page enfant",
+      "descendants": "Modèle pour descendants"
+    }
+  },
+
+  "copy_to_clipboard": {
+    "Copy to clipboard": "Copier dans le presse-papier",
+    "Page path": "Chemin de la page",
+    "Page URL": "URL de la page",
+    "Permanent link": "Lien permanent",
+    "Page path and permanent link": "Chemin de la page et lien permanent",
+    "Markdown link": "Lien Markdown",
+    "Append params": "Affixer les paramètres"
+  },
+
+  "crop_image_modal": {
+    "image_crop": "Recadrage d'image",
+    "crop": "Recadrer",
+    "save": "Sauvegarder",
+    "cancel": "Annuler"
+  },
+
+  "handsontable_modal": {
+    "title": "Modifier table",
+    "data_import": "Import de données",
+    "save": "Sauvegarder",
+    "cancel": "Annuler",
+    "done": "Terminer",
+    "data_import_form": {
+      "select_data_format": "Sélectionner format de données",
+      "import_data": "Importer données",
+      "paste_table_data": "Coller les données de la table",
+      "parse_error": "Erreur d'analyse",
+      "cancel": "Annuler",
+      "import": "Importer"
+    }
+  },
+
+  "questionnaire_modal": {
+    "required": "Requis",
+    "submit": "Soumettre",
+    "close": "Fermer",
+    "title": "Sondages aléatoires GROWI pour données anonymisées.",
+    "more_satisfied_services": "Nous espérons satisfaire au mieux les utilisateurs de GROWI",
+    "strive_to_improve_services": "et utilisons les retours d'utilisateurs afin d'améliorer l'expérience d'usage GROWI",
+    "length_of_experience": {
+      "more_than_two_years": "Plus de 2 ans",
+      "one_to_two_years": "Plus d'un an, mais moins de 2 ans",
+      "six_months_to_one_year": "Plus de 6 mois, mais moins d'un an",
+      "three_months_to_six_months": "Plus de 3 mois, mais moins de 6 mois",
+      "one_month_to_three_months": "Plus d'un moins, mais moins de 3 mois",
+      "less_than_one_month": "Moins d'un mois"
+    },
+    "satisfaction_with_growi": "Satisfaction avec GROWI",
+    "history_of_growi_usage": "Historique d'usage de GROWI",
+    "occupation": "Occupation",
+    "position": "Position",
+    "comment_on_growi": "Commentaires sur GROWI",
+    "successfully_submitted": "Questionnaire soumis.",
+    "thanks_for_answering": "Merci pour votre avis."
+  },
+
+  "not_found_page": {
+    "page_not_exist": "Cette page est introuvable."
+  },
+
+  "g2g_data_transfer": {
+    "tab": "Transfert de données",
+    "data_transfer": "Transfert de données",
+    "transfer_data_to_this_growi": "Transférer les données d'un autre GROWI vers ce GROWI",
+    "publish_transfer_key": "Publier la clé de transfert",
+    "transfer_key_limit": "Les clés de transfert sont valides durant une heure.",
+    "once_transfer_key_used": "Les clés de transfert sont à usage unique.",
+    "transfer_to_growi_cloud": "Si vous souhaitez transférer depuis GROWI.cloud, cliquez ici."
+  }
+}

+ 867 - 0
apps/app/public/static/locales/fr_FR/translation.json

@@ -0,0 +1,867 @@
+{
+  "meta": {
+    "display_name": "Français"
+  },
+  "Help": "Aide",
+  "view": "Voir",
+  "Edit": "Modifier",
+  "Delete": "Supprimer",
+  "delete_all": "Tout supprimer",
+  "Duplicate": "Dupliquer",
+  "PathRecovery": "Récupération de chemin",
+  "Copy": "Copier",
+  "preview": "Prévisualiser",
+  "desktop": "Ordinateur",
+  "phone": "Téléphone",
+  "tablet": "Tablette",
+  "Click to copy": "Cliquer pour copier",
+  "Rename": "Renommer",
+  "Move/Rename": "Déplacer/renommer",
+  "Redirected": "Redirigé",
+  "Unlinked": "Délié",
+  "unlink_redirection": "Délier la redirection",
+  "Done": "Terminer",
+  "Cancel": "Annuler",
+  "Create": "Créer",
+  "Description": "Description",
+  "Admin": "Administration",
+  "administrator": "Administrateur",
+  "Tag": "Étiquette",
+  "Tags": "Étiquettes",
+  "Close": "Fermer",
+  "Shortcuts": "Raccourcis",
+  "CustomSidebar": "Navigation latérale",
+  "eg": "e.g.",
+  "add": "Ajouter",
+  "Undo": "Annuler",
+  "Article": "Article",
+  "Page Path": "Chemin de page",
+  "Category": "Catégorie",
+  "User": "Utilisateur",
+  "account_id": "Identifiant de compte",
+  "Update": "Mettre à jour",
+  "Update Page": "Mettre à jour la page",
+  "Error": "Erreur",
+  "Warning": "Avertissement",
+  "Sign in": "Se connecter",
+  "Sign in with External auth": "Se connecter avec {{signin}}",
+  "Sign up is here": "Inscription",
+  "Sign in is here": "Connexion",
+  "Sign up": "S'inscrire",
+  "or": "ou",
+  "Sign up with Google Account": "S'inscrire avec Google",
+  "Sign in with Google Account": "Se connecter avec Google",
+  "Sign up with this Google Account": "S'inscrire avec ce compte Google",
+  "Select": "Sélectionner",
+  "Required": "Requis",
+  "Example": "Exemple",
+  "Taro Yamada": "John Doe",
+  "List View": "Liste",
+  "Timeline View": "Chronologie",
+  "History": "Historique",
+  "attachment_data": "Pièces jointes",
+  "No_attachments_yet": "Aucunes pièces jointes.",
+  "Presentation Mode": "Mode présentation",
+  "Not available for guest": "Indisponible pour les invités",
+  "Not available in this version": "Indisponible dans cette version",
+  "No users have liked this yet": "Aucun utilisateur n'a aimé cette page",
+  "No users have liked this yet.": "Aucun utilisateur n'a aimé cette page.",
+  "No users have bookmarked yet": "Aucun utilisateur n'a mis en favoris cette page",
+  "Create Archive Page": "Créer page d'archive",
+  "Create Sidebar Page": "Créer page <strong>/Sidebar</strong>",
+  "File type": "Type de fichier",
+  "Target page": "Page cible",
+  "Include Attachment File": "Inclure le fichier de pièces jointes",
+  "Include Comment": "Inclure les commentaires",
+  "Include Subordinated Page": "Inclure les pages subordonnées",
+  "Include Subordinated Target Page": "inclure {{target}}",
+  "All Subordinated Page": "Toutes les pages subordonnées",
+  "Specify Hierarchy": "Spécifier hiérarchie",
+  "Submitted the request to create the archive": "Création d'archive en cours",
+  "username": "Nom d'utilisateur",
+  "Created": "Crée le",
+  "Last updated": "Modifié le",
+  "Share": "Partager",
+  "Markdown Link": "Lien Markdown",
+  "Create/Edit Template": "Créer/Modifier page modèle",
+  "Go to this version": "Voir cette version",
+  "View diff": "Voir le diff",
+  "No diff": "Aucune différences",
+  "Latest": "Dernière version",
+  "User ID": "Identifiant utilisateur",
+  "User Information": "Informations utilisateur",
+  "User Activation": "Activation utilisateur",
+  "Basic Info": "Informations de base",
+  "Name": "Nom",
+  "Email": "Adresse courriel",
+  "Language": "Langue",
+  "English": "Anglais",
+  "Japanese": "Japonais",
+  "Set Profile Image": "Sélectionner image de profil",
+  "Upload Image": "Téléverser image",
+  "Current Image": "Image actuelle",
+  "Delete Image": "Supprimer image",
+  "Delete this image?": "Supprimer cette image?",
+  "Updated": "Modifié",
+  "Upload new image": "Téléverser nouvelle image",
+  "Connected": "Connecté",
+  "Loading": "Chargement...",
+  "Disclose E-mail": "Afficher adresse courriel",
+  "page exists": "cette page est déjà existante",
+  "Error occurred": "Une erreur est survenue",
+  "Input page name": "Nom de la page",
+  "Input page name (optional)": "Nom de la page (optionnel)",
+  "Input parent page path": "Chemin de la page parent",
+  "New Page": "Nouvelle page",
+  "Create under": "Créer la page sous:",
+  "V5 Page Migration": "Convertir vers la V5",
+  "GROWI.5.0_new_schema": "Nouveau schéma GROWI.5.0",
+  "See_more_detail_on_new_schema": "Plus de détails sur <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <i class='icon-share-alt'></i> ",
+  "external_account_management": "Gestion des comptes externes",
+  "UserGroup": "Groupe utilisateur",
+  "Basic Settings": "Paramètres de base",
+  "The contents entered here will be shown in the header etc": "Le contenu entré ici sera visible dans l'en-tête",
+  "Public": "Public",
+  "Anyone with the link": "Tous les utilisateurs disposant du lien",
+  "Specified users only": "Utilisateurs spécifiés",
+  "Only me": "Seulement moi",
+  "Only inside the group": "Utilisateurs du groupe",
+  "page_list": "Liste de pages",
+  "comments": "Commentaires",
+  "Reselect the group": "Resélectionner ce groupe",
+  "Shareable link": "Lien partageable",
+  "The whitelist of registration permission E-mail address": "Les adresses courriel permises lors de l'inscription",
+  "Add tags for this page": "Ajouter des étiquettes",
+  "tag_list": "Étiquettes",
+  "popular_tags": "Étiquettes populaires",
+  "Check All tags": "voir toutes les étiquettes",
+  "You have no tag, You can set tags on pages": "Vous n'avez aucunes étiquettes, vous pouvez assigner des étiquettes aux pages",
+  "Show latest": "Voir le plus récent",
+  "Load latest": "Charger le plus récent",
+  "edited this page": "à modifié cette page.",
+  "List Drafts": "Brouillons",
+  "Deleted Pages": "Pages supprimées",
+  "Questionnaire": "Questionnaire",
+  "Disassociate": "Dissocier",
+  "No bookmarks yet": "Aucuns favoris",
+  "add_bookmark": "Ajouter aux favoris",
+  "remove_bookmark": "Retirer des favoris",
+  "wide_view": "Vue élargie",
+  "Recent Changes": "Modifications récentes",
+  "Page Tree": "Arbre",
+  "Bookmarks": "Favoris",
+  "In-App Notification": "Notifications",
+  "original_path": "Chemin originel",
+  "new_path": "Nouveau chemin",
+  "duplicated_path": "Chemin dupliqué",
+  "Link sharing is disabled": "Le partage est désactivé",
+  "successfully_saved_the_page": "Page sauvegardée",
+  "you_can_not_create_page_with_this_name": "Vous ne pouvez pas créer cette page",
+  "not_allowed_to_see_this_page": "Vous ne pouvez pas voir cette page",
+  "Confirm": "Confirmer",
+  "Successfully requested": "Demande envoyée.",
+  "form_validation": {
+    "error_message": "Des champs sont invalides",
+    "required": "%s est requis",
+    "invalid_syntax": "La syntaxe de %s est invalide.",
+    "title_required": "Titre requis.",
+    "field_required": "{{target}} est requis"
+  },
+  "page_name": "Nom de la page",
+  "folder_name": "Nom du dossier",
+  "field": "champ",
+  "not_creatable_page": {
+    "message": "Vous ne pouvez pas créer cette page dans ce chemin."
+  },
+  "custom_navigation": {
+    "no_pages_under_this_page": "Il n'y a aucune page sous celle-ci."
+  },
+  "author_info": {
+    "created_at": "Crée le",
+    "last_revision_posted_at": "Dernière révision le"
+  },
+  "installer": {
+    "tab": "Créer compte",
+    "title": "Configuration",
+    "setup": "Configuration initiale",
+    "create_initial_account": "Créer un compte",
+    "initial_account_will_be_administrator_automatically": "Ce compte sera administrateur par défaut.",
+    "unavaliable_user_id": "Cet 'Identifiant utilisateur' est indisponible.",
+    "failed_to_install": "Échec de l'installation. Réessayer.",
+    "failed_to_login_after_install": "Connexion échouée. Redirection vers le formulaire de connexion..."
+  },
+  "breaking_changes": {
+    "v346_using_basic_auth": "Le protocole Basic Authentication <strong>ne sera plus disponible</strong>. Retirer les paramètres de %s"
+  },
+  "page_register": {
+    "send_email": "Envoyer courriel",
+    "notice": {
+      "restricted": "Approbation requise.",
+      "restricted_defail": "Lorsque un administrateur aura approuvé votre accès, vous pourrez vous connecter."
+    },
+    "form_help": {
+      "email": "Votre adresse courriel doit faire partie de la liste autorisée.",
+      "password": "Votre mot de passe doit posséder un minimun de {{target}} caractères.",
+      "user_id": "L'URL des pages crées contient votre identifiant utilisateur. L'identifiant utilisateur peut contenir des lettres, chiffres et certains symboles."
+    }
+  },
+  "page_me": {
+    "form_help": {
+      "profile_image1": "Configuration du téléversement des images incomplète.",
+      "profile_image2": "Configurer AWS ou activer le stockage local."
+    }
+  },
+  "page_me_apitoken": {
+    "api_token": "Jeton API",
+    "notice": {
+      "apitoken_issued": "Aucun jeton d'API existant.",
+      "update_token1": "Un nouveau jeton peut être généré.",
+      "update_token2": "Modifiez le jeton aux endroits où celui-ci est utilisé."
+    },
+    "form_help": {}
+  },
+  "Password": "Mot de passe",
+  "Password Settings": "Paramètres de mot passe",
+  "personal_settings": {
+    "disassociate_external_account": "Dissocier compte externe",
+    "disassociate_external_account_desc": "Dissocier le compte externe <strong>{{providerType}}</strong> <strong>{{accountId}}</strong>?",
+    "set_new_password": "Modifier mot de passe",
+    "update_password": "Modifier mot de passe",
+    "current_password": "Mot de passe actuel",
+    "new_password": "Nouveau mot de passe",
+    "new_password_confirm": "Saisir de nouveau",
+    "password_is_not_set": "Mot de passe non saisi"
+  },
+  "share_links": {
+    "Shere this page link to public": "Partager cette page publiquement",
+    "share_link_list": "Liens de partage",
+    "share_link_management": "Gestion des liens de partage",
+    "delete_all_share_links": "Supprimer tout les liens de partage",
+    "expire": "Expiration",
+    "Days": "Jour",
+    "Custom": "Autre",
+    "description": "Description",
+    "enter_desc": "Entrer description",
+    "Unlimited": "illimité",
+    "Issue": "Partager",
+    "share_settings": "Paramètres de partage",
+    "Invalid_Number_of_Date": "Valeurs invalides",
+    "link_sharing_is_disabled": "Le partage est désactivé"
+  },
+  "API Settings": "Configuration API",
+  "Other Settings": "Autres paramètres",
+  "API Token Settings": "Paramètres de jetons",
+  "Current API Token": "Jeton d'API actuel",
+  "Update API Token": "Modifier jeton",
+  "in_app_notification_settings": {
+    "in_app_notification_settings": "Paramètres de notifications",
+    "subscribe_settings": "Paramètres d'abonnement automatique aux notifications de pages",
+    "default_subscribe_rules": {
+      "page_create": "S'abonner à la page lors de sa création."
+    }
+  },
+  "ui_settings": {
+    "ui_settings": "Paramètres UI",
+    "side_bar_mode": {
+      "settings": "Paramètres navigation latérale",
+      "side_bar_mode_setting": "Activer la navigation latérale",
+      "description": "Activer pour toujours afficher la barre de navigation latérale lorsque l'écran est large. Si la largeur d'écran est faible, le cas inverse est applicable."
+    }
+  },
+  "color_mode_settings": {
+    "light": "Clair",
+    "dark": "Sombre",
+    "system": "Système",
+    "settings": "Paramètres de thème",
+    "description": "Affichage en mode clair, sombre ou selon les paramètres système.<br>Seuls les thèmes supportés seront modifiés."
+  },
+  "editor_settings": {
+    "editor_settings": "Paramètres de l'éditeur"
+  },
+  "search_help": {
+    "title": "Aide",
+    "and": {
+      "syntax help": "séparer avec un espace",
+      "desc": "Pages incluant {{word1}}, {{word2}} dans le titre ou le corps"
+    },
+    "exclude": {
+      "desc": "Pages excluant {{word}} dans le titre ou le corps"
+    },
+    "phrase": {
+      "syntax help": "entourer de guillemets",
+      "desc": "Pages incluant \"{{phrase}}\""
+    },
+    "prefix": {
+      "desc": "Pages dont le titre débute par {{path}}"
+    },
+    "exclude_prefix": {
+      "desc": "Pages dont le titre ne débute pas par {{path}}"
+    },
+    "tag": {
+      "desc": "Pages ayant l'étiquette {{tag}}"
+    },
+    "exclude_tag": {
+      "desc": "Pages n'ayant pas l'étiquette {{tag}}"
+    }
+  },
+  "search": {
+    "search page bodies": "Faire la touche [Enter] pour de la recherche textuelle"
+  },
+  "page_page": {
+    "notice": {
+      "version": "Version historique de la page.",
+      "redirected": "Redirection depuis",
+      "redirected_period": ".",
+      "unlinked": "Les pages de redirection à cette page ont été supprimées.",
+      "restricted": "L'accès à cette page est restreint",
+      "stale": "Plus de {{count}} an est passé depuis la dernière mise à jour.",
+      "stale_plural": "Plus de {{count}} années sont passées depuis la dernière mise à jour.",
+      "expiration": "Ce lien expirera <strong>{{expiredAt}}</strong>.",
+      "no_deadline": "Cette page n'a pas de date d'expiration"
+    }
+  },
+  "page_edit": {
+    "input_channels": "Canal Slack...",
+    "theme": "Thème",
+    "keymap": "Touches",
+    "indent": "Indentation",
+    "editor_config": "Configuration de l'éditeur",
+    "Show active line": "Montrer la ligne active",
+    "auto_format_table": "Formattage les tables",
+    "overwrite_scopes": "{{operation}} et écraser les scopes des pages enfants",
+    "notice": {
+      "conflict": "Sauvegarde impossible, la page est en cours de modification par un autre utilisateur. Recharger la page."
+    },
+    "changes_not_saved": "Les modifications n'ont pas été sauvegardées. Fermer?"
+  },
+  "page_comment": {
+    "comments": "Commentaires",
+    "comment": "Commmenter",
+    "preview": "Prévisualiser",
+    "write": "Écrire",
+    "add_a_comment": "Ajouter un commentaire",
+    "display_the_page_when_posting_this_comment": "Afficher la page en postant le commentaire",
+    "no_user_found": "Aucun utilisateur trouvé",
+    "reply": "Répondre",
+    "delete_comment": "Supprimer?"
+  },
+  "page_api_error": {
+    "notfound_or_forbidden": "Page originale introuvable ou accès restreint.",
+    "already_exists": "Une page avec ce chemin existe déjà.",
+    "outdated": "Page obsolète.",
+    "user_not_admin": "Seul un administrateur peut supprimer la page",
+    "single_deletion_empty_pages": "Une page vide ne peut pas être supprimée",
+    "complete_deletion_not_allowed_for_user": "Vous n'êtes pas autorisé à supprimer cette page"
+  },
+  "page_history": {
+    "revision_list": "Historique des modifications",
+    "revision": "version",
+    "comparing_source": "Source",
+    "comparing_target": "Destination",
+    "comparing_revisions": "Comparer les modifications",
+    "compare_latest": "Comparer avec la version la plus récente",
+    "compare_previous": "Compare avec une version précédente"
+  },
+  "modal_rename": {
+    "label": {
+      "Move/Rename page": "Déplacer/renommer page",
+      "New page name": "Nom de la page",
+      "Failed to get subordinated pages": "échec de récupération des pages subordinnées",
+      "Failed to get exist path": "échec de la récupération du chemin",
+      "Current page name": "Nom de la page courante",
+      "Rename this page only": "Renommer cette page",
+      "Force rename all child pages": "Forcer le renommage des pages",
+      "Other options": "Autres options",
+      "Do not update metadata": "Ne pas modifier les métadonnées",
+      "Redirect": "Rediriger"
+    },
+    "help": {
+      "redirect": "Rediriger vers la nouvelle page",
+      "metadata": "Conserve les métadonnées d'édition de la page",
+      "recursive": "Déplacer/renommer les pages enfants récursivement"
+    }
+  },
+  "Put Back": "Annuler",
+  "Delete Completely": "Supprimer définitivement",
+  "page_has_been_reverted": "{{path}} déplacement annulé",
+  "modal_delete": {
+    "delete_page": "Supprimer la page",
+    "deleting_page": "Suppression de la page",
+    "delete_recursively": "Supprimer les pages enfants.",
+    "delete_completely": "Supprimer définitivement",
+    "delete_completely_restriction": "Vous n'êtes pas autorisé à supprimer définitivement les pages",
+    "recursively": "Supprime toutes les pages sous ce chemin.",
+    "completely": "Supprime définitivement la page."
+  },
+  "deleted_page": "Déplacée dans la corbeille",
+  "deleted_pages": "{{path}} supprimées",
+  "deleted_pages_completely": "{{path}} supprimées définitivement",
+  "renamed_pages": "{{path}} renommée",
+  "empty_trash": "Corbeille vidée",
+  "modal_empty": {
+    "empty_the_trash": "Vider la corbeille",
+    "empty_the_trash_button": "Vider",
+    "not_deletable_notice": "Certaines pages ne peuvent pas être supprimées",
+    "notice": "Les pages supprimées définitivement ne sont pas récupérables."
+  },
+  "modal_duplicate": {
+    "label": {
+      "Duplicate page": "Dupliquer",
+      "New page name": "Nom de la page",
+      "Failed to get subordinated pages": "échec de récupération des pages subordinnées",
+      "Current page name": "Nom de la page courante",
+      "Recursively": "Récursivement",
+      "Duplicate without exist path": "Dupliquer sans le chemin existant",
+      "Same page already exists": "Une page avec ce chemin existe déjà",
+      "Only duplicate user related pages": "Seul les pages dupliquées auquelles vous avez accès"
+    },
+    "help": {
+      "recursive": "Dupliquer les pages enfants récursivement",
+      "only_inherit_user_related_groups": "Si la page est configuré en \"Seulement dans le groupe\", les groupes auxquels vous n'appartenez pas perdront l'accès aux pages dupliquées"
+    }
+  },
+  "duplicated_pages": "{{fromPath}} dupliquée",
+  "modal_putback": {
+    "label": {
+      "Put Back Page": "Annuler déplacement",
+      "recursively": "Annuler récursivement"
+    },
+    "help": {
+      "recursively": "Annuler récursivement pour les pages enfants"
+    }
+  },
+  "modal_shortcuts": {
+    "global": {
+      "title": "Raccourcis clavier",
+      "Open/Close shortcut help": "Ouvrir/fermer<br> l'aide aux raccourcis",
+      "Edit Page": "Modifier page",
+      "Create Page": "Créer page",
+      "Search": "Rechercher",
+      "Show Contributors": "Voir contributeurs",
+      "MirrorMode": "Mode mirroir",
+      "Konami Code": "Code Konami",
+      "konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
+    },
+    "editor": {
+      "title": "Raccourcis d'édition",
+      "Indent": "Indentation",
+      "Outdent": "Retrait",
+      "Save Page": "Sauvegarder la page",
+      "Delete Line": "Supprimer la ligne"
+    },
+    "commentform": {
+      "title": "Raccourcis de commentaires",
+      "Post": "Poster"
+    }
+  },
+  "modal_resolve_conflict": {
+    "file_conflicting_with_newer_remote": "Ce fichier est en conflit avec une autre version",
+    "resolve_conflict_message": "Sélectionner le corps de la page",
+    "resolve_conflict": "Résoudre le conflit",
+    "resolve_and_save": "Résoudre et sauvegarder",
+    "select_revision": "Sélectionner {{revision}}",
+    "requested_revision": "moi",
+    "origin_revision": "origine",
+    "latest_revision": "les autres",
+    "selected_editable_revision": "Corps de page sélectionné (Modifiable)"
+  },
+  "link_edit": {
+    "edit_link": "Modifier lien",
+    "set_link_and_label": "Ajouter lien et étiquette",
+    "link": "Lien",
+    "placeholder_of_link_input": "Chemin de la page ou URL",
+    "label": "Étiquette",
+    "path_format": "Format",
+    "use_relative_path": "Chemin relatif",
+    "use_permanent_link": "Lien permanent",
+    "notation": "Notation",
+    "markdown": "Markdown",
+    "GROWI_original": "GROWI original",
+    "pukiwiki": "Pukiwiki",
+    "preview": "Prévisualiser",
+    "page_not_found_in_preview": "\"{{path}}\" n'est pas une page GROWI."
+  },
+  "toaster": {
+    "file_upload_failed": "Échec du téléversement.",
+    "initialize_successed": "Initialisation de {{target}} réussie",
+    "remove_share_link_success": "Suppression de {{shareLinkId}} réussie",
+    "issue_share_link": "Lien de partage ajouté",
+    "remove_share_link": "{{count}} liens de partage supprimés",
+    "switch_disable_link_sharing_success": "Paramètres des liens de partage modifiés",
+    "failed_to_reset_password": "Échec de la réinitialisation du mot de passe",
+    "save_succeeded": "Sauvegarde réussie"
+  },
+  "template": {
+    "modal_label": {
+      "Select template": "Sélectionner modèle",
+      "Create/Edit Template Page": "Créer/modifier page modèle",
+      "Create template under": "Créer une page modèle enfant"
+    },
+    "option_label": {
+      "create/edit": "Créer/modifier page modèle",
+      "select": "Sélectionner type de page modèle"
+    },
+    "children": {
+      "label": "Modèle pour page enfant",
+      "desc": "Applicable aux pages de même niveau que la page modèle"
+    },
+    "decendants": {
+      "label": "Modèle pour descendants",
+      "desc": "Applicable aux page descendantes"
+    }
+  },
+  "sandbox": {
+    "header": "En-tête",
+    "header_x": "En-tête {{index}}",
+    "block": "Paragraphe",
+    "block_detail": "fait un paragraphe",
+    "empty_line": "Ligne vide",
+    "line_break": "Saut de ligne",
+    "line_break_detail": "(2 espaces) fait un saut de ligne",
+    "typography": "Typographie",
+    "italics": "Italique",
+    "bold": "Gras",
+    "italic_bold": "Gras italique",
+    "strikethrough": "Barré",
+    "link": "Lien",
+    "code_highlight": "Surlignage de code",
+    "list": "Liste",
+    "unordered_list_x": "Liste non ordonnée {{index}}",
+    "ordered_list_x": "Liste ordonnée {{index}}",
+    "task": "Tâche",
+    "task_checked": "Coché",
+    "task_unchecked": "Décoché",
+    "quote": "Citation",
+    "quote1": "Il est possible d'écrire ",
+    "quote2": "des citations sur plusieurs lignes",
+    "quote_nested": "Citation imbriquée",
+    "table": "Table",
+    "image": "Image",
+    "alt_text": "Texte alternatif",
+    "insert_image": "insère une image",
+    "open_sandbox": "Ouvrir le bac à sable"
+  },
+  "slack_notification": {
+    "popover_title": "Notifications Slack",
+    "popover_desc": "Entrer le nom du canal. Plusieurs canaux peuvent être notifiés en entrant leur noms séparés d'une virgule",
+    "input_channels": "Input channels"
+  },
+  "search_result": {
+    "title": "Recherche",
+    "result_meta": "Résultats de recherche pour:",
+    "deletion_mode_btn_lavel": "Sélectionner et supprimer la page",
+    "cancel": "Annuler",
+    "delete": "Supprimer",
+    "check_all": "Tout cocher",
+    "deletion_modal_header": "Supprimer page",
+    "delete_completely": "Supprimer définitivement",
+    "include_certain_path": "Inclure le chemin {{pathToInclude}} ",
+    "delete_all_selected_page": "Tout supprimer",
+    "currently_not_implemented": "Non implémenté",
+    "search_again": "Rechercher",
+    "number_of_list_to_display": "Afficher",
+    "page_number_unit": "pages",
+    "hit_number_unit": "trouvé",
+    "sort_axis": {
+      "relationScore": "Trier par pertinence",
+      "createdAt": "Date de création",
+      "updatedAt": "Dernière modification"
+    }
+  },
+  "private_legacy_pages": {
+    "title": "Anciennes pages privées",
+    "bulk_operation": "Opération de masse",
+    "convert_all_selected_pages": "Convertir au nouveau format V5",
+    "input_path_to_convert": "Entrer un chemin pour convertir les pages",
+    "alert_title": "Des pages au format V4 existent",
+    "alert_desc1": "Sélectionner les pages à convertir vers le format V5 avec le bouton \"Opération de masse\".",
+    "nopages_title": "GROWI V5 est maintenant utilisable!",
+    "nopages_desc1": "Toutes les pages ont été converties au format V5.",
+    "detail_info": "Pour plus de détails, voir <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>Convertir vers GROWI v5.0.x <span className='growi-custom-icons'>external_link</span></a>.",
+    "modal": {
+      "title": "Convertir au format V5",
+      "converting_pages": "Conversion des pages",
+      "convert_recursively_label": "Convertir les pages enfants récursivement",
+      "convert_recursively_desc": "Convertir les pages sous ce chemin récursivement.",
+      "button_label": "Convertir"
+    },
+    "toaster": {
+      "page_migration_succeeded": "Conversion des pages sélectionnées au format V5 réussie.",
+      "page_migration_failed_with_paths": "Conversion de {{paths}} au format V5 échouée.",
+      "page_migration_failed": "Échec de la conversion au format V5."
+    },
+    "by_path_modal": {
+      "title": "Convertir au format V5",
+      "alert": "Cette opération est irréversible et il est possible que certaines pages non visible soit traitées.",
+      "checkbox_label": "Compris",
+      "description": "Entrer un chemin, toutes les pages sous ce chemin seront converties au format V5.",
+      "button_label": "Convertir",
+      "success": "Demande de conversion envoyée.",
+      "error": "Échec de la demande de conversion.",
+      "error_grant_invalid": "Les permissions de la page sont invalides.",
+      "error_page_not_found": "Page introuvable.",
+      "error_duplicate_pages_found": "Plusieurs pages au même chemin ont été trouvées, renommez ou supprimez puis réessayez."
+    }
+  },
+  "login": {
+    "title": "Connexion",
+    "sign_in_error": "Erreur de connexion",
+    "registration_successful": "Inscription réussie. Demander l'approbation d'un administrateur.",
+    "Setup": "Configuration",
+    "enabled_ldap_has_configuration_problem": "LDAP actif, vérifier la configuration.",
+    "set_env_var_for_logs": "(Remplir les variables d'environnement <code>DEBUG=crowi:service:PassportService</code> pour obtenir les journaux d'erreur)"
+  },
+  "invited": {
+    "title": "Invité",
+    "discription_heading": "Créer un compte",
+    "discription": "Créer un compte avec votre adresse courriel invitée"
+  },
+  "export_bulk": {
+    "failed_to_export": "Échec de l'export",
+    "failed_to_count_pages": "Échec du compte des pages",
+    "export_page_markdown": "Exporter la page en Markdown",
+    "export_page_pdf": "Exporter la page en PDF"
+  },
+  "message": {
+    "successfully_connected": "Connecté!",
+    "fail_to_save_access_token": "Échec de la sauvegarde de access_token.",
+    "fail_to_fetch_access_token": "Échec de la récupération de access_token.",
+    "successfully_disconnected": "Déconnecté!",
+    "strategy_has_not_been_set_up": "{{strategy}} n'est pas configuré",
+    "ldap_user_not_valid": "Utilisateur LDAP invalide",
+    "external_account_not_exist": "Compte externe introuvable",
+    "maximum_number_of_users": "Le nombre maximum d'utilisateurs est atteint.",
+    "sign_in_failure": "Échec de la connexion.",
+    "aws_sttings_required": "La configuration AWS est requise pour utiliser cette fonctionnalité.",
+    "application_already_installed": "Application déja installée.",
+    "email_address_could_not_be_used": "Cette adresse courriel n'est pas autorisée",
+    "user_id_is_not_available": "Cet identifiant utilisateur est indisponible.",
+    "username_should_not_be_null": "Le nom d'utilisateur est requis",
+    "email_address_is_already_registered": "Cette adresse courriel est indisponible.",
+    "can_not_register_maximum_number_of_users": "Le nombre maximum d'utilisateurs est atteint.",
+    "email_settings_is_not_setup": "La configuration d'envoi de courriels est incomplète.",
+    "email_authentication_is_not_enabled": "L'authentification par adresse courriel est désactivée.",
+    "failed_to_register": "Échec de l'inscription.",
+    "successfully_created": "Utilisateur {{username}} crée.",
+    "can_not_activate_maximum_number_of_users": "Ne peut activer au dessus du nombre maximal d'utilisateur.",
+    "failed_to_activate": "Échec de l'activation.",
+    "unable_to_use_this_user": "Impossible d'utiliser cet utilisateur",
+    "complete_to_install1": "Complétez pour installer GROWI! Connectez vous en tant qu'administrateur",
+    "complete_to_install2": "Complétez pour installer GROWI! Vérifier les paramètres de la page.",
+    "failed_to_create_admin_user": "Échec de création de l'administrateur. {{errMessage}}",
+    "successfully_send_email_auth": "Un courriel a été envoyé {{email}}. Ouvrez l'URL dans ce courriel et complétez l'inscription.",
+    "incorrect_token_or_expired_url": "Le jeton est invalide ou l'URL est expirée.",
+    "user_already_logged_in": "Vous ne pouvez pas créer de nouveau compte en étant connecté.",
+    "registration_closed": "Création de nouveau compte non-autorisée.",
+    "Username has invalid characters": "Le nom d'utilisateur contient des caractères invalides.",
+    "Username field is required": "Identifiant utilisateur requis.",
+    "Name field is required": "Nom requis.",
+    "Email format is invalid": "Format d'adresse courriel invalide.",
+    "Email field is required": "Adresse courriel requise.",
+    "Password has invalid character": "Le mot de passe contient des caractères invalides",
+    "Password minimum character should be more than 8 characters": "Le mot de passe doit contenir plus de 8 caractères.",
+    "Password field is required": "Mot de passe requis.",
+    "Username or E-mail has invalid characters": "Le nom d'utilisateur ou l'adresse courriel contient des caractères invalides",
+    "Password minimum character should be more than 6 characters": "Le mot de passe doit contenir au moins 6 caractères.",
+    "user_not_found": "Utilisateur introuvable.",
+    "provider_duplicated_username_exception": "<p><strong><span class='material-symbols-outlined me-1'>cancel</span>DuplicatedUsernameException</strong></p><p class='mb-0'> L'authentification est réussie pour {{ failedProviderForDuplicatedUsernameException }} , mais la création d'un utilisateur a échouée. Voir <a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
+  },
+  "grid_edit": {
+    "create_bootstrap_4_grid": "Créer grille Bootstrap 4",
+    "grid_settings": "Paramètres de la grille",
+    "grid_pattern": "Motif de grille",
+    "division": "Divisions",
+    "smart_no": "Mobile / Pas de point d'arrêt",
+    "break_point": "Point d'arrêt par taille d'écran"
+  },
+  "validation": {
+    "aws_region": "Entrer la région AWS ex):us-east-1",
+    "aws_custom_endpoint": "Pour l'option personnalisée, spécifier le protocole http(s)://.",
+    "failed_to_send_a_test_email": "Échec de l'envoi du courriel d'essai, vérifier la configuration."
+  },
+  "forgot_password": {
+    "forgot_password": "Mot de passe oublié?",
+    "send": "Envoyer",
+    "return_to_login": "Retour vers la connexion",
+    "reset_password": "Réinitialiser mot de passe",
+    "sign_in_instead": "Se connecter",
+    "password_reset_request_desc": "Le mot de passe est réinitialisable ici.",
+    "password_reset_excecution_desc": "Entrer un nouveau mot de passe",
+    "new_password": "Nouveau mot de passe",
+    "confirm_new_password": "Confirmer le nouveau mot de passe",
+    "email_is_required": "Adresse courriel requise",
+    "success_to_send_email": "Courriel envoyé",
+    "feature_is_unavailable": "Fonctionnalité indisponible.",
+    "incorrect_token_or_expired_url": "Le jeton est invalide ou l'URL est expirée. Effectuer une nouvelle réinitialisation via le lien ci-dessous.",
+    "password_and_confirm_password_does_not_match": "Le mot de passe ne correspond pas",
+    "please_enable_mailer_alert": "La réinitialisation de mot de passe est désactivée, car la configuration d'envois de courriels est incomplète."
+  },
+  "emoji": {
+    "title": "Choisir un émoji",
+    "search": "Rechercher",
+    "clear": "Vider",
+    "notfound": "Aucun émoji trouvé",
+    "skintext": "Choisir le teint par défaut",
+    "categories": {
+      "search": "Résultats de recherche",
+      "recent": "Récents",
+      "smileys": "Émotions",
+      "people": "Individus & corps",
+      "nature": "Animaux & nature",
+      "foods": "Nourriture & boisson",
+      "activity": "Activités",
+      "places": "Voyage",
+      "objects": "Objets",
+      "symbols": "Symboles",
+      "flags": "Drapeaux",
+      "custom": "Personnalisé"
+    },
+    "categorieslabel": "Catégories d'émojis",
+    "skintones": {
+      "1": "Teint par défaut",
+      "2": "Teint clair",
+      "3": "Teint moyen-clair",
+      "4": "Teint moyen",
+      "5": "Teint moyen-foncé",
+      "6": "Teint foncé"
+    }
+  },
+  "maintenance_mode": {
+    "maintenance_mode": "Mode maintenance",
+    "growi_is_under_maintenance": "GROWI est actuellement en maintenance.",
+    "admin_page": "Administration",
+    "login": "Connexion",
+    "logout": "Déconnexion"
+  },
+  "pagetree": {
+    "cannot_rename_a_title_that_contains_slash": "Renommage impossible lorsque le titre contient '/'",
+    "you_cannot_move_this_page_now": "Déplacement de la page impossible",
+    "something_went_wrong_with_moving_page": "Échec de déplacement de la page"
+  },
+  "duplicated_page_alert": {
+    "same_page_name_exists": "Une page avec ce nom 「{{pageName}}」 existe déjà",
+    "same_page_name_exists_at_path": "Une page avec ce nom {{pageName}} existe déjà {{path}} ",
+    "select_page_to_see": "Sélectionner une page"
+  },
+  "user_group": {
+    "select_group": "Sélectionner groupe",
+    "belonging_to_no_group": "Appartenance au groupe introuvable.",
+    "manage_user_groups": "Gestion des groupes utilisateurs"
+  },
+  "fix_page_grant": {
+    "modal": {
+      "no_grant_available": "La liste de permissions est introuvable. Modifier les permissions de la page parent et réessayer.",
+      "need_to_fix_grant": "Les permissions associées a la page doivent être modifiées. <br> Sélectionner parmi les options ci-dessous.",
+      "grant_label": {
+        "public": "Public",
+        "isForbidden": "Non autorisé",
+        "currentPageGrantLabel": "Autorisations de la page: ",
+        "parentPageGrantLabel": "Autorité de la page parent: ",
+        "docLink": "Pour plus d'informations sur la modifications de permissions, se référer <a href='https://docs.growi.org/en/guide/features/authority.html#permissions-for-subordinate-pages'>こちらのリンク</a>"
+      },
+      "radio_btn": {
+        "restrected": "Avec le lien",
+        "only_me": "seulement moi",
+        "grant_group": "Seulement les groupes spécifiés"
+      },
+      "select_group_default_text": "Sélectionner groupe",
+      "alert_message_select_group": "Aucun groupe sélectionné",
+      "btn_label": "Conversion",
+      "title": "Modifier autorité"
+    },
+    "alert": {
+      "description": "Les permissions de cette page doivent être modifiées.",
+      "btn_label": "Révision"
+    }
+  },
+  "tooltip": {
+    "like": "Like!",
+    "cancel_like": "Annuler",
+    "bookmark": "Favori",
+    "cancel_bookmark": "Annuler favori",
+    "receive_notifications": "Recevoir les notifications",
+    "stop_notification": "Stopper les notifications",
+    "footprints": "Visiteurs",
+    "operation": {
+      "attention": {
+        "rename": "Échec du renommage du chemin des pages descendantes, ouvrir le menu du lecteur 3-points et sélectionner 'Récupération du chemin'"
+      }
+    }
+  },
+  "page_operation": {
+    "paths_recovered": "Chemin récupéré",
+    "path_recovery_failed": "Échec de la récupération du chemin"
+  },
+  "user_home_page": {
+    "bookmarks": "Favoris",
+    "recently_created": "Crée récemment"
+  },
+  "bookmark_folder": {
+    "bookmark_folder": "dossier de favoris",
+    "bookmark": "favoris",
+    "delete_modal": {
+      "modal_header_label": "Supprimer dossier de favoris",
+      "modal_body_description": "Supprimer le dossier de favoris et son contenu",
+      "modal_body_alert": "Le contenu d'un dossier supprimé n'est pas récupérable",
+      "modal_footer_button": "Supprimer dossier"
+    },
+    "input_placeholder": "Nom du dossier",
+    "new_folder": "Nouveau dossier",
+    "delete": "Supprimer dossier",
+    "drop_item_here": "Glisser-déposer ici",
+    "cancel_bookmark": "Retirer le favori",
+    "move_to_root": "Déplacer à la racine",
+    "root": "racine (par défaut)"
+  },
+  "v5_page_migration": {
+    "page_tree_not_avaliable": "Cette fonctionnalité n'est pas encore disponible.",
+    "go_to_settings": "Activer cette fonctionnalité dans les paramètres"
+  },
+  "questionnaire": {
+    "give_us_feedback": "Faites-nous part de votre avis",
+    "thank_you_for_answering": "Merci pour votre réponse",
+    "additional_feedback": "Envoyez-nous votre avis depuis le menu déroulant sur le menu utilisateur.",
+    "dont_show_again": "Ne plus afficher",
+    "deny": "Ne pas répondre",
+    "agree": "En accord",
+    "disagree": "En désaccord",
+    "answer": "Répondre",
+    "no_answer": "Aucune réponse",
+    "settings": "Configuration du questionnaire",
+    "failed_to_send": "Échec de l'envoi du questionnaire",
+    "denied": "Le questionnaire ne sera plus montré",
+    "personal_settings_explanation": "Les questionnaires de satisfaction seront actifs.",
+    "enable_questionnaire": "Activer questionnaire",
+    "disabled_by_admin": "Questionnaire désactivé par l'administrateur"
+  },
+  "tag_edit_modal": {
+    "edit_tags": "Modifier étiquettes",
+    "done": "Terminer",
+    "tags_input": {
+      "tag_name": "nom de l'étiquette"
+    }
+  },
+  "delete_attachment_modal": {
+    "confirm_delete_attachment": "Supprimer pièce jointe?"
+  },
+  "rich_attachment": {
+    "attachment_not_be_found": "Pièce jointe introuvable"
+  },
+  "page_select_modal": {
+    "select_page_location": "Sélectionner emplacement de la page"
+  },
+  "wip_page": {
+    "save_as_wip": "Sauvegarder comme brouillon",
+    "success_save_as_wip": "Sauvegardée en tant que brouillon",
+    "fail_save_as_wip": "Échec de la sauvegarde du brouillon",
+    "alert": "Page en cours d'écriture",
+    "publish_page": "Publier page",
+    "success_publish_page": "Page publiée",
+    "fail_publish_page": "Échec de publication de la page"
+  },
+  "sidebar_header": {
+    "show_wip_page": "Voir brouillon",
+    "size_s": "Taille: P",
+    "size_l": "Taille: G"
+  }
+}

+ 14 - 0
apps/app/resource/locales/fr_FR/admin/userInvitation.ejs

@@ -0,0 +1,14 @@
+Bonjour, <%- email %>
+
+Vous avez été invité au Wiki, vous pouvez vous connectez avec les identifiants suivants:
+
+Adresse courriel: <%- email %>
+Mot de passe: <%- password %>
+(Ce mot de passe est généré automatiquement. Une modification du mot de passe est requise lors de la connexion initiale)
+
+Nous vous attendons avec impatience!
+<%- url %>
+
+--
+<%- appTitle %>
+<%- url %>

+ 11 - 0
apps/app/resource/locales/fr_FR/admin/userResetPassword.ejs

@@ -0,0 +1,11 @@
+Bonjour, <%- email %>
+
+Votre mot de passe a été réinitialisé par l'administrateur, vous pouvez vous connectez avec les identifiants suivants:
+
+Adresse courriel: <%- email %>
+Nouveau mot de passe: <%- password %>
+(Ce mot de passe est généré automatiquement. Une modification du mot de passe est requise lors de la connexion initiale)
+
+--
+<%- appTitle %>
+<%- url %>

+ 20 - 0
apps/app/resource/locales/fr_FR/admin/userWaitingActivation.ejs

@@ -0,0 +1,20 @@
+Bonjour, <%- adminUser.name %>
+
+Un nouvel utilisateur s'est inscrit à <%- appTitle %>.
+
+
+====
+Informations sur l'utilisateur:
+
+Nom: <%- createdUser.name %>
+Nom d'utilisateur: <%- createdUser.username %>
+Adresse courriel: <%- createdUser.email %>
+====
+
+Pour effectuer une action, voir:
+<%- url %>/admin/users
+
+
+--
+<%- appTitle %>
+<%- url %>

+ 9 - 0
apps/app/resource/locales/fr_FR/notifications/comment.ejs

@@ -0,0 +1,9 @@
+<%- username %> à commenté sur <%- path %>.
+
+----------------------
+
+<%- comment %>
+
+----------------------
+
+GROWI: <%- appTitle %>

+ 13 - 0
apps/app/resource/locales/fr_FR/notifications/notActiveUser.ejs

@@ -0,0 +1,13 @@
+Réinitialisation du mot de passe
+
+Bonjour, <%- email %>
+
+Une demande de réinitialisation de mot de passe a été demandée depuis <%- appTitle %>.
+Cette adresse courriel n'est pas enregistré. Réessayez avec une adresse courriel différente.
+
+Si vous n'avez pas demandé de réinitialisation de mot de passe, ignorez ce courriel.
+
+-------------------------------------------------------------------------
+
+GROWI: <%- appTitle %>
+URL: <%- url %>

+ 5 - 0
apps/app/resource/locales/fr_FR/notifications/pageCreate.ejs

@@ -0,0 +1,5 @@
+<%- username %> a crée une page <%- path %>.
+
+----------------------
+
+GROWI: <%- appTitle %>

+ 5 - 0
apps/app/resource/locales/fr_FR/notifications/pageDelete.ejs

@@ -0,0 +1,5 @@
+<%- username %> à supprimé la page <%- path %>.
+
+----------------------
+
+GROWI: <%- appTitle %>

+ 5 - 0
apps/app/resource/locales/fr_FR/notifications/pageEdit.ejs

@@ -0,0 +1,5 @@
+<%- username %> a modifié la page <%- path %>.
+
+----------------------
+
+GROWI: <%- appTitle %>

+ 5 - 0
apps/app/resource/locales/fr_FR/notifications/pageLike.ejs

@@ -0,0 +1,5 @@
+<%- username %> a aimé la page <%- path %>.
+
+----------------------
+
+GROWI: <%- appTitle %>

+ 5 - 0
apps/app/resource/locales/fr_FR/notifications/pageMove.ejs

@@ -0,0 +1,5 @@
+<%- username %> a renommé la page <%- oldPath %> en <%- newPath %>.
+
+----------------------
+
+GROWI: <%- appTitle %>

+ 12 - 0
apps/app/resource/locales/fr_FR/notifications/passwordReset.ejs

@@ -0,0 +1,12 @@
+Réinitialisation du mot de passe
+
+Bonjour, <%- email %>
+
+Une demande de réinitialisation de mot de passe a été demandée pour votre compte GROWI (<%- appTitle %>).
+Pour réinitialiser votre mot de passe, cliquer sur le lien ci-dessous.
+
+<%- url %>
+
+Ce lien a une durée de vie de 10 minutes, expirant le <%- expiredAt %>.
+
+Si vous n'avez pas demandé de réinitialisation de mot de passe, ignorez ce courriel.

+ 8 - 0
apps/app/resource/locales/fr_FR/notifications/passwordResetSuccessful.ejs

@@ -0,0 +1,8 @@
+Réinitialisation du mot de passe réussie
+
+Bonjour, <%- email %>
+
+Votre mot de passe a été réinitialisé.
+Connectez-vous avec votre nouveau mot de passe
+
+Merci,

+ 12 - 0
apps/app/resource/locales/fr_FR/notifications/userActivation.ejs

@@ -0,0 +1,12 @@
+Confirmation de compte
+
+Bonjour, <%- email %>
+
+Un compte a été crée pour GROWI (<%- appTitle %>).
+Pour activer votre compte, cliquez sur le lien ci-dessous.
+
+<%- url %>
+
+Le lien a une durée de vie de 1 heure, expirant le <%- expiredAt %>.
+
+Si vous n'avez pas crée ce compte, vous pouvez ignorer ce courriel.

+ 169 - 0
apps/app/resource/locales/fr_FR/sandbox-bootstrap5.md

@@ -0,0 +1,169 @@
+# 1. Badges
+
+<span class="badge text-bg-primary">primary</span>  
+
+<span class="badge text-bg-secondary">secondary</span>  
+
+<span class="badge text-bg-success">success</span>  
+
+<span class="badge text-bg-danger">danger</span>  
+
+<span class="badge text-bg-warning">warning</span>  
+
+<span class="badge text-bg-info">info</span>  
+
+<span class="badge text-bg-light">light</span>  
+
+<span class="badge text-bg-dark">dark</span>  
+
+
+# 2. Alerts
+
+<div class="alert alert-primary" role="alert">
+  This is a primary alert.
+</div>
+
+<div class="alert alert-secondary" role="alert">
+  This is a secondary alert.
+</div>
+
+<div class="alert alert-success" role="alert">
+  This is a success alert.
+</div>
+
+<div class="alert alert-danger" role="alert">
+  This is a danger alert.
+</div>
+
+<div class="alert alert-warning" role="alert">
+  This is a warning alert.
+</div>
+
+<div class="alert alert-info" role="alert">
+  This is a info alert.
+</div>
+
+<div class="alert alert-light" role="alert">
+  This is a light alert.
+</div>
+
+<div class="alert alert-dark" role="alert">
+  This is a dark alert.
+</div>
+
+
+# 3. Cards
+
+<div class="card text-bg-primary mb-3" style="max-width: 50rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Primary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-secondary mb-3" style="max-width: 45rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Secondary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-success mb-3" style="max-width: 40rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Success card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-danger mb-3" style="max-width: 35rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Danger card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-warning mb-3" style="max-width: 30rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Warning card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-info mb-3" style="max-width: 25rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Info card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-light mb-3" style="max-width: 20rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Light card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-dark mb-3" style="max-width: 15rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Dark card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+
+# 4. Colors
+## Contextual colors
+<p class="text-primary">Look, I'm in a well!</p>
+<p class="text-warning">Look, I'm in a well!</p>
+<p class="text-danger">Look, I'm in a well!</p>
+
+## Contextual backgrounds
+<p class="text-danger bg-primary">Look, I'm in a well!</p>
+<p class="text-primary bg-warning">Look, I'm in a well!</p>
+<p class="text-warning bg-danger">Look, I'm in a well!</p>
+
+
+# 5. Collapse
+## Displaying content
+<a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
+  Show content
+</a>
+
+<div class="collapse" id="collapse-1">
+  <div class="card card-body">
+
+- Content you want to display
+  - Content you want to display
+      
+  </div>
+</div>
+
+## Hiding content
+<a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
+  Hide content
+</a>
+
+<div class="collapse show" id="collapse-2">
+  <div class="card card-body">
+
+- Content you want to hide
+  - Content you want to hide
+
+  </div>
+</div>
+
+
+# Official docs
+- [Click here for Badges details](https://getbootstrap.jp/docs/5.3/components/badge/)
+- [Click here for Alerts details](https://getbootstrap.jp/docs/5.3/components/alerts/)
+- [Click here for Cards details](https://getbootstrap.jp/docs/5.3/components/card/)
+- [Click here for Colors details](https://getbootstrap.jp/docs/5.3/utilities/colors/)
+- [Click here for Collapse details](https://getbootstrap.jp/docs/5.3/components/collapse/)

Разница между файлами не показана из-за своего большого размера
+ 7 - 0
apps/app/resource/locales/fr_FR/sandbox-diagrams.md


+ 71 - 0
apps/app/resource/locales/fr_FR/sandbox-math.md

@@ -0,0 +1,71 @@
+# :pencil: Math
+
+See [KaTeX](https://katex.org/).
+
+## Inline Formula
+
+When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are
+  $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
+
+## The Lorenz Equations
+
+$$
+\begin{align}
+\dot{x} & = \sigma(y-x) \\
+\dot{y} & = \rho x - y - xz \\
+\dot{z} & = -\beta z + xy
+\end{align}
+$$
+
+
+## The Cauchy-Schwarz Inequality
+
+$$
+\left( \sum_{k=1}^n a_k b_k \right)^{\!\!2} \leq
+ \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
+$$
+
+## A Cross Product Formula
+
+$$
+\mathbf{V}_1 \times \mathbf{V}_2 =
+ \begin{vmatrix}
+  \mathbf{i} & \mathbf{j} & \mathbf{k} \\
+  \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\
+  \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0 \\
+ \end{vmatrix}
+$$
+
+
+## The probability of getting $\left(k\right)$ heads when flipping $\left(n\right)$ coins is:
+
+$$
+P(E) = {n \choose k} p^k (1-p)^{ n-k}
+$$
+
+## An Identity of Ramanujan
+
+$$
+\frac{1}{(\sqrt{\phi \sqrt{5}}-\phi) e^{\frac25 \pi}} =
+     1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
+      {1+\frac{e^{-8\pi}} {1+\ldots} } } }
+$$
+
+## A Rogers-Ramanujan Identity
+
+$$
+1 +  \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots =
+    \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})},
+     \quad\quad \text{for $|q|<1$}.
+$$
+
+## Maxwell's Equations
+
+$$
+\begin{align}
+  \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
+  \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
+  \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
+  \nabla \cdot \vec{\mathbf{B}} & = 0
+\end{align}
+$$

+ 158 - 0
apps/app/resource/locales/fr_FR/sandbox.md

@@ -0,0 +1,158 @@
+# What is Sandbox?
+- In this page, you will find tips that help you to master GROWI 
+- Feel free to enrich the content of your pages with the references under this hierarchy
+
+
+# :closed_book:Headings & Paragraphs
+- By inserting headings and paragraphs, you can make the text on the page easier to read
+
+## Headers
+- Add `#` before the heading text to create a heading 
+    - Depending on the number of `#`, the typeface size of headings would be different shown in the View screen 
+    - Check the View screen on the right side to understand the effect of headings
+- The number of `#` will decide the hierarchy level and help you to organize the contents
+
+```
+# First-level heading
+## Second-level heading
+### Third-level heading
+#### Forth-level heading
+##### Fifth-level heading
+###### Sixth-level heading
+```
+
+## Break
+- Insert two half-width spaces at the end of the sentence you want to break
+    - You can also change this in the Setting to break the line without half-width spaces
+        - Change the line break setting in the `Markdown Settings` sector of the admin page
+
+#### Without line break
+Paragraph 1
+Paragraph 2
+
+#### With line break
+Paragraph 1  
+Paragraph 2
+
+## Block
+- Paragraphs can be created by inserting a blank table in the text
+- Passage can be broken into sentences and make them easier to read
+
+#### Without paragraph
+Paragraph 1  
+Paragraph 2
+
+#### With paragraph
+Paragraph 1  
+
+Paragraph 2
+
+
+# :green_book: Styling Text
+- Various styles can be applied to enrich the textual expression of a sentence
+    - These styles also can be easily applied by selecting the toolbar icon at the bottom of the Edit screen
+
+## Italic
+- Enclose the text with an asterisk `*` or an underscore `_`.
+
+#### Examples
+- This sentence indicates emphasis with *Italic*
+- This sentence indicates emphasis with _Italic_ 
+
+## Bold
+- Enclose the text with two asterisks `*` or two underscores `_`
+
+#### Example
+- This sentence indicates emphasis with **Bold** 
+- This sentence indicates emphasis with __Bold__
+
+## Italic & Bold
+- Enclose the text with three asterisks `*` or three underscores `_`
+
+#### Example
+- This sentence indicates emphasis with ***Italic & Bold***
+- This sentence indicates emphasis witH ___Italic & Bold___
+
+
+# :orange_book: Insert Lists
+## Bulleted List
+- Insert a bulleted list by starting a line with a hyphen `-`, a plus `+`, or an asterisk `*`
+
+#### Example
+- This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
+        - This sentence is present in the bulleted list
+        - This sentence is present in the bulleted list
+- This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
+
+## Numbered List
+- `Number.` at the beginning of a line to insert a numbered list
+- Numbered list and bulleted list can also be combined for use
+
+#### Example
+1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+        - This sentence is present in the bulleted list 
+1. This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
+
+## Task List
+- Insert an unchecked checkbox list by writing `[] `
+    - Check the checkbox by writing `[x]`
+
+#### Example
+- [ ] Task 1
+    - [x] Task 1-1
+    - [ ] Task 1-2
+- [x] Task 2
+
+
+# :blue_book: Others
+## Blockquotes
+- Use quoted expressions by putting `>` at the beginning of the paragraph
+    - Multiple quotations can be expressed by using a sequence of `>` characters
+- Lists and other elements can be used together within the blockquotes
+
+#### Example
+> - Quotation
+> - Quotation
+>> Multiple quotations need to insert more `>`
+
+## Code
+- It is possible to express the code by adding it in three `` ` ``
+
+#### Example
+```
+Add codes here  
+Line breaks and paragraphs can be reflected in the code
+
+- List also can be used in code
+    - List also can be used in code
+```
+
+## Inline Code
+- Enclose words in `` ` `` to make inline code
+
+#### Example
+Here is the `inline code` 
+
+## Horizontal lines
+- Insert the horizontal line with three or more consecutive asterisks `*` or underscores `_`
+
+#### Example
+Below is a horizontal line
+***
+
+Below is a horizontal line
+___
+
+
+# :ledger: More Applications
+- [Bootstrap5](/Sandbox/Bootstrap5)
+
+- [Diagrams](/Sandbox/Diagrams)
+
+- [Math](/Sandbox/Math)

+ 48 - 0
apps/app/resource/locales/fr_FR/welcome.md

@@ -0,0 +1,48 @@
+# :tada: Bienvenue dans GROWI
+
+GROWI est un outil de documentation interne et une base de connaissances pour les entreprises et individus.  
+Avec GROWI, les utilisateurs peuvent facilement partager et modifier de l'information.
+
+<div class="alert alert-primary" role="alert">
+※Sentez-vous libre de modifier cette page en tant que page d'accueil de la documentation.
+</div>
+
+# :beginner: Que peut-on faire avec GROWI?
+## 1. Gestion des connaissances: Créer des pages pour stocker l'information
+- Comment créer et modifier des pages?
+    - Vous pouvez créer une nouvelle page en cliquant sur l'"icone de crayon" dans le coin supérieur gauche
+    - Vous pouvez modifier une page en cliquant sur le bouton "Edit" dans le coin supérieur droit
+- Comment gérer les pages?
+    - GROWI organise les pages en une structure **hiérarchique**
+        - Exemple: ` /page A/page B/page C ` 
+    - Des étiquettes peuvent également être ajoutées aux pages
+
+## 2. Récupération de l'information: Rechercher l'information de diverses manières
+- Recherche de mots-clés
+- Rechercer en utilisant les barres latérales
+    - Recherche par arbre
+    - Recherche par date de modification
+    - Recherche par étiquette, et plus...
+
+## 3. Partage de l'information: Facile à l'interne et l'externe
+- Vous pouvez envoyer l'URL and le lien permanent d'une page aux autres utilisateurs
+    - Les groupes d'utilisateurs permettent de gérer les permissions sur le contenu
+- GROWI permet le partage aux individus ne possédant pas de compte
+    - Les liens de partage permettent de rendre des pages publiques!
+
+#### :bulb: Voir [Sandbox](/Sandbox) pour en apprendre plus sur la modification des pages!
+
+
+# :wrench: Pour les administrateurs
+
+### :arrow_right: Comment utiliser GROWI avec plusieurs membres?
+- :heavy_check_mark: Inviter les membres!
+    - [Ajouter ou inviter de nouveaux utilisateurs dans GROWI](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#temporary-issuance-of-a-new-user)
+
+### :arrow_right: Insatisfait avec l'interface de GROWI?
+- :heavy_check_mark: Modifions l'apparence de l'interface GROWI!
+    - [Appliquer un thème](/admin/customize)
+
+### :arrow_right: La configuration est incomplète?
+- :heavy_check_mark: Remplissons les paramètres de sécurité!
+    - [Configurer les paramètres de sécurité](/admin/security)

+ 2 - 0
apps/app/src/client/util/locale-utils.ts

@@ -8,12 +8,14 @@ import * as nextI18NextConfig from '^/config/next-i18next.config';
 const DIAGRAMS_NET_LANG_MAP = {
   ja_JP: 'ja',
   zh_CN: 'zh',
+  fr_FR: 'fr',
 };
 
 const ACCEPT_LANG_MAP = {
   en: Lang.en_US,
   ja: Lang.ja_JP,
   zh: Lang.zh_CN,
+  fr: Lang.fr_FR,
 };
 
 export const getDiagramsNetLangCode = (lang) => {

+ 1 - 1
apps/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -3,7 +3,7 @@ import React, { useState, useCallback } from 'react';
 
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { UserPicture } from '@growi/ui/dist/components';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { useTranslation } from 'react-i18next';
 import { Tooltip } from 'reactstrap';

+ 1 - 1
apps/app/src/components/Admin/AuditLogManagement.tsx

@@ -2,7 +2,7 @@ import type { FC } from 'react';
 import React, { useState, useCallback, useRef } from 'react';
 
 import { LoadingSpinner } from '@growi/ui/dist/components';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'react-i18next';
 
 import type { IClearable } from '~/client/interfaces/clearable';

+ 1 - 1
apps/app/src/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
 import ArchiveFilesTableMenu from './ArchiveFilesTableMenu';

+ 3 - 4
apps/app/src/components/Admin/UserGroup/UserGroupForm.tsx

@@ -1,9 +1,8 @@
-import React, {
-  FC, useCallback, useEffect, useState,
-} from 'react';
+import type { FC } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 
 import type { IUserGroupHasId } from '@growi/core';
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
 type Props = {

+ 1 - 1
apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx

@@ -2,7 +2,7 @@ import type { FC } from 'react';
 import React, { useState, useEffect } from 'react';
 
 import type { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '@growi/core';
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import { UserPicture } from '@growi/ui/dist/components';
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
 import type { IUserGroupRelationHasIdPopulatedUser } from '~/interfaces/user-group-response';

+ 1 - 1
apps/app/src/components/Admin/Users/ExternalAccountTable.tsx

@@ -1,7 +1,7 @@
 import React, { useCallback } from 'react';
 
 import type { IAdminExternalAccount } from '@growi/core';
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
 import AdminExternalAccountsContainer from '~/client/services/AdminExternalAccountsContainer';

+ 1 - 1
apps/app/src/components/Admin/Users/UserTable.tsx

@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
 
 import type { IUserHasId } from '@growi/core';
 import { UserPicture } from '@growi/ui/dist/components';
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
 import AdminUsersContainer from '~/client/services/AdminUsersContainer';

+ 1 - 1
apps/app/src/components/AuthorInfo/AuthorInfo.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import type { IUser } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { UserPicture } from '@growi/ui/dist/components';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 

+ 4 - 8
apps/app/src/components/Comments.tsx

@@ -6,19 +6,16 @@ import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import { debounce } from 'throttle-debounce';
 
-import { type PageCommentProps } from '~/components/PageComment';
 import { useSWRxPageComment } from '~/stores/comment';
 import { useIsTrashPage, useSWRMUTxPageInfo } from '~/stores/page';
 
 import { useCurrentUser } from '../stores/context';
 
-import type { CommentEditorProps } from './PageComment/CommentEditor';
-
 const { isTopPage } = pagePathUtils;
 
 
-const PageComment = dynamic<PageCommentProps>(() => import('~/components/PageComment').then(mod => mod.PageComment), { ssr: false });
-const CommentEditor = dynamic<CommentEditorProps>(() => import('./PageComment/CommentEditor').then(mod => mod.CommentEditor), { ssr: false });
+const PageComment = dynamic(() => import('~/components/PageComment').then(mod => mod.PageComment), { ssr: false });
+const CommentEditorPre = dynamic(() => import('./PageComment/CommentEditor').then(mod => mod.CommentEditorPre), { ssr: false });
 
 export type CommentsProps = {
   pageId: string,
@@ -84,10 +81,9 @@ export const Comments = (props: CommentsProps): JSX.Element => {
       </div>
       {!isDeleted && (
         <div id="page-comment-write">
-          <CommentEditor
+          <CommentEditorPre
             pageId={pageId}
-            isForNewComment
-            onCommentButtonClicked={onCommentButtonClickHandler}
+            onCommented={onCommentButtonClickHandler}
             revisionId={revision._id}
           />
         </div>

+ 0 - 1
apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx

@@ -120,7 +120,6 @@ export const CopyDropdown = (props) => {
 
         <DropdownMenu
           strategy="fixed"
-          container="body"
         >
           <div className="d-flex align-items-center justify-content-between">
             <DropdownItem header className="px-3">

+ 1 - 1
apps/app/src/components/Me/ExternalAccountRow.jsx

@@ -1,7 +1,7 @@
 
 import React from 'react';
 
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 

+ 3 - 3
apps/app/src/components/Page/DisplaySwitcher.tsx

@@ -27,11 +27,11 @@ export const DisplaySwitcher = (props: Props): JSX.Element => {
   usePageUpdatedEffect();
   useHashChangedEffect();
 
-  const isViewMode = editorMode === EditorMode.View;
-
   return (
     <>
-      { isViewMode && pageView }
+      <div className="d-edit-none">
+        {pageView}
+      </div>
 
       <LazyRenderer shouldRender={isEditable === true && editorMode === EditorMode.Editor}>
         { isLatestRevision

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLink.tsx

@@ -51,7 +51,7 @@ export const ShareLink = (): JSX.Element => {
 
   return (
     <div className="container p-0" data-testid="share-link-management">
-      <h3 className="grw-modal-head d-flex pb-2">
+      <h3 className="d-flex pb-2">
         { t('share_links.share_link_list') }
         <button className="btn btn-danger ms-auto " type="button" onClick={deleteAllLinksButtonHandler}>{t('delete_all')}</button>
       </h3>

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkForm.tsx

@@ -102,7 +102,7 @@ export const ShareLinkForm: FC<Props> = (props: Props) => {
 
   return (
     <div className="share-link-form p-3">
-      <h3 className="grw-modal-head pb-2"> { t('share_links.share_settings') }</h3>
+      <h3 className="pb-2"> { t('share_links.share_settings') }</h3>
       <div className=" p-3">
 
         {/* ExpirationTypeOptions */}

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
 import { CopyDropdown } from '../../Common/CopyDropdown';

+ 1 - 1
apps/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -1,7 +1,7 @@
 import React, { useCallback } from 'react';
 
 import { UserPicture } from '@growi/ui/dist/components';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 

+ 8 - 3
apps/app/src/components/PageComment.tsx

@@ -26,7 +26,7 @@ import { ReplyComments } from './PageComment/ReplyComments';
 import styles from './PageComment.module.scss';
 
 
-export type PageCommentProps = {
+type PageCommentProps = {
   rendererOptions?: RendererOptions,
   pageId: string,
   pagePath: string,
@@ -168,8 +168,11 @@ export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps):
 
             return (
               <div key={comment._id} className={commentThreadClasses}>
+                {/* Comment */}
                 {commentElement(comment)}
+                {/* Reply comments */}
                 {hasReply && replyCommentsElement(allReplies[comment._id])}
+
                 {(!isReadOnly && !showEditorIds.has(comment._id)) && (
                   <div className="d-flex flex-row-reverse">
                     <NotAvailableForGuest>
@@ -187,14 +190,16 @@ export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps):
                     </NotAvailableForGuest>
                   </div>
                 )}
+
+                {/* Editor to reply */}
                 {(!isReadOnly && showEditorIds.has(comment._id)) && (
                   <CommentEditor
                     pageId={pageId}
                     replyTo={comment._id}
-                    onCancelButtonClicked={() => {
+                    onCanceled={() => {
                       removeShowEditorId(comment._id);
                     }}
-                    onCommentButtonClicked={() => onCommentButtonClickHandler(comment._id)}
+                    onCommented={() => onCommentButtonClickHandler(comment._id)}
                     revisionId={revisionId}
                   />
                 )}

+ 3 - 3
apps/app/src/components/PageComment/Comment.tsx

@@ -138,10 +138,10 @@ export const Comment = (props: CommentProps): JSX.Element => {
           replyTo={undefined}
           currentCommentId={commentId}
           commentBody={comment.comment}
-          onCancelButtonClicked={() => setIsReEdit(false)}
-          onCommentButtonClicked={() => {
+          onCanceled={() => setIsReEdit(false)}
+          onCommented={() => {
             setIsReEdit(false);
-            if (onComment != null) onComment();
+            onComment();
           }}
           revisionId={revisionId}
         />

+ 7 - 0
apps/app/src/components/PageComment/CommentEditor.module.scss

@@ -36,3 +36,10 @@
     padding-top: 0.5em;
   }
 }
+
+// border-radius
+.comment-editor-styles :global {
+  .cm-editor, .cm-scroller {
+    border-radius: var(--bs-border-radius);
+  }
+}

+ 173 - 189
apps/app/src/components/PageComment/CommentEditor.tsx

@@ -1,5 +1,7 @@
+import type { ReactNode } from 'react';
 import React, {
-  useCallback, useState, useRef, useEffect,
+  useCallback, useState, useEffect,
+  useMemo,
 } from 'react';
 
 import {
@@ -8,15 +10,13 @@ import {
 import { UserPicture } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
-import { useRouter } from 'next/router';
 import {
   TabContent, TabPane,
 } from 'reactstrap';
 
 import { uploadAttachments } from '~/client/services/upload-attachments';
 import { toastError } from '~/client/util/toastr';
-import type { IEditorMethods } from '~/interfaces/editor-methods';
-import { useSWRxPageComment, useSWRxEditingCommentsNum } from '~/stores/comment';
+import { useSWRxPageComment } from '~/stores/comment';
 import {
   useCurrentUser, useIsSlackConfigured, useAcceptedUploadFileType,
 } from '~/stores/context';
@@ -24,6 +24,7 @@ import {
   useSWRxSlackChannels, useIsSlackEnabled, useIsEnabledUnsavedWarning, useEditorSettings,
 } from '~/stores/editor';
 import { useCurrentPagePath } from '~/stores/page';
+import { useCommentEditorDirtyMap } from '~/stores/ui';
 import { useNextThemes } from '~/stores/use-next-themes';
 import loggerFactory from '~/utils/logger';
 
@@ -43,23 +44,35 @@ const logger = loggerFactory('growi:components:CommentEditor');
 
 const SlackNotification = dynamic(() => import('../SlackNotification').then(mod => mod.SlackNotification), { ssr: false });
 
-export type CommentEditorProps = {
+
+const CommentEditorLayout = ({ children }: { children: ReactNode }): JSX.Element => {
+  return (
+    <div className={`${styles['comment-editor-styles']} form`}>
+      <div className="comment-form">
+        <div className="bg-comment rounded">
+          {children}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+
+type CommentEditorProps = {
   pageId: string,
-  isForNewComment?: boolean,
   replyTo?: string,
   revisionId: string,
   currentCommentId?: string,
   commentBody?: string,
-  onCancelButtonClicked?: () => void,
-  onCommentButtonClicked?: () => void,
+  onCanceled?: () => void,
+  onCommented?: () => void,
 }
 
-
 export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
 
   const {
-    pageId, isForNewComment, replyTo, revisionId,
-    currentCommentId, commentBody, onCancelButtonClicked, onCommentButtonClicked,
+    pageId, replyTo, revisionId,
+    currentCommentId, commentBody, onCanceled, onCommented,
   } = props;
 
   const { data: currentUser } = useCurrentUser();
@@ -72,39 +85,31 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   const { data: editorSettings } = useEditorSettings();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const {
-    increment: incrementEditingCommentsNum,
-    decrement: decrementEditingCommentsNum,
-  } = useSWRxEditingCommentsNum();
+    evaluate: evaluateEditorDirtyMap,
+    clean: cleanEditorDirtyMap,
+  } = useCommentEditorDirtyMap();
   const { mutate: mutateResolvedTheme } = useResolvedThemeForEditor();
-  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.COMMENT);
   const { resolvedTheme } = useNextThemes();
   mutateResolvedTheme({ themeData: resolvedTheme });
 
-  const [isReadyToUse, setIsReadyToUse] = useState(!isForNewComment);
-  const [comment, setComment] = useState(commentBody ?? '');
+  const editorKey = useMemo(() => {
+    if (replyTo != null) {
+      return `comment_replyTo_${replyTo}`;
+    }
+    if (currentCommentId != null) {
+      return `comment_edit_${currentCommentId}`;
+    }
+    return GlobalCodeMirrorEditorKey.COMMENT_NEW;
+  }, [currentCommentId, replyTo]);
+
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
+
   const [showPreview, setShowPreview] = useState(false);
   const [error, setError] = useState();
   const [slackChannels, setSlackChannels] = useState<string>('');
-  const [incremented, setIncremented] = useState(false);
 
   const { t } = useTranslation('');
 
-  const editorRef = useRef<IEditorMethods>(null);
-
-  const router = useRouter();
-
-  // UnControlled CodeMirror value is not reset on page transition, so explicitly set the value to the initial value
-  const onRouterChangeComplete = useCallback(() => {
-    editorRef.current?.setValue('');
-  }, []);
-
-  useEffect(() => {
-    router.events.on('routeChangeComplete', onRouterChangeComplete);
-    return () => {
-      router.events.off('routeChangeComplete', onRouterChangeComplete);
-    };
-  }, [onRouterChangeComplete, router.events]);
-
   const handleSelect = useCallback((showPreview: boolean) => {
     setShowPreview(showPreview);
   }, []);
@@ -129,47 +134,34 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   }, []);
 
   const initializeEditor = useCallback(async() => {
-    const editingCommentsNum = comment !== '' ? await decrementEditingCommentsNum() : undefined;
+    const dirtyNum = await cleanEditorDirtyMap(editorKey);
+    mutateIsEnabledUnsavedWarning(dirtyNum > 0);
 
-    setComment('');
     setShowPreview(false);
     setError(undefined);
-    initializeSlackEnabled();
-    // reset value
-    if (editorRef.current == null) { return }
-    editorRef.current.setValue('');
 
-    if (editingCommentsNum != null && editingCommentsNum === 0) {
-      mutateIsEnabledUnsavedWarning(false); // must be after clearing comment or else onChange will override bool
-    }
+    initializeSlackEnabled();
 
-  }, [initializeSlackEnabled, comment, decrementEditingCommentsNum, mutateIsEnabledUnsavedWarning]);
+  }, [editorKey, cleanEditorDirtyMap, mutateIsEnabledUnsavedWarning, initializeSlackEnabled]);
 
   const cancelButtonClickedHandler = useCallback(() => {
-    // change state to not ready
-    // when this editor is for the new comment mode
-    if (isForNewComment) {
-      setIsReadyToUse(false);
-    }
-
     initializeEditor();
-
-    if (onCancelButtonClicked != null) {
-      onCancelButtonClicked();
-    }
-  }, [isForNewComment, onCancelButtonClicked, initializeEditor]);
+    onCanceled?.();
+  }, [onCanceled, initializeEditor]);
 
   const postCommentHandler = useCallback(async() => {
+    const commentBodyToPost = codeMirrorEditor?.getDoc() ?? '';
+
     try {
       if (currentCommentId != null) {
         // update current comment
-        await updateComment(comment, revisionId, currentCommentId);
+        await updateComment(commentBodyToPost, revisionId, currentCommentId);
       }
       else {
         // post new comment
         const postCommentArgs = {
           commentForm: {
-            comment,
+            comment: commentBodyToPost,
             revisionId,
             replyTo,
           },
@@ -183,9 +175,7 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
 
       initializeEditor();
 
-      if (onCommentButtonClicked != null) {
-        onCommentButtonClicked();
-      }
+      onCommented?.();
 
       // Insert empty string as new comment editor is opened after comment
       codeMirrorEditor?.initDoc('');
@@ -194,10 +184,8 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
       const errorMessage = err.message || 'An unknown error occured when posting comment';
       setError(errorMessage);
     }
-  }, [
-    currentCommentId, initializeEditor, onCommentButtonClicked, codeMirrorEditor,
-    updateComment, comment, revisionId, replyTo, isSlackEnabled, slackChannels, postComment,
-  ]);
+  // eslint-disable-next-line max-len
+  }, [currentCommentId, initializeEditor, onCommented, codeMirrorEditor, updateComment, revisionId, replyTo, isSlackEnabled, slackChannels, postComment]);
 
   // the upload event handler
   const uploadHandler = useCallback((files: File[]) => {
@@ -218,52 +206,10 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
     });
   }, [codeMirrorEditor, pageId]);
 
-  const getCommentHtml = useCallback(() => {
-    if (currentPagePath == null) {
-      return <></>;
-    }
-
-    return <CommentPreview markdown={comment} />;
-  }, [currentPagePath, comment]);
-
-  const renderBeforeReady = useCallback((): JSX.Element => {
-    return (
-      <div>
-        <NotAvailableForGuest>
-          <NotAvailableForReadOnlyUser>
-            <button
-              type="button"
-              className="btn btn-outline-primary w-100 text-start py-3"
-              onClick={() => setIsReadyToUse(true)}
-              data-testid="open-comment-editor-button"
-            >
-              <UserPicture user={currentUser} noLink noTooltip additionalClassName="me-3" />
-              <span className="material-symbols-outlined me-1 fs-5">add_comment</span>
-              <small>{t('page_comment.add_a_comment')}...</small>
-            </button>
-          </NotAvailableForReadOnlyUser>
-        </NotAvailableForGuest>
-      </div>
-    );
-  }, [currentUser]);
-
-  // const onChangeHandler = useCallback((newValue: string, isClean: boolean) => {
-  //   setComment(newValue);
-  //   if (!isClean && !incremented) {
-  //     incrementEditingCommentsNum();
-  //     setIncremented(true);
-  //   }
-  //   mutateIsEnabledUnsavedWarning(!isClean);
-  // }, [mutateIsEnabledUnsavedWarning, incrementEditingCommentsNum, incremented]);
-
-  const onChangeHandler = useCallback((newValue: string) => {
-    setComment(newValue);
-
-    if (!incremented) {
-      incrementEditingCommentsNum();
-      setIncremented(true);
-    }
-  }, [incrementEditingCommentsNum, incremented]);
+  const onChangeHandler = useCallback(async(value: string) => {
+    const dirtyNum = await evaluateEditorDirtyMap(editorKey, value);
+    mutateIsEnabledUnsavedWarning(dirtyNum > 0);
+  }, [editorKey, evaluateEditorDirtyMap, mutateIsEnabledUnsavedWarning]);
 
   // initialize CodeMirrorEditor
   useEffect(() => {
@@ -274,20 +220,18 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   }, [codeMirrorEditor, commentBody]);
 
 
-  const renderReady = () => {
-    const commentPreview = getCommentHtml();
-
-    const errorMessage = <span className="text-danger text-end me-2">{error}</span>;
-    const cancelButton = (
-      <button
-        type="button"
-        className="btn btn-outline-neutral-secondary"
-        onClick={cancelButtonClickedHandler}
-      >
-        {t('Cancel')}
-      </button>
-    );
-    const submitButton = (
+  const errorMessage = useMemo(() => <span className="text-danger text-end me-2">{error}</span>, [error]);
+  const cancelButton = useMemo(() => (
+    <button
+      type="button"
+      className="btn btn-outline-neutral-secondary"
+      onClick={cancelButtonClickedHandler}
+    >
+      {t('Cancel')}
+    </button>
+  ), [cancelButtonClickedHandler, t]);
+  const submitButton = useMemo(() => {
+    return (
       <button
         type="button"
         data-testid="comment-submit-button"
@@ -297,79 +241,119 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
         {t('page_comment.comment')}
       </button>
     );
+  }, [postCommentHandler, t]);
 
-    return (
-      <>
-        <div className="px-4 pt-3 pb-1">
-          <div className="d-flex justify-content-between align-items-center mb-2">
-            <div className="d-flex">
-              <UserPicture user={currentUser} noLink noTooltip />
-              <p className="ms-2 mb-0">{t('page_comment.add_a_comment')}</p>
-            </div>
-            <SwitchingButtonGroup showPreview={showPreview} onSelected={handleSelect} />
-          </div>
-          <TabContent activeTab={showPreview ? 'comment_preview' : 'comment_editor'}>
-            <TabPane tabId="comment_editor">
-              <CodeMirrorEditorComment
-                acceptedUploadFileType={acceptedUploadFileType}
-                onChange={onChangeHandler}
-                onSave={postCommentHandler}
-                onUpload={uploadHandler}
-                editorSettings={editorSettings}
-              />
-            </TabPane>
-            <TabPane tabId="comment_preview">
-              <div className="comment-preview-container">
-                {commentPreview}
-              </div>
-            </TabPane>
-          </TabContent>
-        </div>
-
-        <div className="comment-submit px-4 pb-3 mb-2">
+  return (
+    <CommentEditorLayout>
+      <div className="px-4 pt-3 pb-1">
+        <div className="d-flex justify-content-between align-items-center mb-2">
           <div className="d-flex">
-            <span className="flex-grow-1" />
-            <span className="d-none d-sm-inline">{errorMessage && errorMessage}</span>
-
-            {isSlackConfigured && isSlackEnabled != null
-              && (
-                <div className="align-self-center me-md-3">
-                  <SlackNotification
-                    isSlackEnabled={isSlackEnabled}
-                    slackChannels={slackChannels}
-                    onEnabledFlagChange={isSlackEnabledToggleHandler}
-                    onChannelChange={slackChannelsChangedHandler}
-                    id="idForComment"
-                  />
-                </div>
-              )
-            }
-            <div className="d-none d-sm-block">
-              <span className="me-2">{cancelButton}</span><span>{submitButton}</span>
-            </div>
-          </div>
-          <div className="d-block d-sm-none mt-2">
-            <div className="d-flex justify-content-end">
-              {error && errorMessage}
-              <span className="me-2">{cancelButton}</span><span>{submitButton}</span>
-            </div>
+            <UserPicture user={currentUser} noLink noTooltip />
+            <p className="ms-2 mb-0">{t('page_comment.add_a_comment')}</p>
           </div>
+          <SwitchingButtonGroup showPreview={showPreview} onSelected={handleSelect} />
         </div>
-      </>
-    );
-  };
+        <TabContent activeTab={showPreview ? 'comment_preview' : 'comment_editor'}>
+          <TabPane tabId="comment_editor">
+            <CodeMirrorEditorComment
+              editorKey={editorKey}
+              acceptedUploadFileType={acceptedUploadFileType}
+              onChange={onChangeHandler}
+              onSave={postCommentHandler}
+              onUpload={uploadHandler}
+              editorSettings={editorSettings}
+            />
+          </TabPane>
+          <TabPane tabId="comment_preview">
+            <div className="comment-preview-container">
+              <CommentPreview markdown={codeMirrorEditor?.getDoc() ?? ''} />
+            </div>
+          </TabPane>
+        </TabContent>
+      </div>
 
-  return (
-    <div className={`${styles['comment-editor-styles']} form page-comment-form`}>
-      <div className="comment-form">
-        <div className="comment-form-main bg-comment rounded">
-          {isReadyToUse
-            ? renderReady()
-            : renderBeforeReady()
+      <div className="comment-submit px-4 pb-3 mb-2">
+        <div className="d-flex">
+          <span className="flex-grow-1" />
+          <span className="d-none d-sm-inline">{errorMessage && errorMessage}</span>
+
+          {isSlackConfigured && isSlackEnabled != null
+            && (
+              <div className="align-self-center me-md-3">
+                <SlackNotification
+                  isSlackEnabled={isSlackEnabled}
+                  slackChannels={slackChannels}
+                  onEnabledFlagChange={isSlackEnabledToggleHandler}
+                  onChannelChange={slackChannelsChangedHandler}
+                  id="idForComment"
+                />
+              </div>
+            )
           }
+          <div className="d-none d-sm-block">
+            <span className="me-2">{cancelButton}</span><span>{submitButton}</span>
+          </div>
+        </div>
+        <div className="d-block d-sm-none mt-2">
+          <div className="d-flex justify-content-end">
+            {error && errorMessage}
+            <span className="me-2">{cancelButton}</span><span>{submitButton}</span>
+          </div>
         </div>
       </div>
-    </div>
+    </CommentEditorLayout>
   );
 
 };
+
+
+export const CommentEditorPre = (props: CommentEditorProps): JSX.Element => {
+
+  const { onCommented, onCanceled, ...rest } = props;
+
+  const { data: currentUser } = useCurrentUser();
+  const { mutate: mutateResolvedTheme } = useResolvedThemeForEditor();
+  const { resolvedTheme } = useNextThemes();
+  mutateResolvedTheme({ themeData: resolvedTheme });
+
+  const [isReadyToUse, setIsReadyToUse] = useState(false);
+
+  const { t } = useTranslation('');
+
+  const render = useCallback((): JSX.Element => {
+    return (
+      <CommentEditorLayout>
+        <NotAvailableForGuest>
+          <NotAvailableForReadOnlyUser>
+            <button
+              type="button"
+              className="btn btn-outline-primary w-100 text-start py-3"
+              onClick={() => setIsReadyToUse(true)}
+              data-testid="open-comment-editor-button"
+            >
+              <UserPicture user={currentUser} noLink noTooltip additionalClassName="me-3" />
+              <span className="material-symbols-outlined me-1 fs-5">add_comment</span>
+              <small>{t('page_comment.add_a_comment')}...</small>
+            </button>
+          </NotAvailableForReadOnlyUser>
+        </NotAvailableForGuest>
+      </CommentEditorLayout>
+    );
+  }, [currentUser, t]);
+
+  return isReadyToUse
+    ? (
+      <CommentEditor
+        onCommented={() => {
+          onCommented?.();
+          setIsReadyToUse(false);
+        }}
+        onCanceled={() => {
+          onCanceled?.();
+          setIsReadyToUse(false);
+        }}
+        {...rest}
+      />
+    )
+    : render();
+};

+ 1 - 2
apps/app/src/components/PageComment/DeleteCommentModal.tsx

@@ -2,8 +2,7 @@ import React from 'react';
 
 import { isPopulated } from '@growi/core';
 import { UserPicture } from '@growi/ui/dist/components';
-import { format } from 'date-fns';
-import { t } from 'i18next';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import {
   Button, Modal, ModalHeader, ModalBody, ModalFooter,

+ 4 - 4
apps/app/src/components/PageCreateModal.tsx

@@ -4,7 +4,7 @@ import React, {
 
 import { Origin } from '@growi/core';
 import { pagePathUtils, pathUtils } from '@growi/core/dist/utils';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, UncontrolledButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem,
@@ -139,7 +139,7 @@ const PageCreateModal: React.FC = () => {
     return (
       <div className="row">
         <fieldset className="col-12 mb-4">
-          <h3 className="grw-modal-head pb-2">{t('create_page_dropdown.todays.desc', { ns: 'commons' })}</h3>
+          <h3 className="pb-2">{t('create_page_dropdown.todays.desc', { ns: 'commons' })}</h3>
 
           <div className="d-sm-flex align-items-center justify-items-between">
 
@@ -184,7 +184,7 @@ const PageCreateModal: React.FC = () => {
     return (
       <div className="row" data-testid="row-create-page-under-below">
         <fieldset className="col-12 mb-4">
-          <h3 className="grw-modal-head pb-2">{t('Create under')}</h3>
+          <h3 className="pb-2">{t('Create under')}</h3>
 
           <div className="d-sm-flex align-items-center justify-items-between">
             <div className="flex-fill">
@@ -242,7 +242,7 @@ const PageCreateModal: React.FC = () => {
       <div className="row">
         <fieldset className="col-12">
 
-          <h3 className="grw-modal-head pb-2">
+          <h3 className="pb-2">
             {t('template.modal_label.Create template under')}<br />
             <code className="h6" data-testid="grw-page-create-modal-path-name">{pathname}</code>
           </h3>

+ 1 - 1
apps/app/src/components/PageEditor/ConflictDiffModal.tsx

@@ -7,7 +7,7 @@ import {
   MergeViewer, CodeMirrorEditorDiff, GlobalCodeMirrorEditorKey, useCodeMirrorEditorIsolated,
 } from '@growi/editor';
 import { UserPicture } from '@growi/ui/dist/components';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,

+ 1 - 1
apps/app/src/components/PageList/PageListItemL.tsx

@@ -10,7 +10,7 @@ import { isIPageInfoForListing, isIPageInfoForEntity } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
 import { pathUtils } from '@growi/core/dist/utils';
 import { UserPicture, PageListMeta } from '@growi/ui/dist/components';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import Clamp from 'react-multiline-clamp';

+ 11 - 12
apps/app/src/components/PageRenameModal.tsx

@@ -209,12 +209,12 @@ const PageRenameModal = (): JSX.Element => {
 
     return (
       <>
-        <div>
-          <label className="form-label">{ t('modal_rename.label.Current page name') }</label><br />
-          <code>{ path }</code>
+        <div className="mb-3">
+          <label className="form-label w-100">{ t('modal_rename.label.Current page name') }</label>
+          <code className="fs-6">{ path }</code>
         </div>
-        <div>
-          <label htmlFor="newPageName" className="form-label">{ t('modal_rename.label.New page name') }</label><br />
+        <div className="mb-3">
+          <label htmlFor="newPageName" className="form-label w-100">{ t('modal_rename.label.New page name') }</label>
           <div className="input-group">
             <div>
               <span className="input-group-text">{siteUrl}</span>
@@ -241,15 +241,14 @@ const PageRenameModal = (): JSX.Element => {
                 )}
             </form>
           </div>
+          { isTargetPageDuplicate && (
+            <p className="text-danger">Error: Target path is duplicated.</p>
+          ) }
+          { isMatchedWithUserHomepagePath && (
+            <p className="text-danger">Error: Cannot move to directory under /user page.</p>
+          ) }
         </div>
 
-        { isTargetPageDuplicate && (
-          <p className="text-danger">Error: Target path is duplicated.</p>
-        ) }
-        { isMatchedWithUserHomepagePath && (
-          <p className="text-danger">Error: Cannot move to directory under /user page.</p>
-        ) }
-
         { !isV5Compatible(page.meta) && (
           <>
             <div className="form-check form-check-warning">

+ 2 - 3
apps/app/src/components/SearchPage/SearchResultContent.tsx

@@ -1,6 +1,6 @@
 import type { FC } from 'react';
 import React, {
-  useCallback, useEffect, useRef, useState,
+  useCallback, useEffect, useRef,
 } from 'react';
 
 import { getIdForRef } from '@growi/core';
@@ -27,7 +27,6 @@ import { mutateSearching } from '~/stores/search';
 import type { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 import { PagePathNav } from '../Common/PagePathNav';
 import { type RevisionLoaderProps } from '../Page/RevisionLoader';
-import { type PageCommentProps } from '../PageComment';
 import type { PageContentFooterProps } from '../PageContentFooter';
 
 import styles from './SearchResultContent.module.scss';
@@ -38,7 +37,7 @@ const _fluidLayoutClass = styles['fluid-layout'];
 
 const PageControls = dynamic(() => import('../PageControls').then(mod => mod.PageControls), { ssr: false });
 const RevisionLoader = dynamic<RevisionLoaderProps>(() => import('../Page/RevisionLoader').then(mod => mod.RevisionLoader), { ssr: false });
-const PageComment = dynamic<PageCommentProps>(() => import('../PageComment').then(mod => mod.PageComment), { ssr: false });
+const PageComment = dynamic(() => import('../PageComment').then(mod => mod.PageComment), { ssr: false });
 const PageContentFooter = dynamic<PageContentFooterProps>(() => import('../PageContentFooter').then(mod => mod.PageContentFooter), { ssr: false });
 
 type AdditionalMenuItemsProps = AdditionalMenuItemsRendererProps & {

+ 1 - 1
apps/app/src/components/Sidebar/PageCreateButton/hooks/use-create-todays-memo.tsx

@@ -2,7 +2,7 @@ import { useCallback } from 'react';
 
 import { Origin } from '@growi/core';
 import { userHomepagePath } from '@growi/core/dist/utils/page-path-utils';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import { useTranslation } from 'react-i18next';
 
 import { useCreatePageAndTransit } from '~/client/services/create-page';

+ 1 - 1
apps/app/src/components/TemplateModal/use-formatter.tsx

@@ -1,6 +1,6 @@
 import path from 'path';
 
-import dateFnsFormat from 'date-fns/format';
+import { format as dateFnsFormat } from 'date-fns/format';
 import mustache from 'mustache';
 
 import { useCurrentPagePath } from '~/stores/page';

+ 1 - 1
apps/app/src/components/User/UserDate.jsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 import PropTypes from 'prop-types';
 
 

+ 1 - 1
apps/app/src/server/models/attachment.ts

@@ -1,7 +1,7 @@
 import path from 'path';
 
 import type { IAttachment } from '@growi/core';
-import { addSeconds } from 'date-fns';
+import { addSeconds } from 'date-fns/addSeconds';
 import {
   Schema, type Model, type Document, Types,
 } from 'mongoose';

+ 1 - 1
apps/app/src/server/models/obsolete-page.js

@@ -1,5 +1,6 @@
 import { GroupType, Origin } from '@growi/core';
 import { templateChecker, pagePathUtils, pathUtils } from '@growi/core/dist/utils';
+import { differenceInYears } from 'date-fns/differenceInYears';
 import escapeStringRegexp from 'escape-string-regexp';
 
 import { Comment } from '~/features/comment/server/models/comment';
@@ -18,7 +19,6 @@ import UserGroupRelation from './user-group-relation';
 
 const nodePath = require('path');
 
-const differenceInYears = require('date-fns/differenceInYears');
 const debug = require('debug')('growi:models:page');
 const mongoose = require('mongoose');
 const urljoin = require('url-join');

+ 1 - 1
apps/app/src/server/models/page-operation.ts

@@ -1,6 +1,6 @@
 import type { IGrantedGroup } from '@growi/core';
 import { GroupType } from '@growi/core';
-import { addSeconds } from 'date-fns';
+import { addSeconds } from 'date-fns/addSeconds';
 import type {
   Model, Document, QueryOptions, FilterQuery,
 } from 'mongoose';

+ 3 - 2
apps/app/src/server/models/password-reset-order.ts

@@ -1,8 +1,9 @@
 import crypto from 'crypto';
 
-import { addMinutes } from 'date-fns';
+import { addMinutes } from 'date-fns/addMinutes';
+import type { Model, Document } from 'mongoose';
 import mongoose, {
-  Schema, Model, Document,
+  Schema,
 } from 'mongoose';
 import uniqueValidator from 'mongoose-unique-validator';
 

+ 3 - 2
apps/app/src/server/models/user-registration-order.ts

@@ -1,8 +1,9 @@
 import crypto from 'crypto';
 
-import { addHours } from 'date-fns';
+import { addHours } from 'date-fns/addHours';
+import type { Model, Document } from 'mongoose';
 import {
-  Schema, Model, Document,
+  Schema,
 } from 'mongoose';
 import uniqueValidator from 'mongoose-unique-validator';
 

+ 4 - 0
apps/app/src/server/routes/comment.js

@@ -244,6 +244,10 @@ module.exports = function(crowi, app) {
       return res.json(ApiResponse.error('Current user is not accessible to this page.'));
     }
 
+    if (comment === '') {
+      return res.json(ApiResponse.error('Comment text is required'));
+    }
+
     let createdComment;
     try {
       createdComment = await Comment.add(pageId, req.user._id, revisionId, comment, position, replyTo);

+ 1 - 1
apps/app/src/server/service/config-loader.ts

@@ -1,5 +1,5 @@
 import { envUtils } from '@growi/core/dist/utils';
-import { parseISO } from 'date-fns';
+import { parseISO } from 'date-fns/parseISO';
 
 import { GrowiServiceType } from '~/features/questionnaire/interfaces/growi-info';
 import loggerFactory from '~/utils/logger';

+ 3 - 2
apps/app/src/server/service/config-manager.ts

@@ -1,11 +1,12 @@
-import parseISO from 'date-fns/parseISO';
+import { parseISO } from 'date-fns/parseISO';
 
 import loggerFactory from '~/utils/logger';
 
 import ConfigModel from '../models/config';
 import S2sMessage from '../models/vo/s2s-message';
 
-import ConfigLoader, { ConfigObject } from './config-loader';
+import type { ConfigObject } from './config-loader';
+import ConfigLoader from './config-loader';
 import type { S2sMessagingService } from './s2s-messaging/base';
 import type { S2sMessageHandlable } from './s2s-messaging/handlable';
 

+ 7 - 6
apps/app/src/server/service/in-app-notification.ts

@@ -2,21 +2,22 @@ import type {
   HasObjectId, IUser, IPage,
 } from '@growi/core';
 import { SubscriptionStatusType } from '@growi/core';
-import { subDays } from 'date-fns';
-import { Types, FilterQuery, UpdateQuery } from 'mongoose';
+import { subDays } from 'date-fns/subDays';
+import type { Types, FilterQuery, UpdateQuery } from 'mongoose';
 
 import { AllEssentialActions } from '~/interfaces/activity';
-import { InAppNotificationStatuses, PaginateResult } from '~/interfaces/in-app-notification';
-import { ActivityDocument } from '~/server/models/activity';
+import type { PaginateResult } from '~/interfaces/in-app-notification';
+import { InAppNotificationStatuses } from '~/interfaces/in-app-notification';
+import type { ActivityDocument } from '~/server/models/activity';
+import type { InAppNotificationDocument } from '~/server/models/in-app-notification';
 import {
   InAppNotification,
-  InAppNotificationDocument,
 } from '~/server/models/in-app-notification';
 import InAppNotificationSettings from '~/server/models/in-app-notification-settings';
 import Subscription from '~/server/models/subscription';
 import loggerFactory from '~/utils/logger';
 
-import Crowi from '../crowi';
+import type Crowi from '../crowi';
 import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
 
 import { generateSnapshot } from './in-app-notification/in-app-notification-utils';

+ 1 - 1
apps/app/src/server/service/installer.ts

@@ -3,7 +3,7 @@ import path from 'path';
 import type {
   Lang, IPage, IUser,
 } from '@growi/core';
-import { addSeconds } from 'date-fns';
+import { addSeconds } from 'date-fns/addSeconds';
 import ExtensibleCustomError from 'extensible-custom-error';
 import fs from 'graceful-fs';
 import mongoose from 'mongoose';

+ 11 - 28
apps/app/src/stores/comment.tsx

@@ -1,11 +1,12 @@
+import { useCallback } from 'react';
+
 import type { Nullable } from '@growi/core';
-import useSWR, { SWRResponse } from 'swr';
+import type { SWRResponse } from 'swr';
+import useSWR from 'swr';
 
 import { apiGet, apiPost } from '~/client/util/apiv1-client';
 
-import { ICommentHasIdList, ICommentPostArgs } from '../interfaces/comment';
-
-import { useStaticSWR } from './use-static-swr';
+import type { ICommentHasIdList, ICommentPostArgs } from '../interfaces/comment';
 
 type IResponseComment = {
   comments: ICommentHasIdList,
@@ -25,8 +26,9 @@ export const useSWRxPageComment = (pageId: Nullable<string>): SWRResponse<IComme
     ([endpoint, pageId]) => apiGet(endpoint, { page_id: pageId }).then((response:IResponseComment) => response.comments),
   );
 
-  const update = async(comment: string, revisionId: string, commentId: string) => {
-    const { mutate } = swrResponse;
+  const { mutate } = swrResponse;
+
+  const update = useCallback(async(comment: string, revisionId: string, commentId: string) => {
     await apiPost('/comments.update', {
       commentForm: {
         comment,
@@ -35,10 +37,9 @@ export const useSWRxPageComment = (pageId: Nullable<string>): SWRResponse<IComme
       },
     });
     mutate();
-  };
+  }, [mutate]);
 
-  const post = async(args: ICommentPostArgs) => {
-    const { mutate } = swrResponse;
+  const post = useCallback(async(args: ICommentPostArgs) => {
     const { commentForm, slackNotificationForm } = args;
     const { comment, revisionId, replyTo } = commentForm;
     const { isSlackEnabled, slackChannels } = slackNotificationForm;
@@ -56,7 +57,7 @@ export const useSWRxPageComment = (pageId: Nullable<string>): SWRResponse<IComme
       },
     });
     mutate();
-  };
+  }, [mutate, pageId]);
 
   return {
     ...swrResponse,
@@ -64,21 +65,3 @@ export const useSWRxPageComment = (pageId: Nullable<string>): SWRResponse<IComme
     post,
   };
 };
-
-type EditingCommentsNumOperation = {
-  increment(): Promise<number | undefined>,
-  decrement(): Promise<number | undefined>,
-}
-
-export const useSWRxEditingCommentsNum = (): SWRResponse<number, Error> & EditingCommentsNumOperation => {
-  const swrResponse = useStaticSWR<number, Error>('editingCommentsNum', undefined, { fallbackData: 0 });
-
-  return {
-    ...swrResponse,
-    increment: () => swrResponse.mutate((swrResponse.data ?? 0) + 1),
-    decrement: () => {
-      const newValue = (swrResponse.data ?? 0) - 1;
-      return swrResponse.mutate(Math.max(0, newValue));
-    },
-  };
-};

+ 57 - 2
apps/app/src/stores/ui.tsx

@@ -1,5 +1,6 @@
 import {
   type RefObject, useCallback, useEffect,
+  useLayoutEffect,
 } from 'react';
 
 import { PageGrant, type Nullable } from '@growi/core';
@@ -7,6 +8,7 @@ import { type SWRResponseWithUtils, useSWRStatic, withUtils } from '@growi/core/
 import { pagePathUtils, isClient, isServer } from '@growi/core/dist/utils';
 import { Breakpoint } from '@growi/ui/dist/interfaces';
 import { addBreakpointListener, cleanupBreakpointListener } from '@growi/ui/dist/utils';
+import { useRouter } from 'next/router';
 import type { HtmlElementNode } from 'rehype-toc';
 import type SimpleBar from 'simplebar-react';
 import type { MutatorOptions } from 'swr';
@@ -15,9 +17,8 @@ import {
 } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
-import type { IFocusable } from '~/client/interfaces/focusable';
 import { scheduleToPut } from '~/client/services/user-ui-settings';
-import type { IPageGrantData, IPageSelectedGrant } from '~/interfaces/page';
+import type { IPageSelectedGrant } from '~/interfaces/page';
 import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
 import type { UpdateDescCountData } from '~/interfaces/websocket';
 import {
@@ -384,6 +385,60 @@ export const usePageTreeDescCountMap = (initialData?: UpdateDescCountData): SWRR
   };
 };
 
+
+type UseCommentEditorDirtyMapOperation = {
+  evaluate(key: string, commentBody: string): Promise<number>,
+  clean(key: string): Promise<number>,
+}
+
+export const useCommentEditorDirtyMap = (): SWRResponse<Map<string, boolean>, Error> & UseCommentEditorDirtyMapOperation => {
+  const router = useRouter();
+
+  const swrResponse = useSWRStatic<Map<string, boolean>, Error>('editingCommentsNum', undefined, { fallbackData: new Map() });
+
+  const { mutate } = swrResponse;
+
+  const evaluate = useCallback(async(key: string, commentBody: string) => {
+    const newMap = await mutate((map) => {
+      if (map == null) return new Map();
+
+      if (commentBody.length === 0) {
+        map.delete(key);
+      }
+      else {
+        map.set(key, true);
+      }
+
+      return map;
+    });
+    return newMap?.size ?? 0;
+  }, [mutate]);
+  const clean = useCallback(async(key: string) => {
+    const newMap = await mutate((map) => {
+      if (map == null) return new Map();
+      map.delete(key);
+      return map;
+    });
+    return newMap?.size ?? 0;
+  }, [mutate]);
+
+  const reset = useCallback(() => mutate(new Map()), [mutate]);
+
+  useLayoutEffect(() => {
+    router.events.on('routeChangeComplete', reset);
+    return () => {
+      router.events.off('routeChangeComplete', reset);
+    };
+  }, [reset, router.events]);
+
+  return {
+    ...swrResponse,
+    evaluate,
+    clean,
+  };
+};
+
+
 /** **********************************************************
  *                          SWR Hooks
  *                Determined value by context

+ 0 - 4
apps/app/src/styles/_layout.scss

@@ -17,10 +17,6 @@
   }
 }
 
-.grw-modal-head {
-  border-bottom: 1px solid transparent;
-}
-
 .grw-scrollable-modal-body {
   max-height: calc(100vh - 330px);
   overflow-y: scroll;

+ 1 - 1
apps/app/test/integration/service/v5.page.test.ts

@@ -1,4 +1,4 @@
-import { addSeconds } from 'date-fns';
+import { addSeconds } from 'date-fns/addSeconds';
 import mongoose from 'mongoose';
 
 import { PageActionStage, PageActionType } from '../../../src/interfaces/page-operation';

+ 3 - 3
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "7.0.2-slackbot-proxy.0",
+  "version": "7.0.3-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
@@ -41,7 +41,7 @@
     "bunyan": "^1.8.15",
     "compression": "^1.7.4",
     "cookie-parser": "^1.4.5",
-    "date-fns": "^2.23.0",
+    "date-fns": "^3.6.0",
     "express-bunyan-logger": "^1.3.3",
     "extensible-custom-error": "^0.0.7",
     "helmet": "^4.6.0",
@@ -49,7 +49,7 @@
     "method-override": "^3.0.0",
     "mysql2": "^2.2.5",
     "read-pkg-up": "^7.0.1",
-    "typeorm": "^0.2.31",
+    "typeorm": "^0.3.20",
     "universal-bunyan": "^0.9.2"
   },
   "devDependencies": {

+ 1 - 1
apps/slackbot-proxy/src/controllers/growi-to-slack.ts

@@ -7,7 +7,7 @@ import {
   Controller, Get, Post, Inject, Req, Res, UseBefore, PathParams, Put, QueryParams,
 } from '@tsed/common';
 import axios from 'axios';
-import { addHours } from 'date-fns';
+import { addHours } from 'date-fns/addHours';
 import createError from 'http-errors';
 
 

+ 1 - 1
apps/slackbot-proxy/src/entities/relation.ts

@@ -1,4 +1,4 @@
-import { differenceInMilliseconds } from 'date-fns';
+import { differenceInMilliseconds } from 'date-fns/differenceInMilliseconds';
 import {
   Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, ManyToOne, Index,
 } from 'typeorm';

+ 1 - 1
apps/slackbot-proxy/src/services/RelationsService.ts

@@ -3,7 +3,7 @@ import { getSupportedGrowiActionsRegExp } from '@growi/slack/dist/utils/get-supp
 import { permissionParser } from '@growi/slack/dist/utils/permission-parser';
 import { Inject, Service } from '@tsed/di';
 import axios from 'axios';
-import { addHours } from 'date-fns';
+import { addHours } from 'date-fns/addHours';
 
 import { Relation, PermissionSettingsInterface } from '~/entities/relation';
 import { RelationRepository } from '~/repositories/relation';

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 1 - 0
packages/core/src/interfaces/lang.ts

@@ -2,6 +2,7 @@ export const Lang = {
   en_US: 'en_US',
   ja_JP: 'ja_JP',
   zh_CN: 'zh_CN',
+  fr_FR: 'fr_FR',
 } as const;
 export const AllLang = Object.values(Lang);
 export type Lang = typeof Lang[keyof typeof Lang];

+ 1 - 1
packages/editor/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/editor",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "license": "MIT",
   "type": "module",
   "module": "dist/index.js",

+ 11 - 6
packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx

@@ -1,5 +1,6 @@
 import {
   forwardRef, useMemo, useRef, useEffect,
+  DetailedHTMLProps,
 } from 'react';
 
 import { indentUnit } from '@codemirror/language';
@@ -23,11 +24,14 @@ import { Toolbar } from './Toolbar';
 
 import style from './CodeMirrorEditor.module.scss';
 
-const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
-  return (
-    <div {...props} className={`flex-expand-vert ${style['codemirror-editor-container']}`} ref={ref} />
-  );
-});
+const CodeMirrorEditorContainer = forwardRef<HTMLDivElement, DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>>(
+  (props, ref) => {
+    const { className = '', ...rest } = props;
+    return (
+      <div className={`${className} flex-expand-vert ${style['codemirror-editor-container']}`} ref={ref} {...rest} />
+    );
+  },
+);
 
 export type CodeMirrorEditorProps = {
   acceptedUploadFileType?: AcceptedUploadFileType,
@@ -48,6 +52,7 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   const {
     editorKey,
     hideToolbar,
+
     acceptedUploadFileType = AcceptedUploadFileType.NONE,
     indentSize,
     editorSettings,
@@ -205,7 +210,7 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   }, [isUploading, isDragAccept, isDragReject, acceptedUploadFileType]);
 
   return (
-    <div className={`${style['codemirror-editor']} flex-expand-vert overflow-y-hidden rounded`}>
+    <div className={`${style['codemirror-editor']} flex-expand-vert overflow-y-hidden`}>
       <div {...getRootProps()} className={`dropzone ${fileUploadState} flex-expand-vert`}>
         <input {...getInputProps()} />
         <FileDropzoneOverlay isEnabled={isDragActive} />

+ 1 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.tsx

@@ -23,7 +23,7 @@ export const Toolbar = memo((props: Props): JSX.Element => {
 
   const { editorKey, acceptedUploadFileType, onUpload } = props;
   return (
-    <div className={`d-flex gap-2 py-1 px-2 px-md-3 border-top codemirror-editor-toolbar ${styles['codemirror-editor-toolbar']}`}>
+    <div className={`d-flex gap-2 py-1 px-2 px-md-3 border-top ${styles['codemirror-editor-toolbar']}`}>
       <AttachmentsDropup editorKey={editorKey} onUpload={onUpload} acceptedUploadFileType={acceptedUploadFileType} />
       <TextFormatTools editorKey={editorKey} />
       <EmojiButton

+ 15 - 13
packages/editor/src/components/CodeMirrorEditorComment.tsx

@@ -1,27 +1,29 @@
-import { useEffect } from 'react';
+import { memo, useEffect } from 'react';
 
 import type { Extension } from '@codemirror/state';
-import { keymap, scrollPastEnd } from '@codemirror/view';
+import { keymap } from '@codemirror/view';
 
-import { GlobalCodeMirrorEditorKey } from '../consts';
 import { useCodeMirrorEditorIsolated } from '../stores';
 
-import { CodeMirrorEditor, CodeMirrorEditorProps } from '.';
+import { CodeMirrorEditor, type CodeMirrorEditorProps } from '.';
+
+import type { GlobalCodeMirrorEditorKey } from 'src/consts';
 
 
 const additionalExtensions: Extension[] = [
-  scrollPastEnd(),
 ];
 
+type Props = CodeMirrorEditorProps & {
+  editorKey: string | GlobalCodeMirrorEditorKey,
+}
 
-type Props = CodeMirrorEditorProps & object
-
-export const CodeMirrorEditorComment = (props: Props): JSX.Element => {
+export const CodeMirrorEditorComment = memo((props: Props): JSX.Element => {
   const {
-    onSave, ...otherProps
+    editorKey,
+    onSave, ...rest
   } = props;
 
-  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.COMMENT);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
 
   // setup additional extensions
   useEffect(() => {
@@ -55,9 +57,9 @@ export const CodeMirrorEditorComment = (props: Props): JSX.Element => {
 
   return (
     <CodeMirrorEditor
-      editorKey={GlobalCodeMirrorEditorKey.COMMENT}
+      editorKey={editorKey}
       onSave={onSave}
-      {...otherProps}
+      {...rest}
     />
   );
-};
+});

+ 1 - 1
packages/editor/src/components/CodeMirrorEditorMain.tsx

@@ -8,7 +8,7 @@ import { GlobalCodeMirrorEditorKey } from '../consts';
 import { setDataLine } from '../services/extensions/setDataLine';
 import { useCodeMirrorEditorIsolated, useCollaborativeEditorMode } from '../stores';
 
-import { CodeMirrorEditor, CodeMirrorEditorProps } from '.';
+import { CodeMirrorEditor, type CodeMirrorEditorProps } from '.';
 
 const additionalExtensions: Extension[] = [
   [

+ 1 - 1
packages/editor/src/consts/global-code-mirror-editor-key.ts

@@ -1,6 +1,6 @@
 export const GlobalCodeMirrorEditorKey = {
   MAIN: 'main',
-  COMMENT: 'comment',
+  COMMENT_NEW: 'comment_new',
   DIFF: 'diff',
   READONLY: 'readonly',
 } as const;

+ 1 - 1
packages/presentation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/presentation",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "GROWI plugin for presentation",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/preset-templates/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/preset-templates",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "scripts": {
     "test": "vitest run",
     "version": "yarn version --no-git-tag-version --preid=RC"

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "license": "MIT",
   "main": "dist/libs/preset-themes.umd.js",
   "module": "dist/libs/preset-themes.mjs",

+ 1 - 1
packages/remark-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-attachment-refs",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-drawio/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-drawio",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "remark plugin to draw diagrams with draw.io (diagrams.net)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-growi-directive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-growi-directive",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "remark plugin to support GROWI plugin (forked from remark-directive@2.0.1)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-lsx",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [

+ 2 - 2
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "license": "MIT",
   "type": "module",
   "main": "dist/index.cjs",
@@ -57,7 +57,7 @@
     "browser-bunyan": "^1.6.3",
     "bunyan": "^1.8.15",
     "crypto": "^1.0.1",
-    "date-fns": "^2.23.0",
+    "date-fns": "^3.6.0",
     "extensible-custom-error": "^0.0.7",
     "http-errors": "^2.0.0",
     "qs": "^6.10.2",

+ 1 - 1
packages/slack/src/utils/generate-last-update-markdown.ts

@@ -1,4 +1,4 @@
-import { formatDistanceStrict } from 'date-fns';
+import { formatDistanceStrict } from 'date-fns/formatDistanceStrict';
 
 export function generateLastUpdateMrkdwn(updatedAt: string | Date | number, baseDate: Date): string {
   if (updatedAt != null) {

+ 2 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "7.0.2-RC.0",
+  "version": "7.0.3-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [
@@ -40,6 +40,7 @@
     "@growi/core": "link:../core"
   },
   "devDependencies": {
+    "date-fns": "^3.6.0",
     "reactstrap": "^9.2.0"
   },
   "peerDependencies": {

+ 1 - 1
packages/ui/src/components/Attachment.tsx

@@ -1,5 +1,5 @@
 import type { IAttachmentHasId } from '@growi/core';
-import { format } from 'date-fns';
+import { format } from 'date-fns/format';
 
 import { UserPicture } from './UserPicture';
 

+ 81 - 130
yarn.lock

@@ -1176,20 +1176,20 @@
     core-js-pure "^3.20.2"
     regenerator-runtime "^0.13.4"
 
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.22.15", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
-  version "7.23.6"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
-  integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==
-  dependencies:
-    regenerator-runtime "^0.14.0"
-
-"@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.22.15", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
   version "7.24.0"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e"
   integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==
   dependencies:
     regenerator-runtime "^0.14.0"
 
+"@babel/runtime@^7.21.0":
+  version "7.24.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
+  integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
+  dependencies:
+    regenerator-runtime "^0.14.0"
+
 "@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.3.3":
   version "7.23.9"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a"
@@ -1856,7 +1856,7 @@
     xdg-basedir "^4.0.0"
 
 "@growi/core@link:packages/core":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     bson-objectid "^2.0.4"
     escape-string-regexp "^4.0.0"
@@ -1865,7 +1865,7 @@
   version "7.0.0-RC.0"
 
 "@growi/editor@link:packages/editor":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     markdown-table "^3.0.3"
     react "^18.2.0"
@@ -1878,18 +1878,18 @@
     extensible-custom-error "^0.0.7"
 
 "@growi/presentation@link:packages/presentation":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
 
 "@growi/preset-templates@link:packages/preset-templates":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
 
 "@growi/preset-themes@link:packages/preset-themes":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
 
 "@growi/remark-attachment-refs@link:packages/remark-attachment-refs":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -1902,10 +1902,10 @@
     universal-bunyan "^0.9.2"
 
 "@growi/remark-drawio@link:packages/remark-drawio":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
 
 "@growi/remark-growi-directive@link:packages/remark-growi-directive":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     "@types/mdast" "^3.0.0"
     "@types/unist" "^2.0.0"
@@ -1922,7 +1922,7 @@
     uvu "^0.5.0"
 
 "@growi/remark-lsx@link:packages/remark-lsx":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -1934,7 +1934,7 @@
     swr "^2.2.2"
 
 "@growi/slack@link:packages/slack":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     "@slack/oauth" "^2.0.1"
     "@slack/web-api" "^6.2.4"
@@ -1945,7 +1945,7 @@
     browser-bunyan "^1.6.3"
     bunyan "^1.8.15"
     crypto "^1.0.1"
-    date-fns "^2.23.0"
+    date-fns "^3.6.0"
     extensible-custom-error "^0.0.7"
     http-errors "^2.0.0"
     qs "^6.10.2"
@@ -1953,7 +1953,7 @@
     url-join "^4.0.0"
 
 "@growi/ui@link:packages/ui":
-  version "7.0.2-RC.0"
+  version "7.0.3-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
 
@@ -3485,10 +3485,10 @@
   resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
   integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
 
-"@sqltools/formatter@^1.2.2":
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20"
-  integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==
+"@sqltools/formatter@^1.2.5":
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
+  integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
 
 "@swc-node/core@^1.10.1":
   version "1.10.1"
@@ -4411,11 +4411,6 @@
   dependencies:
     "@types/node" "*"
 
-"@types/zen-observable@^0.8.2":
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71"
-  integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg==
-
 "@typescript-eslint/eslint-plugin@^5.59.7":
   version "5.59.7"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz#e470af414f05ecfdc05a23e9ce6ec8f91db56fe2"
@@ -4851,10 +4846,10 @@ anymatch@^3.0.3, anymatch@~3.1.2:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-app-root-path@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad"
-  integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==
+app-root-path@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86"
+  integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==
 
 append-field@^1.0.0:
   version "1.0.0"
@@ -5826,12 +5821,7 @@ can-use-dom@^0.1.0:
   resolved "https://registry.yarnpkg.com/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a"
   integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo=
 
-caniuse-lite@^1.0.30000865, caniuse-lite@^1.0.30001580:
-  version "1.0.30001582"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001582.tgz#db3070547ce0b48d9f44a509b86c4a02ba5d9055"
-  integrity sha512-vsJG3V5vgfduaQGVxL53uSX/HUzxyr2eA8xCo36OLal7sRcSZbibJtLeh0qja4sFOr/QQGt4opB4tOy+eOgAxg==
-
-caniuse-lite@^1.0.30001579:
+caniuse-lite@^1.0.30000865, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001580:
   version "1.0.30001599"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce"
   integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==
@@ -5883,7 +5873,7 @@ chalk@4.1.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1:
+chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
   integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -5891,7 +5881,7 @@ chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@^1.1.1, chalk@^1.1.3:
+chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
   dependencies:
@@ -6089,7 +6079,7 @@ cli-cursor@^3.1.0:
   dependencies:
     restore-cursor "^3.1.0"
 
-cli-highlight@^2.1.10:
+cli-highlight@^2.1.11:
   version "2.1.11"
   resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf"
   integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==
@@ -7154,9 +7144,16 @@ date-and-time@^2.0.0:
   integrity sha512-O7Xe5dLaqvY/aF/MFWArsAM1J4j7w1CSZlPCX9uHgmb+6SbkPd8Q4YOvfvH/cZGvFlJFfHOZKxQtmMUOoZhc/w==
 
 date-fns@^2.23.0, date-fns@^2.24.0:
-  version "2.28.0"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
-  integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
+  version "2.30.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
+  integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
+  dependencies:
+    "@babel/runtime" "^7.21.0"
+
+date-fns@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf"
+  integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
 
 date-format@^2.0.0:
   version "2.1.0"
@@ -7175,10 +7172,10 @@ date-time@^3.1.0:
   dependencies:
     time-zone "^1.0.0"
 
-dayjs@^1.10.4, dayjs@^1.11.7:
-  version "1.11.7"
-  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
-  integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
+dayjs@^1.10.4, dayjs@^1.11.7, dayjs@^1.11.9:
+  version "1.11.10"
+  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
+  integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
 
 debounce@^1.2.1:
   version "1.2.1"
@@ -7582,7 +7579,12 @@ dotenv-flow@^3.2.0:
   dependencies:
     dotenv "^8.0.0"
 
-dotenv@>=8.2.0, dotenv@^8.0.0, dotenv@^8.2.0:
+dotenv@>=8.2.0, dotenv@^16.0.3:
+  version "16.4.5"
+  resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
+  integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
+
+dotenv@^8.0.0:
   version "8.6.0"
   resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
   integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
@@ -8621,11 +8623,6 @@ fflate@^0.7.4:
   resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
   integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
 
-figlet@^1.1.1:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.5.0.tgz#2db4d00a584e5155a96080632db919213c3e003c"
-  integrity sha512-ZQJM4aifMpz6H19AW1VqvZ7l4pOE9p7i/3LyxgO2kp+PO/VcDYNqIHEMtkccqIhTXMKci4kjueJr/iCQEaT/Ww==
-
 figures@^3.0.0, figures@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -9680,7 +9677,7 @@ helmet@^4.6.0:
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
   integrity sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==
 
-highlight.js@11.9.0:
+highlight.js@11.9.0, highlight.js@^11.7.0:
   version "11.9.0"
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
   integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
@@ -9690,11 +9687,6 @@ highlight.js@^10.4.1, highlight.js@^10.7.1, highlight.js@~10.7.0:
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
   integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
 
-highlight.js@^11.7.0:
-  version "11.7.0"
-  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
-  integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==
-
 hogan.js@3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
@@ -9738,12 +9730,7 @@ hpagent@^1.0.0:
   resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-1.2.0.tgz#0ae417895430eb3770c03443456b8d90ca464903"
   integrity sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==
 
-html-escaper@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491"
-  integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==
-
-html-escaper@^2.0.2:
+html-escaper@^2.0.0, html-escaper@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
   integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
@@ -11042,7 +11029,7 @@ js-yaml@^3.13.1:
     argparse "^1.0.7"
     esprima "^4.0.0"
 
-js-yaml@^4.0.0, js-yaml@^4.1.0:
+js-yaml@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
   integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
@@ -12670,24 +12657,12 @@ migrate-mongo@^8.2.3:
     mongodb "^4.0.1"
     p-each-series "^2.2.0"
 
-mime-db@1.51.0, "mime-db@>= 1.43.0 < 2":
-  version "1.51.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
-  integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
-
-mime-db@1.52.0:
+mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
   version "1.52.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
 
-mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19, mime-types@~2.1.24:
-  version "2.1.34"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
-  integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
-  dependencies:
-    mime-db "1.51.0"
-
-mime-types@~2.1.34:
+mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
   version "2.1.35"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
   integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -12853,7 +12828,7 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
-mkdirp@^2.1.6:
+mkdirp@^2.1.3, mkdirp@^2.1.6:
   version "2.1.6"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19"
   integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
@@ -13870,11 +13845,6 @@ parent-module@^1.0.0:
   dependencies:
     callsites "^3.0.0"
 
-parent-require@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977"
-  integrity sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=
-
 parse-entities@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
@@ -15047,6 +15017,11 @@ reflect-metadata@^0.1.13:
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
   integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
 
+reflect-metadata@^0.2.1:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b"
+  integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==
+
 refractor@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a"
@@ -17527,28 +17502,26 @@ typedarray@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
-typeorm@^0.2.31:
-  version "0.2.32"
-  resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.32.tgz#544dbfdfe0cd0887548d9bcbd28527ea4f4b3c9b"
-  integrity sha512-LOBZKZ9As3f8KRMPCUT2H0JZbZfWfkcUnO3w/1BFAbL/X9+cADTF6bczDGGaKVENJ3P8SaKheKmBgpt5h1x+EQ==
+typeorm@^0.3.20:
+  version "0.3.20"
+  resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.20.tgz#4b61d737c6fed4e9f63006f88d58a5e54816b7ab"
+  integrity sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==
   dependencies:
-    "@sqltools/formatter" "^1.2.2"
-    app-root-path "^3.0.0"
+    "@sqltools/formatter" "^1.2.5"
+    app-root-path "^3.1.0"
     buffer "^6.0.3"
-    chalk "^4.1.0"
-    cli-highlight "^2.1.10"
-    debug "^4.3.1"
-    dotenv "^8.2.0"
-    glob "^7.1.6"
-    js-yaml "^4.0.0"
-    mkdirp "^1.0.4"
-    reflect-metadata "^0.1.13"
+    chalk "^4.1.2"
+    cli-highlight "^2.1.11"
+    dayjs "^1.11.9"
+    debug "^4.3.4"
+    dotenv "^16.0.3"
+    glob "^10.3.10"
+    mkdirp "^2.1.3"
+    reflect-metadata "^0.2.1"
     sha.js "^2.4.11"
-    tslib "^2.1.0"
-    xml2js "^0.4.23"
-    yargonaut "^1.1.4"
-    yargs "^16.2.0"
-    zen-observable-ts "^1.0.0"
+    tslib "^2.5.0"
+    uuid "^9.0.0"
+    yargs "^17.6.2"
 
 typescript@~5.0.0, typescript@~5.0.4:
   version "5.0.4"
@@ -18531,15 +18504,6 @@ yaml@^2.0.0:
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
   integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
 
-yargonaut@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.4.tgz#c64f56432c7465271221f53f5cc517890c3d6e0c"
-  integrity sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==
-  dependencies:
-    chalk "^1.1.1"
-    figlet "^1.1.1"
-    parent-require "^1.0.0"
-
 yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9:
   version "20.2.9"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
@@ -18576,7 +18540,7 @@ yargs@^16.0.0, yargs@^16.2.0:
     y18n "^5.0.5"
     yargs-parser "^20.2.2"
 
-yargs@^17.0.1, yargs@^17.3.1, yargs@^17.7.1, yargs@~17.7.1:
+yargs@^17.0.1, yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.1, yargs@~17.7.1:
   version "17.7.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
   integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
@@ -18641,19 +18605,6 @@ z-schema@~5.0.2:
   optionalDependencies:
     commander "^9.4.1"
 
-zen-observable-ts@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.0.0.tgz#30d1202b81d8ba4c489e3781e8ca09abf0075e70"
-  integrity sha512-KmWcbz+9kKUeAQ8btY8m1SsEFgBcp7h/Uf3V5quhan7ZWdjGsf0JcGLULQiwOZibbFWnHkYq8Nn2AZbJabovQg==
-  dependencies:
-    "@types/zen-observable" "^0.8.2"
-    zen-observable "^0.8.15"
-
-zen-observable@^0.8.15:
-  version "0.8.15"
-  resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
-  integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
-
 zip-stream@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79"

Некоторые файлы не были показаны из-за большого количества измененных файлов