@ssml-builder-js/ssml-editor-react 2.12.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/index.d.mts +65 -11
- package/dist/index.d.ts +65 -11
- package/dist/index.js +412 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +411 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/SsmlEditor.tsx +31 -1
- package/src/components/VisualSsmlEditor.tsx +265 -24
- package/src/index.tsx +9 -2
- package/src/locales.ts +226 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssml-builder-js/ssml-editor-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "React-based SSML editor component",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,6 +52,6 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@ssml-builder-js/ssml-core": "^2.
|
|
55
|
+
"@ssml-builder-js/ssml-core": "^2.14.0"
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/SsmlEditor.tsx
CHANGED
|
@@ -40,7 +40,12 @@ import { InsertionPopover } from "./components/popovers/InsertionPopover";
|
|
|
40
40
|
import { useSsmlEditorState } from "./hooks/useSsmlEditorState";
|
|
41
41
|
import { useSsmlMonaco } from "./hooks/useSsmlMonaco";
|
|
42
42
|
import { editorStyles as styles } from "./styles/editorStyles";
|
|
43
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
VisualSsmlEditor,
|
|
45
|
+
type VisualInspectorRenderer,
|
|
46
|
+
type VisualVoiceCatalogEntry,
|
|
47
|
+
type VoiceSelectorRenderProps,
|
|
48
|
+
} from "./components/VisualSsmlEditor";
|
|
44
49
|
const UNGROUPED_TOOLBAR_GROUP = "__ssml-editor-ungrouped__";
|
|
45
50
|
const TIMING_POPOVER_TAGS = new Set(["break", "mstts:silence", "mstts:audioduration"]);
|
|
46
51
|
const PROSODY_POPOVER_TAGS = new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
|
|
@@ -764,6 +769,18 @@ export interface SsmlEditorProps {
|
|
|
764
769
|
customInsertions?: SsmlEditorCustomInsertionCollection;
|
|
765
770
|
/** Adds insertion definitions without replacing built-in definitions. */
|
|
766
771
|
additionalInsertions?: SsmlEditorCustomInsertionCollection;
|
|
772
|
+
/** Replaces the visual inspector for an element type or serialized tag name. */
|
|
773
|
+
customInspectors?: Readonly<Record<string, VisualInspectorRenderer>>;
|
|
774
|
+
/** Replaces the built-in voice catalog selector in visual mode. */
|
|
775
|
+
renderVoiceSelector?: (props: VoiceSelectorRenderProps) => ReactNode;
|
|
776
|
+
/** Voice metadata used by the visual selector. */
|
|
777
|
+
voiceCatalog?: readonly VisualVoiceCatalogEntry[];
|
|
778
|
+
/** Optional locale filter for the visual voice selector. */
|
|
779
|
+
voiceLocale?: string;
|
|
780
|
+
/** Optional region filter for the visual voice selector. */
|
|
781
|
+
voiceRegion?: string;
|
|
782
|
+
/** Optional style filter for the visual voice selector. */
|
|
783
|
+
voiceStyle?: string;
|
|
767
784
|
/** Candidate style values shown by the built-in emotion insertion. */
|
|
768
785
|
emotionStyles?: readonly string[];
|
|
769
786
|
/** Class name applied to the editor container. */
|
|
@@ -865,6 +882,12 @@ export const SsmlEditor = forwardRef<SsmlEditorRef, SsmlEditorProps>(function Ss
|
|
|
865
882
|
insertionGroups,
|
|
866
883
|
customInsertions,
|
|
867
884
|
additionalInsertions,
|
|
885
|
+
customInspectors,
|
|
886
|
+
renderVoiceSelector,
|
|
887
|
+
voiceCatalog,
|
|
888
|
+
voiceLocale,
|
|
889
|
+
voiceRegion,
|
|
890
|
+
voiceStyle,
|
|
868
891
|
emotionStyles,
|
|
869
892
|
className,
|
|
870
893
|
style,
|
|
@@ -1347,6 +1370,13 @@ export const SsmlEditor = forwardRef<SsmlEditorRef, SsmlEditorProps>(function Ss
|
|
|
1347
1370
|
readOnly={isReadOnly}
|
|
1348
1371
|
onChange={commit}
|
|
1349
1372
|
onPreviewSelection={onPreviewSelection}
|
|
1373
|
+
locale={localeProp ?? languageProp}
|
|
1374
|
+
customInspectors={customInspectors}
|
|
1375
|
+
renderVoiceSelector={renderVoiceSelector}
|
|
1376
|
+
voiceCatalog={voiceCatalog}
|
|
1377
|
+
voiceLocale={voiceLocale}
|
|
1378
|
+
voiceRegion={voiceRegion}
|
|
1379
|
+
voiceStyle={voiceStyle}
|
|
1350
1380
|
/>
|
|
1351
1381
|
</div>
|
|
1352
1382
|
) : (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
|
-
import type { ReactElement } from "react";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
3
|
import {
|
|
4
4
|
buildSsml,
|
|
5
5
|
validateAzureSsml,
|
|
@@ -7,12 +7,52 @@ import {
|
|
|
7
7
|
type SsmlElement,
|
|
8
8
|
type SsmlNode,
|
|
9
9
|
} from "@ssml-builder-js/ssml-core";
|
|
10
|
+
import { SSML_ELEMENT_COPY, type SsmlEditorLocale } from "../locales";
|
|
11
|
+
|
|
12
|
+
export interface VisualVoiceCatalogEntry {
|
|
13
|
+
name: string;
|
|
14
|
+
locale: string;
|
|
15
|
+
region?: string;
|
|
16
|
+
regions?: readonly string[];
|
|
17
|
+
styles?: readonly string[];
|
|
18
|
+
supportedTags?: readonly string[];
|
|
19
|
+
unsupportedTags?: readonly string[];
|
|
20
|
+
preview?: boolean;
|
|
21
|
+
status?: "ga" | "preview" | "deprecated";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface VoiceSelectorRenderProps {
|
|
25
|
+
value: string;
|
|
26
|
+
voices: readonly VisualVoiceCatalogEntry[];
|
|
27
|
+
selectedVoice?: VisualVoiceCatalogEntry;
|
|
28
|
+
readOnly: boolean;
|
|
29
|
+
locale: SsmlEditorLocale;
|
|
30
|
+
onChange: (voice: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface VisualInspectorRenderProps {
|
|
34
|
+
document: SsmlDocument;
|
|
35
|
+
element: SsmlElement;
|
|
36
|
+
path: number[];
|
|
37
|
+
readOnly: boolean;
|
|
38
|
+
onChange: (document: SsmlDocument) => void;
|
|
39
|
+
locale: SsmlEditorLocale;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type VisualInspectorRenderer = (props: VisualInspectorRenderProps) => ReactNode;
|
|
10
43
|
|
|
11
44
|
export interface VisualSsmlEditorProps {
|
|
12
45
|
document: SsmlDocument;
|
|
13
46
|
readOnly?: boolean;
|
|
14
47
|
onChange?: (document: SsmlDocument) => void;
|
|
15
48
|
onPreviewSelection?: (ssml: string) => void;
|
|
49
|
+
locale?: SsmlEditorLocale;
|
|
50
|
+
customInspectors?: Readonly<Record<string, VisualInspectorRenderer>>;
|
|
51
|
+
renderVoiceSelector?: (props: VoiceSelectorRenderProps) => ReactNode;
|
|
52
|
+
voiceCatalog?: readonly VisualVoiceCatalogEntry[];
|
|
53
|
+
voiceLocale?: string;
|
|
54
|
+
voiceRegion?: string;
|
|
55
|
+
voiceStyle?: string;
|
|
16
56
|
}
|
|
17
57
|
|
|
18
58
|
interface TextLeaf {
|
|
@@ -192,20 +232,133 @@ function getElementFields(element: SsmlElement): readonly VisualElementField[] {
|
|
|
192
232
|
return VISUAL_ELEMENT_FIELDS[elementLabel(element)] ?? [];
|
|
193
233
|
}
|
|
194
234
|
|
|
235
|
+
function localizedElementLabel(element: SsmlElement, locale: SsmlEditorLocale): string {
|
|
236
|
+
return SSML_ELEMENT_COPY[locale][elementLabel(element)]?.label ?? elementLabel(element);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function filteredVoices(
|
|
240
|
+
voiceCatalog: readonly VisualVoiceCatalogEntry[] | undefined,
|
|
241
|
+
locale: string | undefined,
|
|
242
|
+
region: string | undefined,
|
|
243
|
+
style: string | undefined,
|
|
244
|
+
): readonly VisualVoiceCatalogEntry[] {
|
|
245
|
+
return (voiceCatalog ?? []).filter((voice) => {
|
|
246
|
+
if (locale && voice.locale.toLowerCase() !== locale.toLowerCase()) return false;
|
|
247
|
+
const regions = [voice.region, ...(voice.regions ?? [])].filter((candidate): candidate is string =>
|
|
248
|
+
Boolean(candidate),
|
|
249
|
+
);
|
|
250
|
+
if (region && regions.length > 0 && !regions.some((candidate) => candidate.toLowerCase() === region.toLowerCase()))
|
|
251
|
+
return false;
|
|
252
|
+
if (style && !voice.styles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase())) return false;
|
|
253
|
+
return true;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function VoiceCapabilityMatrix({
|
|
258
|
+
voice,
|
|
259
|
+
locale,
|
|
260
|
+
}: {
|
|
261
|
+
voice: VisualVoiceCatalogEntry;
|
|
262
|
+
locale: SsmlEditorLocale;
|
|
263
|
+
}): ReactElement {
|
|
264
|
+
const status = voice.status ?? (voice.preview ? "preview" : undefined);
|
|
265
|
+
return (
|
|
266
|
+
<div className="ssml-editor-voice-capabilities" data-voice-capabilities="">
|
|
267
|
+
<div>
|
|
268
|
+
{locale === "ja" ? "ステータス" : "Status"}: {status?.toUpperCase() ?? (locale === "ja" ? "GA" : "GA")}
|
|
269
|
+
{status === "preview" || status === "deprecated" ? (
|
|
270
|
+
<span role="status" aria-label={status} className="ssml-editor-voice-warning-badge">
|
|
271
|
+
{status === "preview" ? "⚠ Preview" : "⚠ Deprecated"}
|
|
272
|
+
</span>
|
|
273
|
+
) : null}
|
|
274
|
+
</div>
|
|
275
|
+
{(voice.regions?.length || voice.region) && (
|
|
276
|
+
<div>
|
|
277
|
+
{locale === "ja" ? "リージョン" : "Regions"}:{" "}
|
|
278
|
+
{[...(voice.regions ?? []), ...(voice.region ? [voice.region] : [])]
|
|
279
|
+
.filter((region, index, all) => all.indexOf(region) === index)
|
|
280
|
+
.join(", ")}
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
{voice.supportedTags && voice.supportedTags.length > 0 && (
|
|
284
|
+
<div>
|
|
285
|
+
{locale === "ja" ? "対応タグ" : "Supported tags"}: {voice.supportedTags.join(", ")}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
{voice.unsupportedTags && voice.unsupportedTags.length > 0 && (
|
|
289
|
+
<div className="ssml-editor-voice-unsupported">
|
|
290
|
+
{locale === "ja" ? "非対応タグ" : "Unsupported tags"}: {voice.unsupportedTags.join(", ")}
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function DefaultVoiceSelector({
|
|
298
|
+
value,
|
|
299
|
+
voices,
|
|
300
|
+
selectedVoice,
|
|
301
|
+
readOnly,
|
|
302
|
+
locale,
|
|
303
|
+
onChange,
|
|
304
|
+
}: VoiceSelectorRenderProps): ReactElement {
|
|
305
|
+
return (
|
|
306
|
+
<>
|
|
307
|
+
<select
|
|
308
|
+
aria-label={locale === "ja" ? "音声" : "Voice"}
|
|
309
|
+
value={value}
|
|
310
|
+
disabled={readOnly}
|
|
311
|
+
onChange={(event) => onChange(event.target.value)}
|
|
312
|
+
>
|
|
313
|
+
<option value="">{locale === "ja" ? "音声を選択" : "Select a voice"}</option>
|
|
314
|
+
{voices.map((voice) => (
|
|
315
|
+
<option key={voice.name} value={voice.name}>
|
|
316
|
+
{voice.name}
|
|
317
|
+
{voice.preview || voice.status === "preview" ? " (preview)" : ""}
|
|
318
|
+
</option>
|
|
319
|
+
))}
|
|
320
|
+
</select>
|
|
321
|
+
{selectedVoice ? <VoiceCapabilityMatrix voice={selectedVoice} locale={locale} /> : null}
|
|
322
|
+
</>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
195
326
|
function VisualElementInspector({
|
|
196
327
|
document,
|
|
197
328
|
element,
|
|
198
329
|
path,
|
|
199
330
|
readOnly,
|
|
200
331
|
commit,
|
|
332
|
+
locale,
|
|
333
|
+
customInspector,
|
|
334
|
+
renderVoiceSelector,
|
|
335
|
+
voiceCatalog,
|
|
336
|
+
voiceLocale,
|
|
337
|
+
voiceRegion,
|
|
338
|
+
voiceStyle,
|
|
201
339
|
}: {
|
|
202
340
|
document: SsmlDocument;
|
|
203
341
|
element: SsmlElement;
|
|
204
342
|
path: number[];
|
|
205
343
|
readOnly: boolean;
|
|
206
344
|
commit: (document: SsmlDocument) => void;
|
|
345
|
+
locale: SsmlEditorLocale;
|
|
346
|
+
customInspector?: VisualInspectorRenderer;
|
|
347
|
+
renderVoiceSelector?: (props: VoiceSelectorRenderProps) => ReactNode;
|
|
348
|
+
voiceCatalog?: readonly VisualVoiceCatalogEntry[];
|
|
349
|
+
voiceLocale?: string;
|
|
350
|
+
voiceRegion?: string;
|
|
351
|
+
voiceStyle?: string;
|
|
207
352
|
}): ReactElement {
|
|
353
|
+
if (customInspector) {
|
|
354
|
+
return <>{customInspector({ document, element, path, readOnly, onChange: commit, locale })}</>;
|
|
355
|
+
}
|
|
208
356
|
const fields = getElementFields(element);
|
|
357
|
+
const availableVoices = filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle);
|
|
358
|
+
const selectedVoice = voiceCatalog?.find(
|
|
359
|
+
(voice) => voice.name === (element.type === "voice" ? element.name : undefined),
|
|
360
|
+
);
|
|
361
|
+
const renderSelector = renderVoiceSelector ?? DefaultVoiceSelector;
|
|
209
362
|
return (
|
|
210
363
|
<fieldset>
|
|
211
364
|
<legend>{`<${elementLabel(element)}>`}</legend>
|
|
@@ -218,8 +371,54 @@ function VisualElementInspector({
|
|
|
218
371
|
const inputId = `ssml-visual-${field.key}`;
|
|
219
372
|
return (
|
|
220
373
|
<label key={field.key} htmlFor={inputId}>
|
|
221
|
-
{
|
|
222
|
-
|
|
374
|
+
{locale === "ja"
|
|
375
|
+
? ((
|
|
376
|
+
{
|
|
377
|
+
name: "名前",
|
|
378
|
+
effect: "効果",
|
|
379
|
+
rate: "速度",
|
|
380
|
+
pitch: "ピッチ",
|
|
381
|
+
volume: "音量",
|
|
382
|
+
contour: "ピッチ曲線",
|
|
383
|
+
range: "範囲",
|
|
384
|
+
style: "スタイル",
|
|
385
|
+
styleDegree: "スタイル強度",
|
|
386
|
+
role: "役割",
|
|
387
|
+
interpretAs: "解釈",
|
|
388
|
+
alphabet: "アルファベット",
|
|
389
|
+
ph: "発音",
|
|
390
|
+
level: "強調レベル",
|
|
391
|
+
src: "音声 URL",
|
|
392
|
+
desc: "説明",
|
|
393
|
+
clipBegin: "開始位置",
|
|
394
|
+
clipEnd: "終了位置",
|
|
395
|
+
speed: "速度",
|
|
396
|
+
repeatCount: "繰り返し回数",
|
|
397
|
+
repeatDuration: "繰り返し時間",
|
|
398
|
+
soundLevel: "音量レベル",
|
|
399
|
+
mark: "ブックマーク名",
|
|
400
|
+
alias: "別名",
|
|
401
|
+
lang: "言語",
|
|
402
|
+
uri: "辞書 URI",
|
|
403
|
+
typeValue: "無音の種類",
|
|
404
|
+
value: "値",
|
|
405
|
+
id: "埋め込み ID",
|
|
406
|
+
speakerProfileId: "話者プロファイル ID",
|
|
407
|
+
url: "音声変換 URL",
|
|
408
|
+
profile: "プロファイル",
|
|
409
|
+
} as Record<string, string>
|
|
410
|
+
)[field.key] ?? field.label)
|
|
411
|
+
: field.label}
|
|
412
|
+
{field.key === "name" && elementLabel(element) === "voice" && voiceCatalog ? (
|
|
413
|
+
renderSelector({
|
|
414
|
+
value: inputValue,
|
|
415
|
+
voices: availableVoices,
|
|
416
|
+
selectedVoice,
|
|
417
|
+
readOnly,
|
|
418
|
+
locale,
|
|
419
|
+
onChange: (value) => commit(updateOptionalElementProperty(document, path, field.key, value)),
|
|
420
|
+
})
|
|
421
|
+
) : field.multiline ? (
|
|
223
422
|
<textarea
|
|
224
423
|
id={inputId}
|
|
225
424
|
value={inputValue}
|
|
@@ -346,12 +545,23 @@ export function VisualSsmlEditor({
|
|
|
346
545
|
readOnly = false,
|
|
347
546
|
onChange,
|
|
348
547
|
onPreviewSelection,
|
|
548
|
+
locale = "en",
|
|
549
|
+
customInspectors,
|
|
550
|
+
renderVoiceSelector,
|
|
551
|
+
voiceCatalog,
|
|
552
|
+
voiceLocale,
|
|
553
|
+
voiceRegion,
|
|
554
|
+
voiceStyle,
|
|
349
555
|
}: VisualSsmlEditorProps): ReactElement {
|
|
350
556
|
const [selectedPath, setSelectedPath] = useState<number[] | null>(null);
|
|
351
557
|
const [selection, setSelection] = useState({ start: 0, end: 0 });
|
|
352
558
|
const textLeaves = useMemo(() => collectTextLeaves(document.children ?? []), [document]);
|
|
353
559
|
const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
|
|
354
560
|
const selectedElement = selectedPath ? getNodeAtPath(document.children ?? [], selectedPath) : undefined;
|
|
561
|
+
const selectedCustomInspector =
|
|
562
|
+
selectedElement && isElement(selectedElement)
|
|
563
|
+
? (customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type])
|
|
564
|
+
: undefined;
|
|
355
565
|
const diagnostics = useMemo(() => validateAzureSsml(buildSsml(document)), [document]);
|
|
356
566
|
const commit = (nextDocument: SsmlDocument): void => onChange?.(nextDocument);
|
|
357
567
|
|
|
@@ -399,33 +609,55 @@ export function VisualSsmlEditor({
|
|
|
399
609
|
</ul>
|
|
400
610
|
</nav>
|
|
401
611
|
<div className="ssml-editor-visual-form">
|
|
402
|
-
{selectedElement && isElement(selectedElement)
|
|
612
|
+
{selectedCustomInspector && selectedElement && isElement(selectedElement) ? (
|
|
613
|
+
selectedCustomInspector({
|
|
614
|
+
document,
|
|
615
|
+
element: selectedElement,
|
|
616
|
+
path: selectedPath ?? [],
|
|
617
|
+
readOnly,
|
|
618
|
+
onChange: commit,
|
|
619
|
+
locale,
|
|
620
|
+
})
|
|
621
|
+
) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:dialog" ? (
|
|
403
622
|
<fieldset>
|
|
404
|
-
<legend>
|
|
405
|
-
<p>
|
|
623
|
+
<legend>{localizedElementLabel(selectedElement, locale)}</legend>
|
|
624
|
+
<p>{SSML_ELEMENT_COPY[locale][elementLabel(selectedElement)]?.description}</p>
|
|
406
625
|
<button
|
|
407
626
|
type="button"
|
|
408
627
|
disabled={readOnly}
|
|
409
628
|
onClick={() => commit(addDialogTurn(document, selectedPath ?? []))}
|
|
410
629
|
>
|
|
411
|
-
Add turn
|
|
630
|
+
{locale === "ja" ? "ターンを追加" : "Add turn"}
|
|
412
631
|
</button>
|
|
413
632
|
</fieldset>
|
|
414
633
|
) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? (
|
|
415
634
|
<fieldset>
|
|
416
|
-
<legend>
|
|
417
|
-
<label>
|
|
418
|
-
Voice
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
635
|
+
<legend>{localizedElementLabel(selectedElement, locale)}</legend>
|
|
636
|
+
<label htmlFor="ssml-visual-turn-voice">
|
|
637
|
+
{locale === "ja" ? "音声" : "Voice"}
|
|
638
|
+
{voiceCatalog ? (
|
|
639
|
+
(renderVoiceSelector ?? DefaultVoiceSelector)({
|
|
640
|
+
value: selectedElement.voice ?? "",
|
|
641
|
+
voices: filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle),
|
|
642
|
+
selectedVoice: voiceCatalog?.find((voice) => voice.name === selectedElement.voice),
|
|
643
|
+
readOnly,
|
|
644
|
+
locale,
|
|
645
|
+
onChange: (value) =>
|
|
646
|
+
commit(updateOptionalElementProperty(document, selectedPath ?? [], "voice", value)),
|
|
647
|
+
})
|
|
648
|
+
) : (
|
|
649
|
+
<input
|
|
650
|
+
id="ssml-visual-turn-voice"
|
|
651
|
+
value={selectedElement.voice ?? ""}
|
|
652
|
+
readOnly={readOnly}
|
|
653
|
+
onChange={(event) =>
|
|
654
|
+
commit(updateOptionalElementProperty(document, selectedPath ?? [], "voice", event.target.value))
|
|
655
|
+
}
|
|
656
|
+
/>
|
|
657
|
+
)}
|
|
426
658
|
</label>
|
|
427
659
|
<label>
|
|
428
|
-
Speaker
|
|
660
|
+
{locale === "ja" ? "話者" : "Speaker"}
|
|
429
661
|
<input
|
|
430
662
|
value={selectedElement.speaker ?? ""}
|
|
431
663
|
readOnly={readOnly}
|
|
@@ -435,7 +667,7 @@ export function VisualSsmlEditor({
|
|
|
435
667
|
/>
|
|
436
668
|
</label>
|
|
437
669
|
<label>
|
|
438
|
-
Turn text
|
|
670
|
+
{locale === "ja" ? "発話テキスト" : "Turn text"}
|
|
439
671
|
<textarea
|
|
440
672
|
value={
|
|
441
673
|
selectedElement.children?.filter((child): child is string => typeof child === "string").join("") ??
|
|
@@ -454,9 +686,9 @@ export function VisualSsmlEditor({
|
|
|
454
686
|
</fieldset>
|
|
455
687
|
) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:backgroundaudio" ? (
|
|
456
688
|
<fieldset>
|
|
457
|
-
<legend>
|
|
689
|
+
<legend>{localizedElementLabel(selectedElement, locale)}</legend>
|
|
458
690
|
<label>
|
|
459
|
-
Source URL
|
|
691
|
+
{locale === "ja" ? "音声 URL" : "Source URL"}
|
|
460
692
|
<input
|
|
461
693
|
value={selectedElement.src ?? ""}
|
|
462
694
|
readOnly={readOnly}
|
|
@@ -466,7 +698,7 @@ export function VisualSsmlEditor({
|
|
|
466
698
|
/>
|
|
467
699
|
</label>
|
|
468
700
|
<label>
|
|
469
|
-
Volume
|
|
701
|
+
{locale === "ja" ? "音量" : "Volume"}
|
|
470
702
|
<input
|
|
471
703
|
value={selectedElement.volume === undefined ? "" : String(selectedElement.volume)}
|
|
472
704
|
readOnly={readOnly}
|
|
@@ -476,7 +708,7 @@ export function VisualSsmlEditor({
|
|
|
476
708
|
/>
|
|
477
709
|
</label>
|
|
478
710
|
<label>
|
|
479
|
-
Fade in
|
|
711
|
+
{locale === "ja" ? "フェードイン" : "Fade in"}
|
|
480
712
|
<input
|
|
481
713
|
value={selectedElement.fadeIn === undefined ? "" : String(selectedElement.fadeIn)}
|
|
482
714
|
readOnly={readOnly}
|
|
@@ -486,7 +718,7 @@ export function VisualSsmlEditor({
|
|
|
486
718
|
/>
|
|
487
719
|
</label>
|
|
488
720
|
<label>
|
|
489
|
-
Fade out
|
|
721
|
+
{locale === "ja" ? "フェードアウト" : "Fade out"}
|
|
490
722
|
<input
|
|
491
723
|
value={selectedElement.fadeOut === undefined ? "" : String(selectedElement.fadeOut)}
|
|
492
724
|
readOnly={readOnly}
|
|
@@ -503,6 +735,15 @@ export function VisualSsmlEditor({
|
|
|
503
735
|
path={selectedPath ?? []}
|
|
504
736
|
readOnly={readOnly}
|
|
505
737
|
commit={commit}
|
|
738
|
+
locale={locale}
|
|
739
|
+
customInspector={
|
|
740
|
+
customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type]
|
|
741
|
+
}
|
|
742
|
+
renderVoiceSelector={renderVoiceSelector}
|
|
743
|
+
voiceCatalog={voiceCatalog}
|
|
744
|
+
voiceLocale={voiceLocale}
|
|
745
|
+
voiceRegion={voiceRegion}
|
|
746
|
+
voiceStyle={voiceStyle}
|
|
506
747
|
/>
|
|
507
748
|
) : selectedLeaf ? (
|
|
508
749
|
<>
|
package/src/index.tsx
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
export { SsmlEditor } from "./SsmlEditor";
|
|
6
6
|
export { VisualSsmlEditor } from "./components/VisualSsmlEditor";
|
|
7
|
-
export type {
|
|
7
|
+
export type {
|
|
8
|
+
VisualInspectorRenderProps,
|
|
9
|
+
VisualInspectorRenderer,
|
|
10
|
+
VisualSsmlEditorProps,
|
|
11
|
+
VisualVoiceCatalogEntry,
|
|
12
|
+
VoiceSelectorRenderProps,
|
|
13
|
+
} from "./components/VisualSsmlEditor";
|
|
8
14
|
export type {
|
|
9
15
|
SsmlEditorButton,
|
|
10
16
|
SsmlEditorButtonVisibility,
|
|
@@ -32,7 +38,7 @@ export type {
|
|
|
32
38
|
SsmlInsertionOption,
|
|
33
39
|
SsmlInsertionTemplate,
|
|
34
40
|
} from "./SsmlEditor";
|
|
35
|
-
export { EDITOR_COPY, INLINE_BADGE_COPY, SSML_HOVER_COPY } from "./locales";
|
|
41
|
+
export { EDITOR_COPY, INLINE_BADGE_COPY, SSML_ELEMENT_COPY, SSML_HOVER_COPY } from "./locales";
|
|
36
42
|
export type {
|
|
37
43
|
EditorCopy,
|
|
38
44
|
InlineBadgeCopy,
|
|
@@ -42,6 +48,7 @@ export type {
|
|
|
42
48
|
SsmlHoverLocale,
|
|
43
49
|
SsmlHoverParameterCopy,
|
|
44
50
|
SsmlHoverTagCopy,
|
|
51
|
+
SsmlElementLocaleCopy,
|
|
45
52
|
} from "./locales";
|
|
46
53
|
export {
|
|
47
54
|
createSsmlEditorInsertionDefinition,
|