@inshapardaz/likhari-react 0.1.6-dev.58 → 0.1.6-dev.62
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/dist/EditorRoot.d.ts +2 -1
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +7 -4
- package/dist/components/LinkDialog.d.ts.map +1 -1
- package/dist/components/LinkDialog.js +5 -3
- package/dist/components/Toolbar.d.ts +4 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +26 -24
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/strings.d.ts +158 -0
- package/dist/i18n/strings.d.ts.map +1 -0
- package/dist/i18n/strings.js +425 -0
- package/dist/i18n/useStrings.d.ts +10 -0
- package/dist/i18n/useStrings.d.ts.map +1 -0
- package/dist/i18n/useStrings.js +14 -0
- package/dist/image/ImageComponent.d.ts.map +1 -1
- package/dist/image/ImageComponent.js +5 -3
- package/dist/image/ImageCropDialog.d.ts.map +1 -1
- package/dist/image/ImageCropDialog.js +11 -6
- package/dist/image/ImageDialog.d.ts.map +1 -1
- package/dist/image/ImageDialog.js +28 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/dist/EditorRoot.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { SerializedEditorState } from 'lexical';
|
|
|
3
3
|
import { type FormatId } from '@inshapardaz/likhari-converters';
|
|
4
4
|
import { type EditorFeatureConfig, type FeatureConfigPresetName } from '@inshapardaz/likhari-core';
|
|
5
5
|
import { type FontOption } from './fonts/index.js';
|
|
6
|
+
import { type Locale } from './i18n/index.js';
|
|
6
7
|
export interface EditorInitialContent {
|
|
7
8
|
format: Extract<FormatId, 'lexical-json' | 'plain-text'>;
|
|
8
9
|
value: string;
|
|
@@ -14,7 +15,7 @@ export interface EditorRootProps {
|
|
|
14
15
|
featurePreset?: FeatureConfigPresetName;
|
|
15
16
|
theme?: MantineThemeOverride;
|
|
16
17
|
colorScheme?: 'light' | 'dark';
|
|
17
|
-
locale?:
|
|
18
|
+
locale?: Locale;
|
|
18
19
|
placeholder?: string;
|
|
19
20
|
/**
|
|
20
21
|
* CSS height for the editor box (toolbar + canvas together) — accepts any
|
package/dist/EditorRoot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAU1D,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAU1D,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAGjE,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,YAAY,CAAC,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAClD;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAClD,iBAAiB,IAAI,OAAO,CAAC;IAC7B,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAQD,eAAO,MAAM,UAAU,uGAkJrB,CAAC"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -18,14 +18,17 @@ import { Toolbar } from './components/Toolbar.js';
|
|
|
18
18
|
import { injectUrduWebFontsCss } from './fonts/index.js';
|
|
19
19
|
import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
|
|
20
20
|
import { ImageOptionsContext } from './image/ImageOptionsContext.js';
|
|
21
|
+
import { UiStringsContext, getStrings } from './i18n/index.js';
|
|
21
22
|
function initialEditorStateJson(initialContent) {
|
|
22
23
|
if (!initialContent)
|
|
23
24
|
return undefined;
|
|
24
25
|
const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
|
|
25
26
|
return JSON.stringify(state);
|
|
26
27
|
}
|
|
27
|
-
export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialContent, featureConfig, featurePreset, theme, colorScheme, locale = 'en', placeholder
|
|
28
|
+
export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialContent, featureConfig, featurePreset, theme, colorScheme, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, }, ref) {
|
|
28
29
|
const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
|
|
30
|
+
const strings = useMemo(() => getStrings(locale), [locale]);
|
|
31
|
+
const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
|
|
29
32
|
// The urdu-web-fonts stylesheets are needed for the font dropdown and for
|
|
30
33
|
// RTL content, whose canvas font (editor.css) is one of those families.
|
|
31
34
|
useEffect(() => {
|
|
@@ -78,12 +81,12 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
78
81
|
return true;
|
|
79
82
|
if (typeof window === 'undefined')
|
|
80
83
|
return true;
|
|
81
|
-
return window.confirm(
|
|
84
|
+
return window.confirm(strings.editor.confirmDiscard);
|
|
82
85
|
},
|
|
83
86
|
focus() {
|
|
84
87
|
rootElementRef.current?.focus();
|
|
85
88
|
},
|
|
86
|
-
}), [isDirty]);
|
|
89
|
+
}), [isDirty, strings]);
|
|
87
90
|
const rootElementRef = useRef(null);
|
|
88
91
|
const handleChange = (state) => {
|
|
89
92
|
editorStateRef.current = state;
|
|
@@ -102,5 +105,5 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
102
105
|
onSave?.(content, format);
|
|
103
106
|
};
|
|
104
107
|
const dir = locale === 'en' ? 'ltr' : 'rtl';
|
|
105
|
-
return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, children: _jsx("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: _jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, onSave: handleSave, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir }), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label":
|
|
108
|
+
return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, children: _jsx("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: _jsx(UiStringsContext.Provider, { value: strings, children: _jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, onSave: handleSave, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale }), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange })] }) }) }) }) }));
|
|
106
109
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LinkDialog.d.ts","sourceRoot":"","sources":["../../src/components/LinkDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LinkDialog.d.ts","sourceRoot":"","sources":["../../src/components/LinkDialog.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxD,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;gDACgD;AAChD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,eAAe,+BAiE7G"}
|
|
@@ -2,9 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Button, Group, Modal, TextInput } from '@mantine/core';
|
|
4
4
|
import { normalizeLinkUrl } from '../utils/linkUrl.js';
|
|
5
|
+
import { useUiStrings } from '../i18n/useStrings.js';
|
|
5
6
|
/** Insert/edit-link dialog (UI spec §6). Mantine Modal supplies focus
|
|
6
7
|
* trapping, Escape-to-close and the overlay. */
|
|
7
8
|
export function LinkDialog({ opened, initialUrl, showTextField, onSubmit, onRemove, onClose }) {
|
|
9
|
+
const strings = useUiStrings();
|
|
8
10
|
const [url, setUrl] = useState(initialUrl);
|
|
9
11
|
const [text, setText] = useState('');
|
|
10
12
|
const [error, setError] = useState(null);
|
|
@@ -19,13 +21,13 @@ export function LinkDialog({ opened, initialUrl, showTextField, onSubmit, onRemo
|
|
|
19
21
|
e.preventDefault();
|
|
20
22
|
const normalized = normalizeLinkUrl(url);
|
|
21
23
|
if (!normalized) {
|
|
22
|
-
setError(
|
|
24
|
+
setError(strings.linkDialog.invalidUrl);
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
27
|
onSubmit({ url: normalized, text: text.trim() || normalized });
|
|
26
28
|
};
|
|
27
|
-
return (_jsx(Modal, { opened: opened, onClose: onClose, title: onRemove ?
|
|
29
|
+
return (_jsx(Modal, { opened: opened, onClose: onClose, title: onRemove ? strings.linkDialog.titleEdit : strings.linkDialog.titleInsert, centered: true, size: "sm", children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TextInput, { label: strings.linkDialog.urlLabel, placeholder: strings.linkDialog.urlPlaceholder, value: url, onChange: (e) => {
|
|
28
30
|
setUrl(e.currentTarget.value);
|
|
29
31
|
setError(null);
|
|
30
|
-
}, error: error, "data-autofocus": true }), showTextField && (_jsx(TextInput, { mt: "sm", label:
|
|
32
|
+
}, error: error, "data-autofocus": true }), showTextField && (_jsx(TextInput, { mt: "sm", label: strings.linkDialog.textLabel, placeholder: strings.linkDialog.textPlaceholder, value: text, onChange: (e) => setText(e.currentTarget.value) })), _jsxs(Group, { justify: "space-between", mt: "md", children: [onRemove ? (_jsx(Button, { variant: "subtle", color: "red", onClick: onRemove, children: strings.link.removeLink })) : (_jsx("span", {})), _jsxs(Group, { gap: "xs", children: [_jsx(Button, { variant: "default", onClick: onClose, children: strings.common.cancel }), _jsx(Button, { type: "submit", children: strings.linkDialog.apply })] })] })] }) }));
|
|
31
33
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
|
|
2
2
|
import { type FontOption } from '../fonts/index.js';
|
|
3
|
+
import type { Locale } from '../i18n/strings.js';
|
|
3
4
|
export interface ToolbarProps {
|
|
4
5
|
config: ResolvedEditorFeatureConfig;
|
|
5
6
|
onSave?: () => void;
|
|
@@ -9,6 +10,8 @@ export interface ToolbarProps {
|
|
|
9
10
|
fontOptions?: FontOption[];
|
|
10
11
|
/** Text direction of the canvas; picks which default font and size are pre-selected. */
|
|
11
12
|
direction?: 'ltr' | 'rtl';
|
|
13
|
+
/** UI locale — picks the translated strings for labels, tooltips and menu items. */
|
|
14
|
+
locale?: Locale;
|
|
12
15
|
}
|
|
13
|
-
export declare function Toolbar({ config, onSave, isDirty, showSave, fontOptions, direction }: ToolbarProps): import("react").JSX.Element;
|
|
16
|
+
export declare function Toolbar({ config, onSave, isDirty, showSave, fontOptions, direction, locale }: ToolbarProps): import("react").JSX.Element;
|
|
14
17
|
//# sourceMappingURL=Toolbar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AAoPvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,EAAE,YAAY,+BAqoBhJ"}
|
|
@@ -15,6 +15,7 @@ import { LinkDialog } from './LinkDialog.js';
|
|
|
15
15
|
import { normalizeLinkUrl } from '../utils/linkUrl.js';
|
|
16
16
|
import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fonts/index.js';
|
|
17
17
|
import { setStyleProperty } from '../utils/style.js';
|
|
18
|
+
import { useStrings } from '../i18n/useStrings.js';
|
|
18
19
|
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconExternalLink, IconFeather, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconPencil, IconPhoto, IconPilcrow, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
19
20
|
/** Sentinel for "no inline style": Lexical returns the default it is given when
|
|
20
21
|
* the selection has none, so this tells "unstyled" (show the canvas default)
|
|
@@ -49,15 +50,15 @@ function ToolbarButton({ icon: Icon, title, active, dirty, disabled, onClick, })
|
|
|
49
50
|
* isn't implemented yet (link/image/poetry/font/language-tooling — see
|
|
50
51
|
* docs/lexical-editor-spec.md §13's phasing). Renders so the toolbar's
|
|
51
52
|
* layout is final now and only needs its onClick wired up later. */
|
|
52
|
-
function StubButton({ icon, title }) {
|
|
53
|
-
return _jsx(ToolbarButton, { icon: icon, title:
|
|
53
|
+
function StubButton({ icon, title, comingSoon }) {
|
|
54
|
+
return _jsx(ToolbarButton, { icon: icon, title: comingSoon(title), disabled: true, onClick: undefined });
|
|
54
55
|
}
|
|
55
56
|
/** Mantine Select (combobox) for the toolbar's dropdown controls. The icon
|
|
56
57
|
* labels the control itself, since a dropdown can't show one per option in
|
|
57
58
|
* its closed state. The dropdown portals to <body>, so it is not clipped by
|
|
58
59
|
* the editor's `overflow: hidden` frame. */
|
|
59
|
-
function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeholder, searchable, renderOption, onChange }) {
|
|
60
|
-
return (_jsx(Select, { size: "xs", w: width, "aria-label": label, title: disabled ?
|
|
60
|
+
function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeholder, searchable, renderOption, onChange, comingSoon, noMatchMessage }) {
|
|
61
|
+
return (_jsx(Select, { size: "xs", w: width, "aria-label": label, title: disabled && comingSoon ? comingSoon(label) : label, data: data, value: value, placeholder: placeholder, disabled: disabled, searchable: searchable, renderOption: renderOption, nothingFoundMessage: searchable ? noMatchMessage : undefined, allowDeselect: false, leftSection: _jsx(Icon, { size: 15, stroke: ICON_STROKE }), comboboxProps: { withinPortal: true, position: 'bottom-start', middlewares: { flip: true, shift: true } }, classNames: { input: 'likhari-toolbar-select-input', dropdown: 'likhari-toolbar-select-dropdown' }, onChange: (v) => v && onChange?.(v) }));
|
|
61
62
|
}
|
|
62
63
|
function OverflowItem({ icon: Icon, label, active, onClick, }) {
|
|
63
64
|
return (_jsx(Menu.Item, { leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), "data-active": active ? 'true' : 'false', className: "likhari-overflow-item",
|
|
@@ -66,16 +67,17 @@ function OverflowItem({ icon: Icon, label, active, onClick, }) {
|
|
|
66
67
|
}
|
|
67
68
|
/** The link menu's contents — shared by the toolbar button and the right-click
|
|
68
69
|
* menu: the URL (opens in a new tab), Edit link, Remove link. */
|
|
69
|
-
function LinkMenuItems({ url, onEdit, onRemove }) {
|
|
70
|
+
function LinkMenuItems({ url, onEdit, onRemove, strings }) {
|
|
70
71
|
// Only offer the link as clickable if it's a URL the editor would have accepted.
|
|
71
72
|
const safeUrl = normalizeLinkUrl(url);
|
|
72
|
-
return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children:
|
|
73
|
+
return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.link.menuLabel }), safeUrl ? (_jsx(Menu.Item, { component: "a", href: safeUrl, target: "_blank", rel: "noopener noreferrer", leftSection: _jsx(IconExternalLink, { size: ICON_SIZE, stroke: ICON_STROKE }), title: url, className: "likhari-link-menu-url", children: url })) : (
|
|
73
74
|
// Not a URL the editor would accept (e.g. loaded from a document): show it, don't make it clickable.
|
|
74
|
-
_jsx(Menu.Item, { disabled: true, title: url, className: "likhari-link-menu-url", children: url ||
|
|
75
|
+
_jsx(Menu.Item, { disabled: true, title: url, className: "likhari-link-menu-url", children: url || strings.link.noUrl })), _jsx(Menu.Item, { leftSection: _jsx(IconPencil, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onEdit, children: strings.link.editLink }), _jsx(Menu.Item, { color: "red", leftSection: _jsx(IconLinkOff, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onRemove, children: strings.link.removeLink })] }));
|
|
75
76
|
}
|
|
76
|
-
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr' }) {
|
|
77
|
+
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en' }) {
|
|
77
78
|
const [editor] = useLexicalComposerContext();
|
|
78
79
|
const [state, setState] = useState(INITIAL_STATE);
|
|
80
|
+
const strings = useStrings(locale);
|
|
79
81
|
const updateToolbar = useCallback(() => {
|
|
80
82
|
editor.getEditorState().read(() => {
|
|
81
83
|
const selection = $getSelection();
|
|
@@ -397,33 +399,33 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
397
399
|
const shownFontFamily = state.fontFamily === UNSET_STYLE ? canvasDefaults.family : state.fontFamily;
|
|
398
400
|
const shownFontSize = state.fontSize === UNSET_STYLE ? canvasDefaults.size : state.fontSize;
|
|
399
401
|
const formattingOptions = [
|
|
400
|
-
{ value: 'paragraph', label:
|
|
401
|
-
...headingLevels.map((level) => ({ value: `h${level}`, label:
|
|
402
|
-
...(config.lists.numbered ? [{ value: 'number', label:
|
|
403
|
-
...(config.lists.bullet ? [{ value: 'bullet', label:
|
|
404
|
-
...(config.lists.check ? [{ value: 'check', label:
|
|
405
|
-
...(config.blocks.quote ? [{ value: 'quote', label:
|
|
402
|
+
{ value: 'paragraph', label: strings.toolbar.formattingOptions.paragraph },
|
|
403
|
+
...headingLevels.map((level) => ({ value: `h${level}`, label: strings.toolbar.formattingOptions.heading(level) })),
|
|
404
|
+
...(config.lists.numbered ? [{ value: 'number', label: strings.toolbar.formattingOptions.numberedList }] : []),
|
|
405
|
+
...(config.lists.bullet ? [{ value: 'bullet', label: strings.toolbar.formattingOptions.bulletList }] : []),
|
|
406
|
+
...(config.lists.check ? [{ value: 'check', label: strings.toolbar.formattingOptions.taskList }] : []),
|
|
407
|
+
...(config.blocks.quote ? [{ value: 'quote', label: strings.toolbar.formattingOptions.quote }] : []),
|
|
406
408
|
];
|
|
407
409
|
const alignOptions = [
|
|
408
|
-
...(config.alignment.start ? [{ value: 'start', label:
|
|
409
|
-
...(config.alignment.center ? [{ value: 'center', label:
|
|
410
|
-
...(config.alignment.start ? [{ value: 'end', label:
|
|
411
|
-
...(config.alignment.justify ? [{ value: 'justify', label:
|
|
412
|
-
...(config.alignment.left ? [{ value: 'left', label:
|
|
413
|
-
...(config.alignment.right ? [{ value: 'right', label:
|
|
410
|
+
...(config.alignment.start ? [{ value: 'start', label: strings.toolbar.alignOptions.start }] : []),
|
|
411
|
+
...(config.alignment.center ? [{ value: 'center', label: strings.toolbar.alignOptions.center }] : []),
|
|
412
|
+
...(config.alignment.start ? [{ value: 'end', label: strings.toolbar.alignOptions.end }] : []),
|
|
413
|
+
...(config.alignment.justify ? [{ value: 'justify', label: strings.toolbar.alignOptions.justify }] : []),
|
|
414
|
+
...(config.alignment.left ? [{ value: 'left', label: strings.toolbar.alignOptions.left }] : []),
|
|
415
|
+
...(config.alignment.right ? [{ value: 'right', label: strings.toolbar.alignOptions.right }] : []),
|
|
414
416
|
];
|
|
415
417
|
const formattingValue = state.listType ?? state.blockType;
|
|
416
418
|
const AlignIcon = ALIGN_ICONS[state.elementFormat] ?? IconAlignLeft;
|
|
417
|
-
return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label":
|
|
419
|
+
return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, children: [showSave && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty), onClick: onSave }) })), config.history && (_jsxs("div", { className: "likhari-toolbar-group", children: [_jsx(ToolbarButton, { icon: IconArrowBackUp, title: strings.toolbar.undo, disabled: !state.canUndo, onClick: () => editor.dispatchCommand(UNDO_COMMAND, undefined) }), _jsx(ToolbarButton, { icon: IconArrowForwardUp, title: strings.toolbar.redo, disabled: !state.canRedo, onClick: () => editor.dispatchCommand(REDO_COMMAND, undefined) })] })), showFormattingGroup && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: IconPilcrow, label: strings.toolbar.formattingLabel, width: 148, value: formattingValue, data: formattingOptions, comingSoon: strings.toolbar.comingSoon, onChange: withRefocus((v) => applyFormatting(v)) }) })), showInlineGroup && (_jsxs("div", { className: "likhari-toolbar-group", children: [fmt.bold && (_jsx(ToolbarButton, { icon: IconBold, title: strings.toolbar.bold, active: state.activeFormats.has('bold'), onClick: () => formatText('bold') })), fmt.italic && (_jsx(ToolbarButton, { icon: IconItalic, title: strings.toolbar.italic, active: state.activeFormats.has('italic'), onClick: () => formatText('italic') })), fmt.underline && (_jsx(ToolbarButton, { icon: IconUnderline, title: strings.toolbar.underline, active: state.activeFormats.has('underline'), onClick: () => formatText('underline') }))] })), (config.font.family || config.font.size) && (_jsxs("div", { className: "likhari-toolbar-group", children: [config.font.family && (_jsx(ToolbarSelect, { icon: IconTypography, label: strings.toolbar.fontFamily, width: 150, value: fontOptions.some((f) => f.family === shownFontFamily) ? shownFontFamily : null, placeholder: strings.toolbar.fontFamilyPlaceholder, data: fontData, searchable: true, noMatchMessage: strings.toolbar.noMatch, renderOption: ({ option }) => _jsx("span", { style: { fontFamily: option.value }, children: option.label }), onChange: withRefocus((v) => applyFont('font-family', v)) })), config.font.size && (_jsx(ToolbarSelect, { icon: IconTextSize, label: strings.toolbar.fontSize, width: 84, value: FONT_SIZES_PX.some((px) => `${px}px` === shownFontSize) ? shownFontSize : null, placeholder: strings.toolbar.fontSizePlaceholder, data: FONT_SIZES_PX.map((px) => ({ value: `${px}px`, label: String(px) })), onChange: withRefocus((v) => applyFont('font-size', v)) }))] })), showAlignGroup && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: AlignIcon, label: strings.toolbar.alignment, width: 138, value: state.elementFormat || 'start', data: alignOptions, onChange: withRefocus((v) => formatElement(v)) }) })), config.links && (_jsx("div", { className: "likhari-toolbar-group", children: state.isLink ? (
|
|
418
420
|
// Caret is in a link: the button opens a menu to see, edit or remove it.
|
|
419
|
-
_jsxs(Menu, { position: "bottom-start", withinPortal: true, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label":
|
|
421
|
+
_jsxs(Menu, { position: "bottom-start", withinPortal: true, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.linkOptions, title: strings.toolbar.linkOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconLink, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: state.linkUrl, onEdit: () => {
|
|
420
422
|
restoreSelection();
|
|
421
423
|
openLinkDialog();
|
|
422
|
-
}, onRemove: removeLink }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title:
|
|
424
|
+
}, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) })), config.links && (_jsxs(Menu, { opened: linkContext !== null, onChange: (opened) => {
|
|
423
425
|
if (!opened)
|
|
424
426
|
setLinkContext(null);
|
|
425
427
|
}, position: "bottom-start", withinPortal: true, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: linkContext?.x ?? -9999, top: linkContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: linkContext?.url ?? '', onEdit: () => {
|
|
426
428
|
restoreSelection();
|
|
427
429
|
openLinkDialog();
|
|
428
|
-
}, onRemove: removeLink }) })] })), showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title:
|
|
430
|
+
}, onRemove: removeLink, strings: strings }) })] })), showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] })), showLanguageGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [config.language.autocorrect && _jsx(StubButton, { icon: IconWand, title: strings.toolbar.autocorrect, comingSoon: strings.toolbar.comingSoon }), config.language.textCleanup && _jsx(StubButton, { icon: IconSparkles, title: strings.toolbar.textCleanup, comingSoon: strings.toolbar.comingSoon }), config.language.spellCheck && _jsx(StubButton, { icon: IconAbc, title: strings.toolbar.spellChecker, comingSoon: strings.toolbar.comingSoon })] })), showOverflowMenu && (_jsxs(Menu, { position: "bottom-end", withinPortal: true, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "aria-label": strings.toolbar.moreFormatting, title: strings.toolbar.moreFormatting, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconDots, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsxs(Menu.Dropdown, { children: [fmt.strikethrough && (_jsx(OverflowItem, { icon: IconStrikethrough, label: strings.toolbar.strikethrough, active: state.activeFormats.has('strikethrough'), onClick: runOverflowAction(() => formatText('strikethrough')) })), fmt.superscript && (_jsx(OverflowItem, { icon: IconSuperscript, label: strings.toolbar.superscript, active: state.activeFormats.has('superscript'), onClick: runOverflowAction(() => formatText('superscript')) })), fmt.subscript && (_jsx(OverflowItem, { icon: IconSubscript, label: strings.toolbar.subscript, active: state.activeFormats.has('subscript'), onClick: runOverflowAction(() => formatText('subscript')) })), fmt.caseTransforms && (_jsxs(_Fragment, { children: [_jsx(OverflowItem, { icon: IconLetterCaseUpper, label: strings.toolbar.uppercase, onClick: runOverflowAction(() => applyCaseTransform('upper')) }), _jsx(OverflowItem, { icon: IconLetterCaseLower, label: strings.toolbar.lowercase, onClick: runOverflowAction(() => applyCaseTransform('lower')) }), _jsx(OverflowItem, { icon: IconLetterCase, label: strings.toolbar.capitalize, onClick: runOverflowAction(() => applyCaseTransform('capitalize')) })] })), fmt.clearFormatting && _jsx(OverflowItem, { icon: IconClearFormatting, label: strings.toolbar.clearFormatting, onClick: runOverflowAction(clearFormatting) }), config.indent && (_jsxs(_Fragment, { children: [_jsx(OverflowItem, { icon: IconIndentDecrease, label: strings.toolbar.outdent, onClick: runOverflowAction(() => editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined)) }), _jsx(OverflowItem, { icon: IconIndentIncrease, label: strings.toolbar.indent, onClick: runOverflowAction(() => editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined)) })] }))] })] })), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog }))] }));
|
|
429
431
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI localization strings for the editor's own chrome (toolbar, dialogs,
|
|
3
|
+
* menus, placeholders) — see docs/lexical-editor-spec.md §3.2. This is
|
|
4
|
+
* separate from content direction/fonts (handled in EditorRoot) and from
|
|
5
|
+
* Markdown/HTML/plain-text format translation, which is out of scope here.
|
|
6
|
+
*/
|
|
7
|
+
/** Re-exported from EditorRoot's existing prop type so there is a single
|
|
8
|
+
* source of truth for which locales the editor knows about. */
|
|
9
|
+
export type Locale = 'en' | 'ur' | 'pa-shahmukhi';
|
|
10
|
+
export interface Strings {
|
|
11
|
+
editor: {
|
|
12
|
+
/** Default placeholder shown in an empty editor (overridden by an explicit `placeholder` prop). */
|
|
13
|
+
placeholder: string;
|
|
14
|
+
/** aria-label on the contenteditable itself. */
|
|
15
|
+
contentLabel: string;
|
|
16
|
+
/** window.confirm() prompt shown by EditorRef.confirmDiscard() when there are unsaved changes. */
|
|
17
|
+
confirmDiscard: string;
|
|
18
|
+
};
|
|
19
|
+
common: {
|
|
20
|
+
cancel: string;
|
|
21
|
+
save: string;
|
|
22
|
+
insert: string;
|
|
23
|
+
};
|
|
24
|
+
toolbar: {
|
|
25
|
+
/** aria-label on the toolbar's own role="toolbar" container. */
|
|
26
|
+
ariaLabel: string;
|
|
27
|
+
save: string;
|
|
28
|
+
undo: string;
|
|
29
|
+
redo: string;
|
|
30
|
+
formattingLabel: string;
|
|
31
|
+
bold: string;
|
|
32
|
+
italic: string;
|
|
33
|
+
underline: string;
|
|
34
|
+
fontFamily: string;
|
|
35
|
+
fontFamilyPlaceholder: string;
|
|
36
|
+
fontSize: string;
|
|
37
|
+
fontSizePlaceholder: string;
|
|
38
|
+
noMatch: string;
|
|
39
|
+
alignment: string;
|
|
40
|
+
linkOptions: string;
|
|
41
|
+
insertLink: string;
|
|
42
|
+
insertImage: string;
|
|
43
|
+
poetryBlocks: string;
|
|
44
|
+
comingSoon: (label: string) => string;
|
|
45
|
+
autocorrect: string;
|
|
46
|
+
textCleanup: string;
|
|
47
|
+
spellChecker: string;
|
|
48
|
+
moreFormatting: string;
|
|
49
|
+
strikethrough: string;
|
|
50
|
+
superscript: string;
|
|
51
|
+
subscript: string;
|
|
52
|
+
uppercase: string;
|
|
53
|
+
lowercase: string;
|
|
54
|
+
capitalize: string;
|
|
55
|
+
clearFormatting: string;
|
|
56
|
+
outdent: string;
|
|
57
|
+
indent: string;
|
|
58
|
+
formattingOptions: {
|
|
59
|
+
paragraph: string;
|
|
60
|
+
heading: (level: number) => string;
|
|
61
|
+
numberedList: string;
|
|
62
|
+
bulletList: string;
|
|
63
|
+
taskList: string;
|
|
64
|
+
quote: string;
|
|
65
|
+
};
|
|
66
|
+
alignOptions: {
|
|
67
|
+
start: string;
|
|
68
|
+
center: string;
|
|
69
|
+
end: string;
|
|
70
|
+
justify: string;
|
|
71
|
+
left: string;
|
|
72
|
+
right: string;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
link: {
|
|
76
|
+
menuLabel: string;
|
|
77
|
+
noUrl: string;
|
|
78
|
+
editLink: string;
|
|
79
|
+
removeLink: string;
|
|
80
|
+
};
|
|
81
|
+
linkDialog: {
|
|
82
|
+
titleInsert: string;
|
|
83
|
+
titleEdit: string;
|
|
84
|
+
urlLabel: string;
|
|
85
|
+
urlPlaceholder: string;
|
|
86
|
+
textLabel: string;
|
|
87
|
+
textPlaceholder: string;
|
|
88
|
+
invalidUrl: string;
|
|
89
|
+
apply: string;
|
|
90
|
+
};
|
|
91
|
+
imageDialog: {
|
|
92
|
+
titleInsert: string;
|
|
93
|
+
titleEdit: string;
|
|
94
|
+
currentImage: string;
|
|
95
|
+
fromUrl: string;
|
|
96
|
+
upload: string;
|
|
97
|
+
embeddedInDocument: string;
|
|
98
|
+
linkedFrom: (src: string) => string;
|
|
99
|
+
convertButton: string;
|
|
100
|
+
convertHint: string;
|
|
101
|
+
imageUrlLabel: string;
|
|
102
|
+
imageUrlPlaceholder: string;
|
|
103
|
+
embedCopyLabel: string;
|
|
104
|
+
embedCopyDescription: string;
|
|
105
|
+
convertedFromUrl: string;
|
|
106
|
+
fileLabel: string;
|
|
107
|
+
filePlaceholder: string;
|
|
108
|
+
fileDescription: (maxSizeMB: number, storedInDocument: boolean) => string;
|
|
109
|
+
altTextLabel: string;
|
|
110
|
+
altTextDescription: string;
|
|
111
|
+
captionLabel: string;
|
|
112
|
+
captionDescription: string;
|
|
113
|
+
errors: {
|
|
114
|
+
enterUrl: string;
|
|
115
|
+
chooseFile: string;
|
|
116
|
+
noImage: string;
|
|
117
|
+
uploadUnusable: string;
|
|
118
|
+
couldNotSave: string;
|
|
119
|
+
couldNotConvert: string;
|
|
120
|
+
couldNotReadFile: string;
|
|
121
|
+
largerThan: (maxSizeMB: number) => string;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
imageCropDialog: {
|
|
125
|
+
title: string;
|
|
126
|
+
sizeLabel: string;
|
|
127
|
+
widthLabel: string;
|
|
128
|
+
heightLabel: string;
|
|
129
|
+
autoPlaceholder: string;
|
|
130
|
+
keepProportions: string;
|
|
131
|
+
originalSize: string;
|
|
132
|
+
dragHint: (natural: {
|
|
133
|
+
width: number;
|
|
134
|
+
height: number;
|
|
135
|
+
} | null) => string;
|
|
136
|
+
errors: {
|
|
137
|
+
uploadUnusable: string;
|
|
138
|
+
couldNotSave: string;
|
|
139
|
+
largerThan: (maxSizeMB: number) => string;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
imageMenu: {
|
|
143
|
+
editImage: string;
|
|
144
|
+
convertToEmbedded: string;
|
|
145
|
+
cropResize: string;
|
|
146
|
+
addCaption: string;
|
|
147
|
+
editCaption: string;
|
|
148
|
+
removeCaption: string;
|
|
149
|
+
deleteImage: string;
|
|
150
|
+
resizeImage: string;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export declare const STRINGS: Record<Locale, Strings>;
|
|
154
|
+
/** Returns the strings dictionary for `locale`, defensively falling back to
|
|
155
|
+
* `en` for any locale not present in `STRINGS` (e.g. a future locale value
|
|
156
|
+
* passed by a caller on an older type definition). */
|
|
157
|
+
export declare function getStrings(locale: Locale): Strings;
|
|
158
|
+
//# sourceMappingURL=strings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI localization strings for the editor's own chrome (toolbar, dialogs,
|
|
3
|
+
* menus, placeholders) — see docs/lexical-editor-spec.md §3.2. This is
|
|
4
|
+
* separate from content direction/fonts (handled in EditorRoot) and from
|
|
5
|
+
* Markdown/HTML/plain-text format translation, which is out of scope here.
|
|
6
|
+
*/
|
|
7
|
+
const en = {
|
|
8
|
+
editor: {
|
|
9
|
+
placeholder: 'Start writing…',
|
|
10
|
+
contentLabel: 'Editor content',
|
|
11
|
+
confirmDiscard: 'You have unsaved changes. Discard them?',
|
|
12
|
+
},
|
|
13
|
+
common: {
|
|
14
|
+
cancel: 'Cancel',
|
|
15
|
+
save: 'Save',
|
|
16
|
+
insert: 'Insert',
|
|
17
|
+
},
|
|
18
|
+
toolbar: {
|
|
19
|
+
ariaLabel: 'Formatting',
|
|
20
|
+
save: 'Save',
|
|
21
|
+
undo: 'Undo',
|
|
22
|
+
redo: 'Redo',
|
|
23
|
+
formattingLabel: 'Formatting',
|
|
24
|
+
bold: 'Bold',
|
|
25
|
+
italic: 'Italic',
|
|
26
|
+
underline: 'Underline',
|
|
27
|
+
fontFamily: 'Font family',
|
|
28
|
+
fontFamilyPlaceholder: 'Font',
|
|
29
|
+
fontSize: 'Font size',
|
|
30
|
+
fontSizePlaceholder: 'Size',
|
|
31
|
+
noMatch: 'No match',
|
|
32
|
+
alignment: 'Alignment',
|
|
33
|
+
linkOptions: 'Link options',
|
|
34
|
+
insertLink: 'Insert link (Ctrl+K)',
|
|
35
|
+
insertImage: 'Insert image',
|
|
36
|
+
poetryBlocks: 'Poetry blocks',
|
|
37
|
+
comingSoon: (label) => `${label} (coming soon)`,
|
|
38
|
+
autocorrect: 'Auto-correct',
|
|
39
|
+
textCleanup: 'Text cleanup',
|
|
40
|
+
spellChecker: 'Spell-checker',
|
|
41
|
+
moreFormatting: 'More formatting',
|
|
42
|
+
strikethrough: 'Strikethrough',
|
|
43
|
+
superscript: 'Superscript',
|
|
44
|
+
subscript: 'Subscript',
|
|
45
|
+
uppercase: 'UPPERCASE',
|
|
46
|
+
lowercase: 'lowercase',
|
|
47
|
+
capitalize: 'Capitalize',
|
|
48
|
+
clearFormatting: 'Clear formatting',
|
|
49
|
+
outdent: 'Outdent',
|
|
50
|
+
indent: 'Indent',
|
|
51
|
+
formattingOptions: {
|
|
52
|
+
paragraph: 'Paragraph',
|
|
53
|
+
heading: (level) => `Heading ${level}`,
|
|
54
|
+
numberedList: 'Numbered list',
|
|
55
|
+
bulletList: 'Bullet list',
|
|
56
|
+
taskList: 'Task list',
|
|
57
|
+
quote: 'Quote',
|
|
58
|
+
},
|
|
59
|
+
alignOptions: {
|
|
60
|
+
start: 'Align start',
|
|
61
|
+
center: 'Align center',
|
|
62
|
+
end: 'Align end',
|
|
63
|
+
justify: 'Justify',
|
|
64
|
+
left: 'Align left',
|
|
65
|
+
right: 'Align right',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
link: {
|
|
69
|
+
menuLabel: 'Link',
|
|
70
|
+
noUrl: '(no URL)',
|
|
71
|
+
editLink: 'Edit link',
|
|
72
|
+
removeLink: 'Remove link',
|
|
73
|
+
},
|
|
74
|
+
linkDialog: {
|
|
75
|
+
titleInsert: 'Insert link',
|
|
76
|
+
titleEdit: 'Edit link',
|
|
77
|
+
urlLabel: 'URL',
|
|
78
|
+
urlPlaceholder: 'https://example.com',
|
|
79
|
+
textLabel: 'Text',
|
|
80
|
+
textPlaceholder: 'Link text (defaults to the URL)',
|
|
81
|
+
invalidUrl: 'Enter a valid http(s), mailto, tel or relative URL',
|
|
82
|
+
apply: 'Apply',
|
|
83
|
+
},
|
|
84
|
+
imageDialog: {
|
|
85
|
+
titleInsert: 'Insert image',
|
|
86
|
+
titleEdit: 'Edit image',
|
|
87
|
+
currentImage: 'Current image',
|
|
88
|
+
fromUrl: 'From URL',
|
|
89
|
+
upload: 'Upload',
|
|
90
|
+
embeddedInDocument: 'Embedded in the document',
|
|
91
|
+
linkedFrom: (src) => `Linked from ${src}`,
|
|
92
|
+
convertButton: 'Convert to embedded image',
|
|
93
|
+
convertHint: 'Downloads a copy into the document, so it can be cropped, rotated and resized.',
|
|
94
|
+
imageUrlLabel: 'Image URL',
|
|
95
|
+
imageUrlPlaceholder: 'https://example.com/photo.jpg',
|
|
96
|
+
embedCopyLabel: 'Embed a copy in the document',
|
|
97
|
+
embedCopyDescription: 'Downloads the image so it can be edited and no longer depends on the URL.',
|
|
98
|
+
convertedFromUrl: 'Converted from the URL: a copy will be embedded in the document.',
|
|
99
|
+
fileLabel: 'Image file',
|
|
100
|
+
filePlaceholder: 'Choose an image',
|
|
101
|
+
fileDescription: (maxSizeMB, storedInDocument) => `Up to ${maxSizeMB} MB${storedInDocument ? '; stored inside the document' : ''}`,
|
|
102
|
+
altTextLabel: 'Alt text',
|
|
103
|
+
altTextDescription: 'Describes the image for screen readers',
|
|
104
|
+
captionLabel: 'Caption',
|
|
105
|
+
captionDescription: 'Shown under the image. Leave empty for none.',
|
|
106
|
+
errors: {
|
|
107
|
+
enterUrl: 'Enter an http(s) or relative image URL',
|
|
108
|
+
chooseFile: 'Choose an image file',
|
|
109
|
+
noImage: 'No image',
|
|
110
|
+
uploadUnusable: 'The upload handler returned an unusable image URL',
|
|
111
|
+
couldNotSave: 'Could not save the image',
|
|
112
|
+
couldNotConvert: 'Could not convert the image',
|
|
113
|
+
couldNotReadFile: 'Could not read the file',
|
|
114
|
+
largerThan: (maxSizeMB) => `Image is larger than ${maxSizeMB} MB`,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
imageCropDialog: {
|
|
118
|
+
title: 'Crop & resize',
|
|
119
|
+
sizeLabel: 'Size',
|
|
120
|
+
widthLabel: 'Width (px)',
|
|
121
|
+
heightLabel: 'Height (px)',
|
|
122
|
+
autoPlaceholder: 'Auto',
|
|
123
|
+
keepProportions: 'Keep proportions',
|
|
124
|
+
originalSize: 'Original size',
|
|
125
|
+
dragHint: (natural) => `You can also drag the corner handle on a selected image. ${natural ? `Original: ${natural.width} × ${natural.height} px.` : ''}`,
|
|
126
|
+
errors: {
|
|
127
|
+
uploadUnusable: 'The upload handler returned an unusable image URL',
|
|
128
|
+
couldNotSave: 'Could not save the image',
|
|
129
|
+
largerThan: (maxSizeMB) => `Image is larger than ${maxSizeMB} MB`,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
imageMenu: {
|
|
133
|
+
editImage: 'Edit image…',
|
|
134
|
+
convertToEmbedded: 'Convert to embedded image',
|
|
135
|
+
cropResize: 'Crop & resize…',
|
|
136
|
+
addCaption: 'Add caption…',
|
|
137
|
+
editCaption: 'Edit caption…',
|
|
138
|
+
removeCaption: 'Remove caption',
|
|
139
|
+
deleteImage: 'Delete image',
|
|
140
|
+
resizeImage: 'Resize image',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
const ur = {
|
|
144
|
+
editor: {
|
|
145
|
+
placeholder: 'لکھنا شروع کریں…',
|
|
146
|
+
contentLabel: 'ایڈیٹر کا مواد',
|
|
147
|
+
confirmDiscard: 'آپ کی غیر محفوظ شدہ تبدیلیاں ہیں۔ کیا انہیں رد کر دیا جائے؟',
|
|
148
|
+
},
|
|
149
|
+
common: {
|
|
150
|
+
cancel: 'منسوخ کریں',
|
|
151
|
+
save: 'محفوظ کریں',
|
|
152
|
+
insert: 'شامل کریں',
|
|
153
|
+
},
|
|
154
|
+
toolbar: {
|
|
155
|
+
ariaLabel: 'فارمیٹنگ',
|
|
156
|
+
save: 'محفوظ کریں',
|
|
157
|
+
undo: 'کالعدم کریں',
|
|
158
|
+
redo: 'دوبارہ کریں',
|
|
159
|
+
formattingLabel: 'فارمیٹنگ',
|
|
160
|
+
bold: 'موٹا',
|
|
161
|
+
italic: 'ترچھا',
|
|
162
|
+
underline: 'زیرِ خط',
|
|
163
|
+
fontFamily: 'فونٹ کا خاندان',
|
|
164
|
+
fontFamilyPlaceholder: 'فونٹ',
|
|
165
|
+
fontSize: 'فونٹ کا سائز',
|
|
166
|
+
fontSizePlaceholder: 'سائز',
|
|
167
|
+
noMatch: 'کوئی مماثلت نہیں',
|
|
168
|
+
alignment: 'سیدھ',
|
|
169
|
+
linkOptions: 'لنک کے اختیارات',
|
|
170
|
+
insertLink: 'لنک شامل کریں (Ctrl+K)',
|
|
171
|
+
insertImage: 'تصویر شامل کریں',
|
|
172
|
+
poetryBlocks: 'شاعری کے بلاکس',
|
|
173
|
+
comingSoon: (label) => `${label} (جلد آ رہا ہے)`,
|
|
174
|
+
autocorrect: 'خودکار تصحیح',
|
|
175
|
+
textCleanup: 'متن کی صفائی',
|
|
176
|
+
spellChecker: 'ہجے چیک کرنے والا',
|
|
177
|
+
moreFormatting: 'مزید فارمیٹنگ',
|
|
178
|
+
strikethrough: 'خط زدہ',
|
|
179
|
+
superscript: 'بالا نویس',
|
|
180
|
+
subscript: 'زیریں نویس',
|
|
181
|
+
uppercase: 'بڑے حروف',
|
|
182
|
+
lowercase: 'چھوٹے حروف',
|
|
183
|
+
capitalize: 'پہلا حرف بڑا',
|
|
184
|
+
clearFormatting: 'فارمیٹنگ ختم کریں',
|
|
185
|
+
outdent: 'حاشیہ کم کریں',
|
|
186
|
+
indent: 'حاشیہ بڑھائیں',
|
|
187
|
+
formattingOptions: {
|
|
188
|
+
paragraph: 'پیراگراف',
|
|
189
|
+
heading: (level) => `عنوان ${level}`,
|
|
190
|
+
numberedList: 'نمبردار فہرست',
|
|
191
|
+
bulletList: 'بلٹ فہرست',
|
|
192
|
+
taskList: 'ٹاسک فہرست',
|
|
193
|
+
quote: 'اقتباس',
|
|
194
|
+
},
|
|
195
|
+
alignOptions: {
|
|
196
|
+
start: 'ابتدائی سیدھ',
|
|
197
|
+
center: 'درمیانی سیدھ',
|
|
198
|
+
end: 'اختتامی سیدھ',
|
|
199
|
+
justify: 'دو طرفہ سیدھ',
|
|
200
|
+
left: 'بائیں سیدھ',
|
|
201
|
+
right: 'دائیں سیدھ',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
link: {
|
|
205
|
+
menuLabel: 'لنک',
|
|
206
|
+
noUrl: '(کوئی یو آر ایل نہیں)',
|
|
207
|
+
editLink: 'لنک میں ترمیم کریں',
|
|
208
|
+
removeLink: 'لنک ہٹائیں',
|
|
209
|
+
},
|
|
210
|
+
linkDialog: {
|
|
211
|
+
titleInsert: 'لنک شامل کریں',
|
|
212
|
+
titleEdit: 'لنک میں ترمیم کریں',
|
|
213
|
+
urlLabel: 'یو آر ایل',
|
|
214
|
+
urlPlaceholder: 'https://example.com',
|
|
215
|
+
textLabel: 'عبارت',
|
|
216
|
+
textPlaceholder: 'لنک کی عبارت (نہ دینے پر یو آر ایل ہی استعمال ہوگا)',
|
|
217
|
+
invalidUrl: 'براہِ کرم ایک درست http(s)، mailto، tel یا رشتہ دار یو آر ایل درج کریں',
|
|
218
|
+
apply: 'لاگو کریں',
|
|
219
|
+
},
|
|
220
|
+
imageDialog: {
|
|
221
|
+
titleInsert: 'تصویر شامل کریں',
|
|
222
|
+
titleEdit: 'تصویر میں ترمیم کریں',
|
|
223
|
+
currentImage: 'موجودہ تصویر',
|
|
224
|
+
fromUrl: 'یو آر ایل سے',
|
|
225
|
+
upload: 'اپ لوڈ',
|
|
226
|
+
embeddedInDocument: 'دستاویز میں شامل',
|
|
227
|
+
linkedFrom: (src) => `${src} سے منسلک`,
|
|
228
|
+
convertButton: 'شامل شدہ تصویر میں تبدیل کریں',
|
|
229
|
+
convertHint: 'دستاویز میں ایک نقل ڈاؤن لوڈ کرتا ہے، تاکہ اسے کاٹا، گھمایا اور سائز تبدیل کیا جا سکے۔',
|
|
230
|
+
imageUrlLabel: 'تصویر کا یو آر ایل',
|
|
231
|
+
imageUrlPlaceholder: 'https://example.com/photo.jpg',
|
|
232
|
+
embedCopyLabel: 'دستاویز میں ایک نقل شامل کریں',
|
|
233
|
+
embedCopyDescription: 'تصویر ڈاؤن لوڈ کرتا ہے تاکہ اس میں ترمیم کی جا سکے اور یہ یو آر ایل پر منحصر نہ رہے۔',
|
|
234
|
+
convertedFromUrl: 'یو آر ایل سے تبدیل شدہ: ایک نقل دستاویز میں شامل کی جائے گی۔',
|
|
235
|
+
fileLabel: 'تصویر کی فائل',
|
|
236
|
+
filePlaceholder: 'ایک تصویر منتخب کریں',
|
|
237
|
+
fileDescription: (maxSizeMB, storedInDocument) => `${maxSizeMB} ایم بی تک${storedInDocument ? '؛ دستاویز کے اندر محفوظ ہوتی ہے' : ''}`,
|
|
238
|
+
altTextLabel: 'متبادل متن',
|
|
239
|
+
altTextDescription: 'سکرین ریڈرز کے لیے تصویر کی تفصیل بیان کرتا ہے',
|
|
240
|
+
captionLabel: 'کیپشن',
|
|
241
|
+
captionDescription: 'تصویر کے نیچے دکھایا جاتا ہے۔ نہ چاہیں تو خالی چھوڑ دیں۔',
|
|
242
|
+
errors: {
|
|
243
|
+
enterUrl: 'ایک http(s) یا رشتہ دار تصویر یو آر ایل درج کریں',
|
|
244
|
+
chooseFile: 'ایک تصویر فائل منتخب کریں',
|
|
245
|
+
noImage: 'کوئی تصویر نہیں',
|
|
246
|
+
uploadUnusable: 'اپ لوڈ ہینڈلر نے ناقابلِ استعمال یو آر ایل واپس کیا',
|
|
247
|
+
couldNotSave: 'تصویر محفوظ نہیں ہو سکی',
|
|
248
|
+
couldNotConvert: 'تصویر تبدیل نہیں ہو سکی',
|
|
249
|
+
couldNotReadFile: 'فائل پڑھی نہیں جا سکی',
|
|
250
|
+
largerThan: (maxSizeMB) => `تصویر ${maxSizeMB} ایم بی سے بڑی ہے`,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
imageCropDialog: {
|
|
254
|
+
title: 'کاٹیں اور سائز تبدیل کریں',
|
|
255
|
+
sizeLabel: 'سائز',
|
|
256
|
+
widthLabel: 'چوڑائی (px)',
|
|
257
|
+
heightLabel: 'اونچائی (px)',
|
|
258
|
+
autoPlaceholder: 'خودکار',
|
|
259
|
+
keepProportions: 'تناسب برقرار رکھیں',
|
|
260
|
+
originalSize: 'اصل سائز',
|
|
261
|
+
dragHint: (natural) => `منتخب شدہ تصویر پر کارنر ہینڈل کھینچ کر بھی سائز تبدیل کیا جا سکتا ہے۔ ${natural ? `اصل: ${natural.width} × ${natural.height} پکسل۔` : ''}`,
|
|
262
|
+
errors: {
|
|
263
|
+
uploadUnusable: 'اپ لوڈ ہینڈلر نے ناقابلِ استعمال یو آر ایل واپس کیا',
|
|
264
|
+
couldNotSave: 'تصویر محفوظ نہیں ہو سکی',
|
|
265
|
+
largerThan: (maxSizeMB) => `تصویر ${maxSizeMB} ایم بی سے بڑی ہے`,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
imageMenu: {
|
|
269
|
+
editImage: 'تصویر میں ترمیم کریں…',
|
|
270
|
+
convertToEmbedded: 'شامل شدہ تصویر میں تبدیل کریں',
|
|
271
|
+
cropResize: 'کاٹیں اور سائز تبدیل کریں…',
|
|
272
|
+
addCaption: 'کیپشن شامل کریں…',
|
|
273
|
+
editCaption: 'کیپشن میں ترمیم کریں…',
|
|
274
|
+
removeCaption: 'کیپشن ہٹائیں',
|
|
275
|
+
deleteImage: 'تصویر حذف کریں',
|
|
276
|
+
resizeImage: 'تصویر کا سائز تبدیل کریں',
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
const paShahmukhi = {
|
|
280
|
+
editor: {
|
|
281
|
+
placeholder: 'لکھنا شروع کرو…',
|
|
282
|
+
contentLabel: 'ایڈیٹر دی سامگری',
|
|
283
|
+
confirmDiscard: 'تہاڈیاں کجھ تبدیلیاں سنبھالیاں نئیں گئیاں۔ کی ایہناں نوں رد کر دیئے؟',
|
|
284
|
+
},
|
|
285
|
+
common: {
|
|
286
|
+
cancel: 'رد کرو',
|
|
287
|
+
save: 'سنبھالو',
|
|
288
|
+
insert: 'پاؤ',
|
|
289
|
+
},
|
|
290
|
+
toolbar: {
|
|
291
|
+
ariaLabel: 'فارمیٹنگ',
|
|
292
|
+
save: 'سنبھالو',
|
|
293
|
+
undo: 'پہلاں جیہا کرو',
|
|
294
|
+
redo: 'مُڑ کرو',
|
|
295
|
+
formattingLabel: 'فارمیٹنگ',
|
|
296
|
+
bold: 'گہرا',
|
|
297
|
+
italic: 'ترچھا',
|
|
298
|
+
underline: 'تھلے لکیر',
|
|
299
|
+
fontFamily: 'فونٹ خاندان',
|
|
300
|
+
fontFamilyPlaceholder: 'فونٹ',
|
|
301
|
+
fontSize: 'فونٹ دا سائز',
|
|
302
|
+
fontSizePlaceholder: 'سائز',
|
|
303
|
+
noMatch: 'کوئی نئیں ملیا',
|
|
304
|
+
alignment: 'سیدھ',
|
|
305
|
+
linkOptions: 'لنک دے اختیار',
|
|
306
|
+
insertLink: 'لنک پاؤ (Ctrl+K)',
|
|
307
|
+
insertImage: 'تصویر پاؤ',
|
|
308
|
+
poetryBlocks: 'شاعری بلاک',
|
|
309
|
+
comingSoon: (label) => `${label} (چھیتی آ رہا اے)`,
|
|
310
|
+
autocorrect: 'خودکار درستی',
|
|
311
|
+
textCleanup: 'متن دی صفائی',
|
|
312
|
+
spellChecker: 'ہجے چیکر',
|
|
313
|
+
moreFormatting: 'ہور فارمیٹنگ',
|
|
314
|
+
strikethrough: 'کٹی لکیر',
|
|
315
|
+
superscript: 'اُتلا لکھت',
|
|
316
|
+
subscript: 'تلا لکھت',
|
|
317
|
+
uppercase: 'وڈے حرف',
|
|
318
|
+
lowercase: 'نکے حرف',
|
|
319
|
+
capitalize: 'پہلا حرف وڈا',
|
|
320
|
+
clearFormatting: 'فارمیٹنگ ہٹاؤ',
|
|
321
|
+
outdent: 'حاشیہ گھٹاؤ',
|
|
322
|
+
indent: 'حاشیہ ودھاؤ',
|
|
323
|
+
formattingOptions: {
|
|
324
|
+
paragraph: 'پیراگراف',
|
|
325
|
+
heading: (level) => `سرلیکھ ${level}`,
|
|
326
|
+
numberedList: 'نمبردار لسٹ',
|
|
327
|
+
bulletList: 'بلٹ لسٹ',
|
|
328
|
+
taskList: 'ٹاسک لسٹ',
|
|
329
|
+
quote: 'حوالہ',
|
|
330
|
+
},
|
|
331
|
+
alignOptions: {
|
|
332
|
+
start: 'شروع دی سیدھ',
|
|
333
|
+
center: 'وچکار دی سیدھ',
|
|
334
|
+
end: 'آخری سیدھ',
|
|
335
|
+
justify: 'دوویں پاسے سیدھ',
|
|
336
|
+
left: 'بائیں سیدھ',
|
|
337
|
+
right: 'سجے سیدھ',
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
link: {
|
|
341
|
+
menuLabel: 'لنک',
|
|
342
|
+
noUrl: '(کوئی یو آر ایل نئیں)',
|
|
343
|
+
editLink: 'لنک وچ تبدیلی کرو',
|
|
344
|
+
removeLink: 'لنک ہٹاؤ',
|
|
345
|
+
},
|
|
346
|
+
linkDialog: {
|
|
347
|
+
titleInsert: 'لنک پاؤ',
|
|
348
|
+
titleEdit: 'لنک وچ تبدیلی کرو',
|
|
349
|
+
urlLabel: 'یو آر ایل',
|
|
350
|
+
urlPlaceholder: 'https://example.com',
|
|
351
|
+
textLabel: 'لکھت',
|
|
352
|
+
textPlaceholder: 'لنک دی لکھت (نہ دین تے یو آر ایل ای ورتیا جاوے گا)',
|
|
353
|
+
invalidUrl: 'کِرپا کر کے صحیح http(s)، mailto، tel یا نسبتی یو آر ایل پاؤ',
|
|
354
|
+
apply: 'لاگو کرو',
|
|
355
|
+
},
|
|
356
|
+
imageDialog: {
|
|
357
|
+
titleInsert: 'تصویر پاؤ',
|
|
358
|
+
titleEdit: 'تصویر وچ تبدیلی کرو',
|
|
359
|
+
currentImage: 'موجودہ تصویر',
|
|
360
|
+
fromUrl: 'یو آر ایل توں',
|
|
361
|
+
upload: 'اپ لوڈ',
|
|
362
|
+
embeddedInDocument: 'دستاویز وچ شامل',
|
|
363
|
+
linkedFrom: (src) => `${src} توں لنک کیتا گیا`,
|
|
364
|
+
convertButton: 'شامل تصویر وچ بدلو',
|
|
365
|
+
convertHint: 'دستاویز وچ اک کاپی ڈاؤن لوڈ کردا اے، تاں جو ایہنوں کٹیا، گھمایا تے سائز بدلیا جا سکے۔',
|
|
366
|
+
imageUrlLabel: 'تصویر دا یو آر ایل',
|
|
367
|
+
imageUrlPlaceholder: 'https://example.com/photo.jpg',
|
|
368
|
+
embedCopyLabel: 'دستاویز وچ اک کاپی شامل کرو',
|
|
369
|
+
embedCopyDescription: 'تصویر ڈاؤن لوڈ کردا اے تاں جو ایہنوں تبدیل کیتا جا سکے تے ایہہ یو آر ایل تے منحصر نہ رہے۔',
|
|
370
|
+
convertedFromUrl: 'یو آر ایل توں بدلیا گیا: اک کاپی دستاویز وچ شامل کیتی جاوے گی۔',
|
|
371
|
+
fileLabel: 'تصویر دی فائل',
|
|
372
|
+
filePlaceholder: 'اک تصویر چُنو',
|
|
373
|
+
fileDescription: (maxSizeMB, storedInDocument) => `${maxSizeMB} ایم بی تیک${storedInDocument ? '؛ دستاویز دے اندر سنبھالی جاندی اے' : ''}`,
|
|
374
|
+
altTextLabel: 'بدل متن',
|
|
375
|
+
altTextDescription: 'سکرین ریڈرز لئی تصویر دی وضاحت کردا اے',
|
|
376
|
+
captionLabel: 'کیپشن',
|
|
377
|
+
captionDescription: 'تصویر دے تھلے دکھایا جاندا اے۔ نہ چاہیدا تے خالی چھڈ دیو۔',
|
|
378
|
+
errors: {
|
|
379
|
+
enterUrl: 'اک http(s) یا نسبتی تصویر یو آر ایل پاؤ',
|
|
380
|
+
chooseFile: 'اک تصویر فائل چُنو',
|
|
381
|
+
noImage: 'کوئی تصویر نئیں',
|
|
382
|
+
uploadUnusable: 'اپ لوڈ ہینڈلر نے ناکارآمد یو آر ایل موڑیا',
|
|
383
|
+
couldNotSave: 'تصویر سنبھالی نئیں جا سکی',
|
|
384
|
+
couldNotConvert: 'تصویر بدلی نئیں جا سکی',
|
|
385
|
+
couldNotReadFile: 'فائل پڑھی نئیں جا سکی',
|
|
386
|
+
largerThan: (maxSizeMB) => `تصویر ${maxSizeMB} ایم بی توں وڈی اے`,
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
imageCropDialog: {
|
|
390
|
+
title: 'کٹو تے سائز بدلو',
|
|
391
|
+
sizeLabel: 'سائز',
|
|
392
|
+
widthLabel: 'چوڑائی (px)',
|
|
393
|
+
heightLabel: 'اُچائی (px)',
|
|
394
|
+
autoPlaceholder: 'خودکار',
|
|
395
|
+
keepProportions: 'تناسب رکھو',
|
|
396
|
+
originalSize: 'اصلی سائز',
|
|
397
|
+
dragHint: (natural) => `چُنی ہوئی تصویر تے کارنر ہینڈل گھسیٹ کے وی سائز بدلیا جا سکدا اے۔ ${natural ? `اصلی: ${natural.width} × ${natural.height} پکسل۔` : ''}`,
|
|
398
|
+
errors: {
|
|
399
|
+
uploadUnusable: 'اپ لوڈ ہینڈلر نے ناکارآمد یو آر ایل موڑیا',
|
|
400
|
+
couldNotSave: 'تصویر سنبھالی نئیں جا سکی',
|
|
401
|
+
largerThan: (maxSizeMB) => `تصویر ${maxSizeMB} ایم بی توں وڈی اے`,
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
imageMenu: {
|
|
405
|
+
editImage: 'تصویر وچ تبدیلی کرو…',
|
|
406
|
+
convertToEmbedded: 'شامل تصویر وچ بدلو',
|
|
407
|
+
cropResize: 'کٹو تے سائز بدلو…',
|
|
408
|
+
addCaption: 'کیپشن شامل کرو…',
|
|
409
|
+
editCaption: 'کیپشن وچ تبدیلی کرو…',
|
|
410
|
+
removeCaption: 'کیپشن ہٹاؤ',
|
|
411
|
+
deleteImage: 'تصویر مٹاؤ',
|
|
412
|
+
resizeImage: 'تصویر دا سائز بدلو',
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
export const STRINGS = {
|
|
416
|
+
en,
|
|
417
|
+
ur,
|
|
418
|
+
'pa-shahmukhi': paShahmukhi,
|
|
419
|
+
};
|
|
420
|
+
/** Returns the strings dictionary for `locale`, defensively falling back to
|
|
421
|
+
* `en` for any locale not present in `STRINGS` (e.g. a future locale value
|
|
422
|
+
* passed by a caller on an older type definition). */
|
|
423
|
+
export function getStrings(locale) {
|
|
424
|
+
return STRINGS[locale] ?? STRINGS.en;
|
|
425
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Locale, type Strings } from './strings.js';
|
|
2
|
+
/** Returns the translated strings dictionary for `locale`. */
|
|
3
|
+
export declare function useStrings(locale: Locale): Strings;
|
|
4
|
+
/** Provided once at `EditorRoot`'s top level so components that aren't
|
|
5
|
+
* direct children (dialogs, context menus) can reach the current locale's
|
|
6
|
+
* strings without threading a `locale` prop through every intermediate
|
|
7
|
+
* component. */
|
|
8
|
+
export declare const UiStringsContext: import("react").Context<Strings>;
|
|
9
|
+
export declare function useUiStrings(): Strings;
|
|
10
|
+
//# sourceMappingURL=useStrings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStrings.d.ts","sourceRoot":"","sources":["../../src/i18n/useStrings.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAElE,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;gBAGgB;AAChB,eAAO,MAAM,gBAAgB,kCAA2C,CAAC;AAEzE,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import { getStrings } from './strings.js';
|
|
3
|
+
/** Returns the translated strings dictionary for `locale`. */
|
|
4
|
+
export function useStrings(locale) {
|
|
5
|
+
return useMemo(() => getStrings(locale), [locale]);
|
|
6
|
+
}
|
|
7
|
+
/** Provided once at `EditorRoot`'s top level so components that aren't
|
|
8
|
+
* direct children (dialogs, context menus) can reach the current locale's
|
|
9
|
+
* strings without threading a `locale` prop through every intermediate
|
|
10
|
+
* component. */
|
|
11
|
+
export const UiStringsContext = createContext(getStrings('en'));
|
|
12
|
+
export function useUiStrings() {
|
|
13
|
+
return useContext(UiStringsContext);
|
|
14
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageComponent.d.ts","sourceRoot":"","sources":["../../src/image/ImageComponent.tsx"],"names":[],"mappings":"AAKA,OAAO,EASL,KAAK,OAAO,EACb,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ImageComponent.d.ts","sourceRoot":"","sources":["../../src/image/ImageComponent.tsx"],"names":[],"mappings":"AAKA,OAAO,EASL,KAAK,OAAO,EACb,MAAM,SAAS,CAAC;AAMjB,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,aAAa,CAAC;AAU5D,UAAU,mBAAmB;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB,yFAAyF;IACzF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,mBAAmB,+BA0P9G"}
|
|
@@ -8,6 +8,7 @@ import { $getNodeByKey, $getSelection, $isNodeSelection, CLICK_COMMAND, COMMAND_
|
|
|
8
8
|
import { ImageCropDialog } from './ImageCropDialog.js';
|
|
9
9
|
import { ImageDialog } from './ImageDialog.js';
|
|
10
10
|
import { useImageOptions } from './ImageOptionsContext.js';
|
|
11
|
+
import { useUiStrings } from '../i18n/useStrings.js';
|
|
11
12
|
import { dataUrlBytes, fetchImageAsDataUrl } from './imageEdit.js';
|
|
12
13
|
// A type-only import of ImageNode (ImageNode itself imports this file), so the
|
|
13
14
|
// node is recognised by its type string rather than `instanceof`.
|
|
@@ -26,6 +27,7 @@ const MIN_WIDTH = 32;
|
|
|
26
27
|
export function ImageComponent({ nodeKey, src, altText, caption, linkType, width, height }) {
|
|
27
28
|
const [editor] = useLexicalComposerContext();
|
|
28
29
|
const options = useImageOptions();
|
|
30
|
+
const strings = useUiStrings();
|
|
29
31
|
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
|
|
30
32
|
const imageRef = useRef(null);
|
|
31
33
|
const figureRef = useRef(null);
|
|
@@ -82,7 +84,7 @@ export function ImageComponent({ nodeKey, src, altText, caption, linkType, width
|
|
|
82
84
|
try {
|
|
83
85
|
const dataUrl = await fetchImageAsDataUrl(src, options.fetchImage);
|
|
84
86
|
if (dataUrlBytes(dataUrl) > options.maxSizeMB * 1024 * 1024) {
|
|
85
|
-
throw new Error(
|
|
87
|
+
throw new Error(strings.imageDialog.errors.largerThan(options.maxSizeMB));
|
|
86
88
|
}
|
|
87
89
|
updateNode((node) => node.setSource(dataUrl, 'embedded'));
|
|
88
90
|
}
|
|
@@ -154,10 +156,10 @@ export function ImageComponent({ nodeKey, src, altText, caption, linkType, width
|
|
|
154
156
|
clearSelection();
|
|
155
157
|
setSelected(true);
|
|
156
158
|
setMenu({ x: e.clientX, y: e.clientY });
|
|
157
|
-
}, children: [_jsxs("span", { className: "likhari-image-wrap", children: [_jsx("img", { ref: imageRef, className: "likhari-image", src: src, alt: altText, width: shownWidth, draggable: false }), isSelected && linkType === 'embedded' && (_jsx("span", { className: "likhari-image-resize-handle", role: "separator", "aria-label":
|
|
159
|
+
}, children: [_jsxs("span", { className: "likhari-image-wrap", children: [_jsx("img", { ref: imageRef, className: "likhari-image", src: src, alt: altText, width: shownWidth, draggable: false }), isSelected && linkType === 'embedded' && (_jsx("span", { className: "likhari-image-resize-handle", role: "separator", "aria-label": strings.imageMenu.resizeImage, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onPointerCancel: onResizeUp }))] }), caption && _jsx("figcaption", { className: "likhari-image-caption", children: caption }), _jsxs(Menu, { opened: menu !== null, onChange: (opened) => {
|
|
158
160
|
if (!opened)
|
|
159
161
|
setMenu(null);
|
|
160
|
-
}, position: "bottom-start", withinPortal: true, shadow: "sm", width: 200, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: menu?.x ?? -9999, top: menu?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsxs(Menu.Dropdown, { children: [_jsx(Menu.Item, { leftSection: _jsx(IconPhotoEdit, { size: 16, stroke: 1.75 }), onClick: () => setDialog({}), children:
|
|
162
|
+
}, position: "bottom-start", withinPortal: true, shadow: "sm", width: 200, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: menu?.x ?? -9999, top: menu?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsxs(Menu.Dropdown, { children: [_jsx(Menu.Item, { leftSection: _jsx(IconPhotoEdit, { size: 16, stroke: 1.75 }), onClick: () => setDialog({}), children: strings.imageMenu.editImage }), linkType === 'linked' && options.allowEmbedded && (_jsx(Menu.Item, { leftSection: _jsx(IconPhotoDown, { size: 16, stroke: 1.75 }), onClick: () => void convertToEmbedded(), children: strings.imageMenu.convertToEmbedded })), linkType === 'embedded' && (_jsx(Menu.Item, { leftSection: _jsx(IconCrop, { size: 16, stroke: 1.75 }), onClick: () => setCropDialogOpen(true), children: strings.imageMenu.cropResize })), options.allowCaption && (_jsx(Menu.Item, { leftSection: _jsx(IconTextCaption, { size: 16, stroke: 1.75 }), onClick: () => setDialog({ intent: 'caption' }), children: caption ? strings.imageMenu.editCaption : strings.imageMenu.addCaption })), options.allowCaption && caption && (_jsx(Menu.Item, { leftSection: _jsx(IconX, { size: 16, stroke: 1.75 }), onClick: () => updateNode((node) => node.setCaption(null)), children: strings.imageMenu.removeCaption })), _jsx(Menu.Divider, {}), _jsx(Menu.Item, { color: "red", leftSection: _jsx(IconTrash, { size: 16, stroke: 1.75 }), onClick: removeImage, children: strings.imageMenu.deleteImage })] })] }), _jsx(ImageDialog, { mode: "edit", opened: dialog !== null, initial: { src, altText, caption, linkType }, intent: dialog?.intent, onSubmit: applyEdit, onClose: () => {
|
|
161
163
|
setDialog(null);
|
|
162
164
|
editor.focus();
|
|
163
165
|
} }), linkType === 'embedded' && (_jsx(ImageCropDialog, { opened: cropDialogOpen, initial: { src, width, height }, onSubmit: applyCrop, onClose: () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageCropDialog.d.ts","sourceRoot":"","sources":["../../src/image/ImageCropDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ImageCropDialog.d.ts","sourceRoot":"","sources":["../../src/image/ImageCropDialog.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB;oEACgE;IAChE,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACtE,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAqBD;;qEAEqE;AACrE,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,oBAAoB,+BA6J3F"}
|
|
@@ -5,6 +5,7 @@ import { normalizeImageUrl } from './imageUrl.js';
|
|
|
5
5
|
import { dataUrlBytes, dataUrlToFile, fitDimension, mimeFromSrc } from './imageEdit.js';
|
|
6
6
|
import { ImageCropper } from './ImageCropper.js';
|
|
7
7
|
import { useImageOptions } from './ImageOptionsContext.js';
|
|
8
|
+
import { useUiStrings } from '../i18n/useStrings.js';
|
|
8
9
|
/** Natural pixel size of an image URL (for the aspect ratio and size presets). */
|
|
9
10
|
function useNaturalSize(src) {
|
|
10
11
|
const [size, setSize] = useState(null);
|
|
@@ -29,6 +30,7 @@ function useNaturalSize(src) {
|
|
|
29
30
|
* its context menu. Edits that change pixels are re-embedded as a data URI,
|
|
30
31
|
* or stored through the host's `onImageUpload` when there is one. */
|
|
31
32
|
export function ImageCropDialog({ opened, initial, onSubmit, onClose }) {
|
|
33
|
+
const strings = useUiStrings();
|
|
32
34
|
const { maxSizeMB, onImageUpload } = useImageOptions();
|
|
33
35
|
const [pixelSrc, setPixelSrc] = useState(null);
|
|
34
36
|
const [width, setWidth] = useState(null);
|
|
@@ -75,7 +77,7 @@ export function ImageCropDialog({ opened, initial, onSubmit, onClose }) {
|
|
|
75
77
|
let src = effectiveSrc;
|
|
76
78
|
if (pixelSrc) {
|
|
77
79
|
if (dataUrlBytes(pixelSrc) > maxSizeMB * 1024 * 1024) {
|
|
78
|
-
setError(
|
|
80
|
+
setError(strings.imageCropDialog.errors.largerThan(maxSizeMB));
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
81
83
|
if (onImageUpload) {
|
|
@@ -83,7 +85,7 @@ export function ImageCropDialog({ opened, initial, onSubmit, onClose }) {
|
|
|
83
85
|
const ext = mimeFromSrc(pixelSrc).split('/')[1];
|
|
84
86
|
src = await onImageUpload(dataUrlToFile(pixelSrc, `image.${ext}`));
|
|
85
87
|
if (!normalizeImageUrl(src)) {
|
|
86
|
-
setError(
|
|
88
|
+
setError(strings.imageCropDialog.errors.uploadUnusable);
|
|
87
89
|
return;
|
|
88
90
|
}
|
|
89
91
|
}
|
|
@@ -91,23 +93,26 @@ export function ImageCropDialog({ opened, initial, onSubmit, onClose }) {
|
|
|
91
93
|
onSubmit({ src, width, height });
|
|
92
94
|
}
|
|
93
95
|
catch (err) {
|
|
94
|
-
|
|
96
|
+
// ImageCropper's own pixel-edit error message (imageEdit.ts,
|
|
97
|
+
// applyPixelOp) is shown as-is when present — see the note in
|
|
98
|
+
// ImageDialog.tsx for why that pure utility's messages stay English.
|
|
99
|
+
setError(err instanceof Error ? err.message : strings.imageCropDialog.errors.couldNotSave);
|
|
95
100
|
}
|
|
96
101
|
finally {
|
|
97
102
|
setBusy(false);
|
|
98
103
|
}
|
|
99
104
|
};
|
|
100
|
-
return (_jsx(Modal, { opened: opened, onClose: onClose, title:
|
|
105
|
+
return (_jsx(Modal, { opened: opened, onClose: onClose, title: strings.imageCropDialog.title, centered: true, size: "lg", children: _jsx("form", { onSubmit: handleSubmit, children: _jsxs(Stack, { gap: "md", children: [_jsx(ImageCropper, { src: effectiveSrc, canReset: pixelSrc !== null, onApply: (edited) => {
|
|
101
106
|
setPixelSrc(edited);
|
|
102
107
|
resetSize();
|
|
103
108
|
}, onReset: () => {
|
|
104
109
|
setPixelSrc(null);
|
|
105
110
|
setWidth(initial.width);
|
|
106
111
|
setHeight(initial.height);
|
|
107
|
-
} }), _jsx(Divider, { label:
|
|
112
|
+
} }), _jsx(Divider, { label: strings.imageCropDialog.sizeLabel, labelPosition: "left" }), _jsxs(Stack, { gap: "sm", children: [_jsxs(Group, { grow: true, align: "flex-end", children: [_jsx(NumberInput, { label: strings.imageCropDialog.widthLabel, min: 1, max: 10000, hideControls: true, placeholder: natural ? String(natural.width) : strings.imageCropDialog.autoPlaceholder, value: width ?? '', onChange: (v) => onSizeChange('width', v) }), _jsx(NumberInput, { label: strings.imageCropDialog.heightLabel, min: 1, max: 10000, hideControls: true, placeholder: natural ? String(natural.height) : strings.imageCropDialog.autoPlaceholder, value: height ?? '', onChange: (v) => onSizeChange('height', v) })] }), _jsx(Checkbox, { label: strings.imageCropDialog.keepProportions, checked: lockAspect, onChange: (e) => setLockAspect(e.currentTarget.checked) }), _jsxs(Group, { gap: "xs", children: [[25, 50, 75, 100].map((pct) => (_jsxs(Button, { size: "xs", variant: "default", disabled: !natural, onClick: () => {
|
|
108
113
|
if (!natural)
|
|
109
114
|
return;
|
|
110
115
|
setWidth(Math.round((natural.width * pct) / 100));
|
|
111
116
|
setHeight(Math.round((natural.height * pct) / 100));
|
|
112
|
-
}, children: [pct, "%"] }, pct))), _jsx(Button, { size: "xs", variant: "subtle", onClick: resetSize, children:
|
|
117
|
+
}, children: [pct, "%"] }, pct))), _jsx(Button, { size: "xs", variant: "subtle", onClick: resetSize, children: strings.imageCropDialog.originalSize })] }), _jsx(Text, { size: "xs", c: "dimmed", children: strings.imageCropDialog.dragHint(natural) })] }), error && (_jsx(Alert, { color: "red", variant: "light", p: "xs", children: error })), _jsxs(Group, { justify: "flex-end", gap: "xs", children: [_jsx(Button, { variant: "default", onClick: onClose, children: strings.common.cancel }), _jsx(Button, { type: "submit", loading: busy, children: strings.common.save })] })] }) }) }));
|
|
113
118
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageDialog.d.ts","sourceRoot":"","sources":["../../src/image/ImageDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ImageDialog.d.ts","sourceRoot":"","sources":["../../src/image/ImageDialog.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB;gFAC4E;IAC5E,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,qFAAqF;IACrF,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,aAAa,CAAA;KAAE,CAAC;IAC5F;4CACwC;IACxC,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAaD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,gBAAgB,+BAgQjG"}
|
|
@@ -5,11 +5,12 @@ import { IconPhotoDown } from '@tabler/icons-react';
|
|
|
5
5
|
import { ACCEPTED_IMAGE_TYPES, normalizeImageUrl, validateImageFile } from './imageUrl.js';
|
|
6
6
|
import { dataUrlBytes, dataUrlToFile, fetchImageAsDataUrl, mimeFromSrc } from './imageEdit.js';
|
|
7
7
|
import { useImageOptions } from './ImageOptionsContext.js';
|
|
8
|
+
import { useUiStrings } from '../i18n/useStrings.js';
|
|
8
9
|
function readAsDataUrl(file) {
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
10
11
|
const reader = new FileReader();
|
|
11
12
|
reader.onload = () => resolve(String(reader.result));
|
|
12
|
-
reader.onerror = () => reject(
|
|
13
|
+
reader.onerror = () => reject(new Error('READ_FAILED'));
|
|
13
14
|
reader.readAsDataURL(file);
|
|
14
15
|
});
|
|
15
16
|
}
|
|
@@ -21,6 +22,7 @@ function readAsDataUrl(file) {
|
|
|
21
22
|
* image's context menu once it has pixels of its own to edit.
|
|
22
23
|
*/
|
|
23
24
|
export function ImageDialog({ mode, opened, initial, intent, onSubmit, onClose }) {
|
|
25
|
+
const strings = useUiStrings();
|
|
24
26
|
const { allowLinked, allowEmbedded, allowCaption, maxSizeMB, onImageUpload, fetchImage } = useImageOptions();
|
|
25
27
|
const editing = mode === 'edit' && initial !== undefined;
|
|
26
28
|
const defaultSource = editing ? 'keep' : allowLinked ? 'linked' : 'embedded';
|
|
@@ -74,8 +76,8 @@ export function ImageDialog({ mode, opened, initial, intent, onSubmit, onClose }
|
|
|
74
76
|
try {
|
|
75
77
|
setFileSrc(await readAsDataUrl(next));
|
|
76
78
|
}
|
|
77
|
-
catch
|
|
78
|
-
setError(
|
|
79
|
+
catch {
|
|
80
|
+
setError(strings.imageDialog.errors.couldNotReadFile);
|
|
79
81
|
}
|
|
80
82
|
};
|
|
81
83
|
/** Downloads a linked image and switches the dialog to its embedded copy. */
|
|
@@ -87,13 +89,17 @@ export function ImageDialog({ mode, opened, initial, intent, onSubmit, onClose }
|
|
|
87
89
|
try {
|
|
88
90
|
const dataUrl = await fetchImageAsDataUrl(src, fetchImage);
|
|
89
91
|
if (dataUrlBytes(dataUrl) > maxSizeMB * 1024 * 1024)
|
|
90
|
-
throw new Error(
|
|
92
|
+
throw new Error(strings.imageDialog.errors.largerThan(maxSizeMB));
|
|
91
93
|
setSource('embedded');
|
|
92
94
|
setFile(null);
|
|
93
95
|
setFileSrc(dataUrl);
|
|
94
96
|
}
|
|
95
97
|
catch (err) {
|
|
96
|
-
|
|
98
|
+
// fetchImageAsDataUrl's own errors (network/CORS failures, unsupported
|
|
99
|
+
// type) are English (packages/react/src/image/imageEdit.ts) — shown
|
|
100
|
+
// as-is since threading locale into that pure utility isn't worth the
|
|
101
|
+
// refactor here; only the size-limit message above is ours to localize.
|
|
102
|
+
setError(err instanceof Error ? err.message : strings.imageDialog.errors.couldNotConvert);
|
|
97
103
|
}
|
|
98
104
|
finally {
|
|
99
105
|
setConverting(false);
|
|
@@ -103,7 +109,11 @@ export function ImageDialog({ mode, opened, initial, intent, onSubmit, onClose }
|
|
|
103
109
|
e.preventDefault();
|
|
104
110
|
setError(null);
|
|
105
111
|
if (!baseSrc) {
|
|
106
|
-
setError(source === 'linked'
|
|
112
|
+
setError(source === 'linked'
|
|
113
|
+
? strings.imageDialog.errors.enterUrl
|
|
114
|
+
: source === 'embedded'
|
|
115
|
+
? strings.imageDialog.errors.chooseFile
|
|
116
|
+
: strings.imageDialog.errors.noImage);
|
|
107
117
|
return;
|
|
108
118
|
}
|
|
109
119
|
try {
|
|
@@ -119,7 +129,7 @@ export function ImageDialog({ mode, opened, initial, intent, onSubmit, onClose }
|
|
|
119
129
|
}
|
|
120
130
|
if (newPixels) {
|
|
121
131
|
if (dataUrlBytes(newPixels) > maxSizeMB * 1024 * 1024) {
|
|
122
|
-
setError(
|
|
132
|
+
setError(strings.imageDialog.errors.largerThan(maxSizeMB));
|
|
123
133
|
return;
|
|
124
134
|
}
|
|
125
135
|
linkType = 'embedded';
|
|
@@ -129,7 +139,7 @@ export function ImageDialog({ mode, opened, initial, intent, onSubmit, onClose }
|
|
|
129
139
|
const upload = !file ? dataUrlToFile(newPixels, `image.${ext}`) : file;
|
|
130
140
|
src = await onImageUpload(upload);
|
|
131
141
|
if (!normalizeImageUrl(src)) {
|
|
132
|
-
setError(
|
|
142
|
+
setError(strings.imageDialog.errors.uploadUnusable);
|
|
133
143
|
return;
|
|
134
144
|
}
|
|
135
145
|
}
|
|
@@ -146,17 +156,19 @@ export function ImageDialog({ mode, opened, initial, intent, onSubmit, onClose }
|
|
|
146
156
|
});
|
|
147
157
|
}
|
|
148
158
|
catch (err) {
|
|
149
|
-
|
|
159
|
+
// fetchImageAsDataUrl's own network/CORS error message (imageEdit.ts)
|
|
160
|
+
// is shown as-is when present — see note in `convert` above.
|
|
161
|
+
setError(err instanceof Error ? err.message : strings.imageDialog.errors.couldNotSave);
|
|
150
162
|
}
|
|
151
163
|
finally {
|
|
152
164
|
setBusy(false);
|
|
153
165
|
}
|
|
154
166
|
};
|
|
155
|
-
const convertButton = (_jsx(Button, { size: "xs", variant: "light", leftSection: _jsx(IconPhotoDown, { size: 14 }), loading: converting, disabled: !baseSrc, onClick: () => convert(baseSrc), children:
|
|
156
|
-
return (_jsx(Modal, { opened: opened, onClose: onClose, title: editing ?
|
|
157
|
-
...(editing ? [{ value: 'keep', label:
|
|
158
|
-
...(allowLinked ? [{ value: 'linked', label:
|
|
159
|
-
...(allowEmbedded ? [{ value: 'embedded', label:
|
|
160
|
-
] })), source === 'keep' && (_jsxs(Stack, { gap: 6, children: [_jsx(Text, { size: "sm", c: "dimmed", lineClamp: 2, style: { wordBreak: 'break-all' }, children: initial?.linkType === 'embedded' ?
|
|
161
|
-
(convertedFromUrl ? (_jsx(Text, { size: "sm", c: "dimmed", children:
|
|
167
|
+
const convertButton = (_jsx(Button, { size: "xs", variant: "light", leftSection: _jsx(IconPhotoDown, { size: 14 }), loading: converting, disabled: !baseSrc, onClick: () => convert(baseSrc), children: strings.imageDialog.convertButton }));
|
|
168
|
+
return (_jsx(Modal, { opened: opened, onClose: onClose, title: editing ? strings.imageDialog.titleEdit : strings.imageDialog.titleInsert, centered: true, size: "sm", children: _jsx("form", { onSubmit: handleSubmit, children: _jsxs(Stack, { gap: "sm", children: [(editing || (allowLinked && allowEmbedded)) && (_jsx(SegmentedControl, { fullWidth: true, value: source, onChange: (v) => changeSource(v), data: [
|
|
169
|
+
...(editing ? [{ value: 'keep', label: strings.imageDialog.currentImage }] : []),
|
|
170
|
+
...(allowLinked ? [{ value: 'linked', label: strings.imageDialog.fromUrl }] : []),
|
|
171
|
+
...(allowEmbedded ? [{ value: 'embedded', label: strings.imageDialog.upload }] : []),
|
|
172
|
+
] })), source === 'keep' && (_jsxs(Stack, { gap: 6, children: [_jsx(Text, { size: "sm", c: "dimmed", lineClamp: 2, style: { wordBreak: 'break-all' }, children: initial?.linkType === 'embedded' ? strings.imageDialog.embeddedInDocument : strings.imageDialog.linkedFrom(initial?.src ?? '') }), initial?.linkType === 'linked' && allowEmbedded && (_jsxs(Group, { gap: "xs", children: [convertButton, _jsx(Text, { size: "xs", c: "dimmed", children: strings.imageDialog.convertHint })] }))] })), source === 'linked' && (_jsxs(_Fragment, { children: [_jsx(TextInput, { label: strings.imageDialog.imageUrlLabel, placeholder: strings.imageDialog.imageUrlPlaceholder, value: url, onChange: (e) => setUrl(e.currentTarget.value), "data-autofocus": intent !== 'caption' ? true : undefined }), allowEmbedded && (_jsx(Checkbox, { label: strings.imageDialog.embedCopyLabel, description: strings.imageDialog.embedCopyDescription, checked: embedCopy, onChange: (e) => setEmbedCopy(e.currentTarget.checked) }))] })), source === 'embedded' &&
|
|
173
|
+
(convertedFromUrl ? (_jsx(Text, { size: "sm", c: "dimmed", children: strings.imageDialog.convertedFromUrl })) : (_jsx(FileInput, { label: strings.imageDialog.fileLabel, placeholder: strings.imageDialog.filePlaceholder, accept: ACCEPTED_IMAGE_TYPES.join(','), value: file, onChange: changeFile, description: strings.imageDialog.fileDescription(maxSizeMB, !onImageUpload) }))), _jsx(TextInput, { label: strings.imageDialog.altTextLabel, description: strings.imageDialog.altTextDescription, value: altText, onChange: (e) => setAltText(e.currentTarget.value) }), allowCaption && (_jsx(TextInput, { label: strings.imageDialog.captionLabel, description: strings.imageDialog.captionDescription, value: caption, onChange: (e) => setCaption(e.currentTarget.value), "data-autofocus": intent === 'caption' ? true : undefined })), error && (_jsx(Alert, { color: "red", variant: "light", p: "xs", children: error })), _jsxs(Group, { justify: "flex-end", gap: "xs", children: [_jsx(Button, { variant: "default", onClick: onClose, children: strings.common.cancel }), _jsx(Button, { type: "submit", loading: busy, children: editing ? strings.common.save : strings.common.insert })] })] }) }) }));
|
|
162
174
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js'
|
|
|
5
5
|
export type { ImagePayload, ImageLinkType, SerializedImageNode } from './image/ImageNode.js';
|
|
6
6
|
export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fonts/index.js';
|
|
7
7
|
export type { FontOption } from './fonts/index.js';
|
|
8
|
+
export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
|
|
9
|
+
export type { Locale, Strings } from './i18n/index.js';
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { EditorRoot } from './EditorRoot.js';
|
|
|
2
2
|
export { defaultMantineTheme } from './theme/mantineTheme.js';
|
|
3
3
|
export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
|
|
4
4
|
export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fonts/index.js';
|
|
5
|
+
export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
|