PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / doc-categories / components / TranslationsPanel.js

TranslationsPanel.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/doc-categories/components/TranslationsPanel.js

135 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useEffect, useState, useRef } from "react";
2 import { __ } from "@wordpress/i18n";
3 import { decodeEntities } from "@wordpress/html-entities";
4 import { getTermTranslations } from "../utils/api";
5 import SlideoverSelect from "./SlideoverSelect";
6
7 const GlobeIcon = () => (
8 <svg width="13" height="13" viewBox="0 0 13 13" fill="none" aria-hidden="true">
9 <circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" strokeWidth="1.2" />
10 <ellipse cx="6.5" cy="6.5" rx="2.5" ry="5.5" stroke="currentColor" strokeWidth="1.2" />
11 <path d="M1 6.5H12" stroke="currentColor" strokeWidth="1.2" />
12 <path d="M1.5 4H11.5M1.5 9H11.5" stroke="currentColor" strokeWidth="1" strokeDasharray="2 1" />
13 </svg>
14 );
15
16 /**
17 * WPML/Polylang translation panel for the slide-over: Language select +
18 * "This is a translation of" (for non-default languages) + a "Translate" list of
19 * the other languages with their existing translation or an "add" link.
20 *
21 * The parent owns `lang` + `translationOf` (sent to the server on save); this
22 * component fetches the group/candidates and renders the controls.
23 */
24 const TranslationsPanel = ({
25 taxonomy,
26 editNode,
27 lang,
28 setLang,
29 translationOf,
30 setTranslationOf,
31 onAddTranslation,
32 }) => {
33 const languages = window.betterdocs_admin?.languages || [];
34 const [data, setData] = useState({
35 default_lang: window.betterdocs_admin?.current_admin_language || "",
36 translations: {},
37 candidates: [],
38 translation_of: 0,
39 });
40 // Track which term/lang we've already seeded translationOf for, so a user's
41 // dropdown change isn't overwritten by a refetch.
42 const seededRef = useRef(null);
43
44 useEffect(() => {
45 if (!languages.length) return;
46 const params = { taxonomy };
47 if (editNode?.id) params.term_id = editNode.id;
48 if (lang) params.lang = lang;
49
50 let active = true;
51 getTermTranslations(params)
52 .then((res) => {
53 if (!active || !res || res.enabled === false) return;
54 setData(res);
55 // Seed the dropdown from the server once per term load (don't clobber edits).
56 const seedKey = `${editNode?.id || "new"}:${lang}`;
57 if (seededRef.current !== seedKey) {
58 seededRef.current = seedKey;
59 if (translationOf == null && res.translation_of) {
60 setTranslationOf(res.translation_of);
61 }
62 }
63 })
64 .catch(() => {});
65 return () => { active = false; };
66 }, [taxonomy, editNode?.id, lang]);
67
68 if (!languages.length) return null;
69
70 const defaultLang = data.default_lang;
71 const translations = data.translations || {};
72 const candidates = data.candidates || [];
73 const showTranslationOf = lang && lang !== defaultLang;
74
75 return (
76 <div className="bd-dc-lang-panel">
77 <div className="bd-dc-lang-panel-title">
78 <GlobeIcon />
79 {__("Language", "betterdocs")}
80 </div>
81
82 <div className="bd-dc-field">
83 <SlideoverSelect
84 value={lang}
85 onChange={(v) => setLang(v)}
86 placeholder={__("— Select —", "betterdocs")}
87 options={languages.map((l) => ({ value: l.code, label: l.label }))}
88 />
89 </div>
90
91 {showTranslationOf && (
92 <div className="bd-dc-field">
93 <label className="bd-dc-label">{__("This is a translation of", "betterdocs")}</label>
94 <SlideoverSelect
95 value={translationOf || 0}
96 onChange={(v) => setTranslationOf(Number(v))}
97 options={[
98 { value: 0, label: __("— None —", "betterdocs") },
99 ...candidates.map((c) => ({ value: c.term_id, label: decodeEntities(c.name) })),
100 ]}
101 />
102 </div>
103 )}
104
105 {/* Translate: other languages — existing translation or an "add" link */}
106 <div className="bd-dc-translate">
107 <div className="bd-dc-translate-head">{__("Translate", "betterdocs")}</div>
108 {languages
109 .filter((l) => l.code !== lang)
110 .map((l) => {
111 const existing = translations[l.code];
112 return (
113 <div key={l.code} className="bd-dc-translate-row">
114 <span className="bd-dc-translate-lang">{l.label}</span>
115 {existing ? (
116 <span className="bd-dc-translate-name">{decodeEntities(existing.name)}</span>
117 ) : (
118 <button
119 type="button"
120 className="bd-dc-translate-add"
121 onClick={() => onAddTranslation && onAddTranslation(l.code)}
122 >
123 {__("add", "betterdocs")}
124 </button>
125 )}
126 </div>
127 );
128 })}
129 </div>
130 </div>
131 );
132 };
133
134 export default TranslationsPanel;
135