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

160341-172821-useTranslationとCustoNavTabの使用

mariko-h 3 месяцев назад
Родитель
Сommit
24c375a0a4

+ 28 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -1072,5 +1072,33 @@
     "success-toaster": "Latest text synchronized",
     "skipped-toaster": "Skipped synchronizing since the editor is not activated. Please open the editor and try again.",
     "error-toaster": "Synchronization of the latest text failed"
+  },
+
+  "editor_guide": {
+    "title": "Editor Guide",
+    "tabs": {
+      "textstyle": "Text Style",
+      "layout": "Layout",
+      "decoration": "Decoration"
+    },
+    "textstyle": {
+      "copy_done": "Copied!",
+      "this": "This is",
+      "is": "",
+      "bold": "bold",
+      "italic": "italic",
+      "strikethrough": "strikethrough",
+      "inline_code": "inline code",
+      "bold_italic": "Bold or italic all",
+      "emoji": "Emoji",
+      "sub_sup": "Subscript / Superscript",
+      "link_label": "Link with label",
+      "link_docs": "GROWI Documentation",
+      "link_growi": "GROWI Link",
+      "link_sandbox": "Sandbox page is here",
+      "all_important": "This text is all\nimportant",
+      "sub_text": "subscript",
+      "sup_text": "superscript"
+    }
   }
 }

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

@@ -1063,5 +1063,33 @@
     "success-toaster": "Dernier texte synchronisé",
     "skipped-toaster": "L'éditeur n'est pas actif. Synchronisation annulée.",
     "error-toaster": "Synchronisation échouée"
+  },
+
+  "editor_guide": {
+    "title": "Guide de l'Éditeur",
+    "tabs": {
+      "textstyle": "Style de Texte",
+      "layout": "Mise en Page",
+      "decoration": "Décoration"
+    },
+    "textstyle": {
+      "copy_done": "Copié !",
+      "this": "Ceci est du",
+      "is": "",
+      "bold": "gras",
+      "italic": "italique",
+      "strikethrough": "barré",
+      "inline_code": "code en ligne",
+      "bold_italic": "Tout en gras ou italique",
+      "emoji": "Émoticône",
+      "sub_sup": "Indice / Exposant",
+      "link_label": "Lien avec étiquette",
+      "link_docs": "Documentation GROWI",
+      "link_growi": "Lien GROWI",
+      "link_sandbox": "La page bac à sable est ici",
+      "all_important": "Tout ce texte est\nimportant",
+      "sub_text": "indice",
+      "sup_text": "exposant"
+    }
   }
 }

+ 28 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -1105,5 +1105,33 @@
     "success-toaster": "最新の本文を同期しました",
     "skipped-toaster": "エディターがアクティブではないため、同期をスキップしました。エディターを開いて再度お試しください。",
     "error-toaster": "最新の本文の同期に失敗しました"
+  },
+
+  "editor_guide": {
+    "title": "エディターガイド",
+    "tabs": {
+      "textstyle": "テキストスタイル",
+      "layout": "レイアウト",
+      "decoration": "装飾"
+    },
+    "textstyle": {
+      "copy_done": "コピーしました!",
+      "this": "これは",
+      "is": "です",
+      "bold": "太字",
+      "italic": "斜体",
+      "strikethrough": "取り消し線",
+      "inline_code": "インラインコード",
+      "bold_italic": "全体が太字か斜体",
+      "emoji": "絵文字",
+      "sub_sup": "下付き・上付き",
+      "link_label": "ラベル付きリンク",
+      "link_docs": "GROWI ドキュメント",
+      "link_growi": "GROWIのリンク",
+      "link_sandbox": "砂場ページはこちら",
+      "all_important": "このテキストはすべて\n重要です",
+      "sub_text": "下付き",
+      "sup_text": "上付き"
+    }
   }
 }

+ 27 - 0
apps/app/public/static/locales/ko_KR/translation.json

@@ -1032,5 +1032,32 @@
     "success-toaster": "최신 텍스트 동기화됨",
     "skipped-toaster": "편집기가 활성화되지 않아 동기화 건너뜀. 편집기를 열고 다시 시도하십시오.",
     "error-toaster": "최신 텍스트 동기화 실패"
+  },
+  "editor_guide": {
+    "title": "에디터 가이드",
+    "tabs": {
+      "textstyle": "텍스트 스타일",
+      "layout": "레이아웃",
+      "decoration": "데코레이션"
+    },
+    "textstyle": {
+      "copy_done": "복사되었습니다!",
+      "this": "이것은",
+      "is": "입니다",
+      "bold": "굵게",
+      "italic": "기울임꼴",
+      "strikethrough": "취소선",
+      "inline_code": "인라인 코드",
+      "bold_italic": "전체가 굵게 또는 기울임꼴",
+      "emoji": "이모지",
+      "sub_sup": "아래 첨자 / 위 첨자",
+      "link_label": "라벨이 있는 링크",
+      "link_docs": "GROWI 문서",
+      "link_growi": "GROWI 링크",
+      "link_sandbox": "연습장 페이지는 여기입니다",
+      "all_important": "이 텍스트는 모두\n중요합니다",
+      "sub_text": "아래 첨자",
+      "sup_text": "위 첨자"
+    }
   }
 }

+ 28 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -1077,5 +1077,33 @@
     "success-toaster": "同步最新文本",
     "skipped-toaster": "由于编辑器未激活,因此跳过同步。 请打开编辑器并重试。",
     "error-toaster": "同步最新文本失败"
+  },
+
+  "editor_guide": {
+    "title": "编辑器指南",
+    "tabs": {
+      "textstyle": "文本样式",
+      "layout": "页面布局",
+      "decoration": "装饰"
+    },
+    "textstyle": {
+      "copy_done": "已复制!",
+      "this": "这是",
+      "is": "效果",
+      "bold": "加粗",
+      "italic": "斜体",
+      "strikethrough": "删除线",
+      "inline_code": "行内代码",
+      "bold_italic": "全体加粗或斜体",
+      "emoji": "表情符号",
+      "sub_sup": "下标 / 上标",
+      "link_label": "带有标签的链接",
+      "link_docs": "GROWI 文档",
+      "link_growi": "GROWI 链接",
+      "link_sandbox": "沙盒页面在这里",
+      "all_important": "这段文字非常\n重要",
+      "sub_text": "下标",
+      "sup_text": "上标"
+    }
   }
 }

+ 34 - 20
apps/app/src/client/components/PageEditor/EditorGuideModal/EditorGuideModal.tsx

@@ -1,10 +1,14 @@
 import {
-  useState, useEffect, useLayoutEffect, type JSX, type RefObject,
+  useState, useEffect, useLayoutEffect, type JSX, type RefObject, useMemo,
 } from 'react';
 
+
 import { useEditorGuideModalStatus, useEditorGuideModalActions } from '@growi/editor/dist/states/modal/editor-guide';
 import { createPortal } from 'react-dom';
 
+import { CustomNavTab } from '../../CustomNavigation/CustomNav';
+import CustomTabContent from '../../CustomNavigation/CustomTabContent';
+
 import { DecorationTab } from './contents/DecorationTab';
 import { LayoutTab } from './contents/LayoutTab';
 import { TextStyleTab } from './contents/TextStyleTab';
@@ -28,6 +32,23 @@ export const EditorGuideModal = ({ containerRef }: Props): JSX.Element => {
 
   const [activeTab, setActiveTab] = useState<TabType>('textstyle');
 
+  const navTabMapping = useMemo(() => {
+    return {
+      textstyle: {
+        i18n: 'テキストスタイル',
+        Content: () => <TextStyleTab />,
+      },
+      layout: {
+        i18n: 'レイアウト',
+        Content: () => <LayoutTab />,
+      },
+      decoration: {
+        i18n: '装飾',
+        Content: () => <DecorationTab />,
+      },
+    };
+  }, []);
+
   // Get rect on open and on resize
   useLayoutEffect(() => {
     if (!isOpened || containerRef.current == null) return;
@@ -64,26 +85,19 @@ export const EditorGuideModal = ({ containerRef }: Props): JSX.Element => {
               <h5 className="mb-0">Editor Guide</h5>
               <button type="button" className="btn-close" onClick={close} aria-label="Close" />
             </div>
-            <ul className="nav nav-tabs nav-fill border-bottom-0 mt-2">
-              {(['textstyle', 'layout', 'decoration'] as TabType[]).map(tab => (
-                <li key={tab} className="nav-item">
-                  <button
-                    type="button"
-                    className={`nav-link border-0 border-bottom border-3 py-2 ${
-                      activeTab === tab ? 'active border-primary fw-bold' : 'border-transparent text-secondary'}`}
-                    onClick={() => setActiveTab(tab)}
-                  >
-                    {tab === 'textstyle' && 'テキストスタイル'}
-                    {tab === 'layout' && 'レイアウト'}
-                    {tab === 'decoration' && '装飾'}
-                  </button>
-                </li>
-              ))}
-            </ul>
+            <div className="mt-2 px-3">
+              <CustomNavTab
+                activeTab={activeTab}
+                navTabMapping={navTabMapping}
+                onNavSelected={tab => setActiveTab(tab as TabType)}
+                hideBorderBottom
+              />
+            </div>
             <div className="card-body overflow-auto">
-              {activeTab === 'textstyle' && <TextStyleTab />}
-              {activeTab === 'layout' && <LayoutTab />}
-              {activeTab === 'decoration' && <DecorationTab />}
+              <CustomTabContent
+                activeTab={activeTab}
+                navTabMapping={navTabMapping}
+              />
             </div>
           </div>
         </div>

+ 127 - 114
apps/app/src/client/components/PageEditor/EditorGuideModal/contents/TextStyleTab.tsx

@@ -1,5 +1,8 @@
 import React from 'react';
 
+import { useTranslation } from 'react-i18next';
+
+
 export const ExternalLinkIcon = () => {
   return (
     <svg
@@ -36,17 +39,16 @@ const GuideRow = ({
   code: string;
   preview: React.ReactNode;
 }) => {
+  const { t } = useTranslation();
   const handleCopy = async () => {
     await navigator.clipboard.writeText(code);
-    alert('コピーしました!');
+    alert(t('editor_guide.textstyle.copy_done'));
   };
 
   return (
     <section className={title !== '' ? 'mt-4 mb-1' : 'mb-1'}>
       {title !== '' && <h3 className="h6 fw-bold mb-2">{title}</h3>}
-      {/* flex-nowrap を追加して、全体が縦に並ばないように強制します */}
       <div className="d-flex flex-row align-items-center gap-3 py-1 flex-nowrap">
-        {/* flex-shrink-0 を追加して、コード枠が右側のテキストに押しつぶされないようにします */}
         <div onClick={handleCopy} style={{ cursor: 'pointer' }} className="flex-shrink-0">
           <div
             className="bg-dark text-light p-2 ps-2 pe-4 rounded position-relative"
@@ -63,8 +65,6 @@ const GuideRow = ({
             </small>
           </div>
         </div>
-
-        {/* whiteSpace: 'nowrap' を追加して、右側のプレビューが勝手に改行されるのを防ぎます */}
         <div className="flex-grow-1" style={{ whiteSpace: 'nowrap' }}>
           <div className="wiki-content small">
             {preview}
@@ -75,117 +75,130 @@ const GuideRow = ({
   );
 };
 
-const TEXT_STYLE_GUIDES = [
-  {
-    id: 'bold',
-    title: '太字',
-    code: 'これは **太字** です\nこれは __太字__ です',
-    preview: <div className="lh-base">これは <strong>太字</strong> です<br />これは <strong>太字</strong> です</div>,
-  },
-  {
-    id: 'italic',
-    title: '斜体',
-    code: 'これは *斜体* です\nこれは _斜体_ です',
-    preview: <div className="lh-base">これは <em>斜体</em> です<br />これは <em>斜体</em> です</div>,
-  },
-  {
-    id: 'strikethrough',
-    title: '取り消し線',
-    code: '~~取り消します~~',
-    preview: <del>取り消します</del>,
-  },
-  {
-    id: 'inline-code',
-    title: 'インラインコード',
-    code: '`インラインコード` \n~~~インラインコード~~~',
-    preview: (
-      <div className="d-flex flex-column gap-2">
-        <code
-          className="rounded px-1"
-          style={{
-            width: 'fit-content',
-            color: '#D63384', // 文字の色
-            border: '1px solid #D63384', // 枠線の色(太さ1px、実線、指定の色)
-            backgroundColor: 'transparent', // 背景を透明にする場合(必要に応じて)
-          }}
-        >
-          インラインコード
-        </code>
-        <code
-          className="rounded px-1"
-          style={{
-            width: 'fit-content',
-            color: '#D63384', // 文字の色
-            border: '1px solid #D63384', // 枠線の色
-            backgroundColor: 'transparent',
-          }}
-        >
-          インラインコード
-        </code>
-      </div>
-    ),
-  },
-  {
-    id: 'bold-italic',
-    title: '全体が太字か斜体',
-    code: '***このテキストはすべて\n重要です***',
-    preview: <strong><u>このテキストはすべて重要です</u></strong>,
-  },
-  {
-    id: 'emoji',
-    title: '絵文字',
-    code: ':+1:\n:white_check_mark:\n:lock:',
-    preview: <span style={{ fontSize: '1.2rem' }}>👍✅🔒</span>,
-  },
-  {
-    id: 'sub',
-    title: '下付き・上付き',
-    code: 'これは<sub>下付き</sub>です',
-    preview: <span>これは<sub>下付き</sub>テキストです</span>,
-  },
-  {
-    id: 'sup',
-    title: '',
-    code: 'これは<sup>上付き</sup>です',
-    preview: <span>これは<sup>上付き</sup>テキストです</span>,
-  },
-  {
-    id: 'link-docs',
-    title: 'ラベル付きリンク',
-    code: '[GROWI ドキュメント](https://docs.growi.org/ja/g)',
-    preview: (
-      <a
-        href="https://docs.growi.org/ja/g"
-        target="_blank"
-        rel="noreferrer"
-        className="text-secondary text-decoration-underline"
-        style={{ color: '#777570' }}
-        onClick={e => e.stopPropagation()}
-      >
-        GROWI のリンク
-        <ExternalLinkIcon />
-      </a>
-    ),
-  },
-  {
-    id: 'link-sandbox',
-    title: '',
-    code: '[砂場ページはこちら](/Sandbox)',
-    preview: (
-      <a
-        href="/Sandbox"
-        className="text-secondary text-decoration-underline"
-        style={{ color: '#777570' }}
-        onClick={e => e.stopPropagation()}
-      >
-        砂場ページはこちら
-        <ExternalLinkIcon />
-      </a>
-    ),
-  },
-];
 
 export const TextStyleTab: React.FC = () => {
+  const { t } = useTranslation();
+  const ts = 'editor_guide.textstyle';
+
+  const TEXT_STYLE_GUIDES = [
+    {
+      id: 'bold',
+      title: t(`${ts}.bold`),
+      code: `${t(`${ts}.is_text`, { val: `**${t(`${ts}.bold`)}**` })}\n${t(`${ts}.is_text`, { val: `__${t(`${ts}.bold`)}__` })}`,
+      preview: (
+        <div className="lh-base">
+          {t(`${ts}.this`)} <strong>{t(`${ts}.bold`)}</strong> {t(`${ts}.is`)}<br />
+          {t(`${ts}.this`)} <strong>{t(`${ts}.bold`)}</strong> {t(`${ts}.is`)}
+        </div>
+      ),
+    },
+    {
+      id: 'italic',
+      title: t(`${ts}.italic`),
+      code: `${t(`${ts}.this`)} *${t(`${ts}.italic`)}* ${t(`${ts}.is`)}\n${t(`${ts}.this`)} _${t(`${ts}.italic`)}_ ${t(`${ts}.is`)}`,
+      preview: (
+        <div className="lh-base">
+          {t(`${ts}.this`)} <em>{t(`${ts}.italic`)}</em> {t(`${ts}.is`)}<br />
+          {t(`${ts}.this`)} <em>{t(`${ts}.italic`)}</em> {t(`${ts}.is`)}
+        </div>
+      ),
+    },
+    {
+      id: 'strikethrough',
+      title: t(`${ts}.strikethrough`),
+      code: `~~${t(`${ts}.strikethrough`)}~~`,
+      preview: <del>{t(`${ts}.strikethrough`)}</del>,
+    },
+    {
+      id: 'inline-code',
+      title: t(`${ts}.inline_code`),
+      code: `\`${t(`${ts}.inline_code`)}\` \n~~~${t(`${ts}.inline_code`)}~~~`,
+      preview: (
+        <div className="d-flex flex-column gap-2">
+          <code
+            className="rounded px-1"
+            style={{
+              width: 'fit-content',
+              color: '#D63384', // 文字の色
+              border: '1px solid #D63384', // 枠線の色(太さ1px、実線、指定の色)
+              backgroundColor: 'transparent', // 背景を透明にする場合(必要に応じて)
+            }}
+          >
+            {t(`${ts}.inline_code`)}
+          </code>
+          <code
+            className="rounded px-1"
+            style={{
+              width: 'fit-content',
+              color: '#D63384', // 文字の色
+              border: '1px solid #D63384', // 枠線の色
+              backgroundColor: 'transparent',
+            }}
+          >
+            {t(`${ts}.inline_code`)}
+          </code>
+        </div>
+      ),
+    },
+    {
+      id: 'bold-italic',
+      title: t(`${ts}.bold_italic`),
+      code: `***${t(`${ts}.all_important`)}***`,
+      preview: <strong><u>{t(`${ts}.all_important`).replace('\n', '')}</u></strong>,
+    },
+    {
+      id: 'emoji',
+      title: t(`${ts}.emoji`),
+      code: ':+1:\n:white_check_mark:\n:lock:',
+      preview: <span style={{ fontSize: '1.2rem' }}>👍✅🔒</span>,
+    },
+    {
+      id: 'sub',
+      title: t(`${ts}.sub_sup`),
+      code: t(`${ts}.is_text`, { val: `<sub>${t(`${ts}.sub_text`)}</sub>` }),
+      preview: <span>{t(`${ts}.this`)} <sub>{t(`${ts}.sub_text`)}</sub> {t(`${ts}.is`)}</span>,
+    },
+    {
+      id: 'sup',
+      title: '',
+      code: t(`${ts}.is_text`, { val: `<sup>${t(`${ts}.sup_text`)}</sup>` }),
+      preview: <span>{t(`${ts}.this`)} <sup>{t(`${ts}.sup_text`)}</sup> {t(`${ts}.is`)}</span>,
+    },
+    {
+      id: 'link-docs',
+      title: t(`${ts}.link_label`),
+      code: `[${t(`${ts}.link_docs`)}](https://docs.growi.org/ja/g)`,
+      preview: (
+        <a
+          href="https://docs.growi.org/ja/g"
+          target="_blank"
+          rel="noreferrer"
+          className="text-secondary text-decoration-underline"
+          style={{ color: '#777570' }}
+          onClick={e => e.stopPropagation()}
+        >
+          {t(`${ts}.link_growi`)}
+          <ExternalLinkIcon />
+        </a>
+      ),
+    },
+    {
+      id: 'link-sandbox',
+      title: '',
+      code: `[${t(`${ts}.link_sandbox`)}](/Sandbox)`,
+      preview: (
+        <a
+          href="/Sandbox"
+          className="text-secondary text-decoration-underline"
+          style={{ color: '#777570' }}
+          onClick={e => e.stopPropagation()}
+        >
+          {t(`${ts}.link_sandbox`)}
+          <ExternalLinkIcon />
+        </a>
+      ),
+    },
+  ];
   return (
     <div className="px-4 py-2 overflow-y-auto" style={{ maxHeight: '80vh' }}>
       {TEXT_STYLE_GUIDES.map(item => (