From 0fcbddda8ebdbf6ec381eeed60e245e03e1739f5 Mon Sep 17 00:00:00 2001 From: Jan Klass Date: Mon, 20 Feb 2023 10:44:25 +0100 Subject: [PATCH 1/9] docs: Fix outdated link in README.md (#6263) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5f7f5cd4..31ee567de 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ The Excalidraw editor (npm package) supports: ## Excalidraw.com -The app hosted at [excalidraw.com](https://excalidraw.com) is a minimal showcase of what you can build with Excalidraw. Its [source code](https://github.com/excalidraw/excalidraw/tree/maielo/new-readme/src/excalidraw-app) is part of this repository as well, and the app features: +The app hosted at [excalidraw.com](https://excalidraw.com) is a minimal showcase of what you can build with Excalidraw. Its [source code](https://github.com/excalidraw/excalidraw/tree/master/src/excalidraw-app) is part of this repository as well, and the app features: - 📡 PWA support (works offline). - 🤼 Real-time collaboration. From 88ff32e9b389b15577f0a6fa73df92a6eda7f914 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Tue, 21 Feb 2023 12:36:43 +0530 Subject: [PATCH 2/9] fix: improve text wrapping in ellipse and alignment (#6172) * fix: improve text wrapping in ellipse * compute height when font properties updated * fix alignment * fix alignment when resizing * fix * ad padding * always compute height when redrawing bounding box and refactor * lint * fix specs * fix * redraw text bounding box when pasted or refreshed * fix * Add specs * fix * restore on font load * add comments --- src/components/App.tsx | 10 ++ src/element/newElement.ts | 10 ++ src/element/textElement.test.ts | 53 ++++++++- src/element/textElement.ts | 194 +++++++++++++++++++------------ src/element/textWysiwyg.test.tsx | 52 ++++----- src/element/textWysiwyg.tsx | 14 ++- 6 files changed, 225 insertions(+), 108 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index a48510bf9..effa604bf 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -108,6 +108,7 @@ import { textWysiwyg, transformElements, updateTextElement, + redrawTextBoundingBox, } from "../element"; import { bindOrUnbindLinearElement, @@ -264,6 +265,7 @@ import { getBoundTextElement, getContainerCenter, getContainerDims, + getContainerElement, getTextBindableContainerAtPosition, isValidTextContainer, } from "../element/textElement"; @@ -1637,6 +1639,14 @@ class App extends React.Component { } this.scene.replaceAllElements(nextElements); + + nextElements.forEach((nextElement) => { + if (isTextElement(nextElement) && isBoundToContainer(nextElement)) { + const container = getContainerElement(nextElement); + redrawTextBoundingBox(nextElement, container); + } + }); + this.history.resumeRecording(); this.setState( diff --git a/src/element/newElement.ts b/src/element/newElement.ts index 8e7b8ee8a..c7f33a462 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -290,6 +290,11 @@ export const getMaxContainerWidth = (container: ExcalidrawElement) => { return BOUND_TEXT_PADDING * 8 * 2; } return containerWidth; + } else if (container.type === "ellipse") { + // The width of the largest rectangle inscribed inside an ellipse is + // Math.round((ellipse.width / 2) * Math.sqrt(2)) which is derived from + // equation of an ellipse -https://github.com/excalidraw/excalidraw/pull/6172 + return Math.round((width / 2) * Math.sqrt(2)) - BOUND_TEXT_PADDING * 2; } return width - BOUND_TEXT_PADDING * 2; }; @@ -306,6 +311,11 @@ export const getMaxContainerHeight = (container: ExcalidrawElement) => { return BOUND_TEXT_PADDING * 8 * 2; } return height; + } else if (container.type === "ellipse") { + // The height of the largest rectangle inscribed inside an ellipse is + // Math.round((ellipse.height / 2) * Math.sqrt(2)) which is derived from + // equation of an ellipse - https://github.com/excalidraw/excalidraw/pull/6172 + return Math.round((height / 2) * Math.sqrt(2)) - BOUND_TEXT_PADDING * 2; } return height - BOUND_TEXT_PADDING * 2; }; diff --git a/src/element/textElement.test.ts b/src/element/textElement.test.ts index e1b9ff6f0..91974aca2 100644 --- a/src/element/textElement.test.ts +++ b/src/element/textElement.test.ts @@ -1,5 +1,11 @@ import { BOUND_TEXT_PADDING } from "../constants"; -import { measureText, wrapText } from "./textElement"; +import { API } from "../tests/helpers/api"; +import { + computeContainerHeightForBoundText, + getContainerCoords, + measureText, + wrapText, +} from "./textElement"; import { FontString } from "./types"; describe("Test wrapText", () => { @@ -193,4 +199,49 @@ describe("Test measureText", () => { `); }); + + describe("Test getContainerCoords", () => { + const params = { width: 200, height: 100, x: 10, y: 20 }; + it("should compute coords correctly when ellipse", () => { + const ellipse = API.createElement({ + type: "ellipse", + ...params, + }); + expect(getContainerCoords(ellipse)).toEqual({ + x: 44.2893218813452455, + y: 39.64466094067262, + }); + }); + it("should compute coords correctly when rectangle", () => { + const rectangle = API.createElement({ + type: "rectangle", + ...params, + }); + expect(getContainerCoords(rectangle)).toEqual({ + x: 10, + y: 20, + }); + }); + }); + + describe("Test computeContainerHeightForBoundText", () => { + const params = { + width: 178, + height: 194, + }; + it("should compute container height correctly for rectangle", () => { + const element = API.createElement({ + type: "rectangle", + ...params, + }); + expect(computeContainerHeightForBoundText(element, 150)).toEqual(160); + }); + it("should compute container height correctly for ellipse", () => { + const element = API.createElement({ + type: "ellipse", + ...params, + }); + expect(computeContainerHeightForBoundText(element, 150)).toEqual(212); + }); + }); }); diff --git a/src/element/textElement.ts b/src/element/textElement.ts index c726c1c3f..ed4d27629 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -44,68 +44,69 @@ export const redrawTextBoundingBox = ( container: ExcalidrawElement | null, ) => { let maxWidth = undefined; - let text = textElement.text; + + const boundTextUpdates = { + x: textElement.x, + y: textElement.y, + text: textElement.text, + width: textElement.width, + height: textElement.height, + baseline: textElement.baseline, + }; + + boundTextUpdates.text = textElement.text; + if (container) { maxWidth = getMaxContainerWidth(container); - text = wrapText( + boundTextUpdates.text = wrapText( textElement.originalText, getFontString(textElement), maxWidth, ); } - const metrics = measureText(text, getFontString(textElement), maxWidth); - let coordY = textElement.y; - let coordX = textElement.x; - // Resize container and vertically center align the text + const metrics = measureText( + boundTextUpdates.text, + getFontString(textElement), + maxWidth, + ); + + boundTextUpdates.width = metrics.width; + boundTextUpdates.height = metrics.height; + boundTextUpdates.baseline = metrics.baseline; + if (container) { - if (!isArrowElement(container)) { - const containerDims = getContainerDims(container); - let nextHeight = containerDims.height; - if (textElement.verticalAlign === VERTICAL_ALIGN.TOP) { - coordY = container.y; - } else if (textElement.verticalAlign === VERTICAL_ALIGN.BOTTOM) { - coordY = - container.y + - containerDims.height - - metrics.height - - BOUND_TEXT_PADDING; - } else { - coordY = container.y + containerDims.height / 2 - metrics.height / 2; - if (metrics.height > getMaxContainerHeight(container)) { - nextHeight = metrics.height + BOUND_TEXT_PADDING * 2; - coordY = container.y + nextHeight / 2 - metrics.height / 2; - } - } - if (textElement.textAlign === TEXT_ALIGN.LEFT) { - coordX = container.x + BOUND_TEXT_PADDING; - } else if (textElement.textAlign === TEXT_ALIGN.RIGHT) { - coordX = - container.x + - containerDims.width - - metrics.width - - BOUND_TEXT_PADDING; - } else { - coordX = container.x + containerDims.width / 2 - metrics.width / 2; - } - updateOriginalContainerCache(container.id, nextHeight); - mutateElement(container, { height: nextHeight }); - } else { + if (isArrowElement(container)) { const centerX = textElement.x + textElement.width / 2; const centerY = textElement.y + textElement.height / 2; const diffWidth = metrics.width - textElement.width; const diffHeight = metrics.height - textElement.height; - coordY = centerY - (textElement.height + diffHeight) / 2; - coordX = centerX - (textElement.width + diffWidth) / 2; + boundTextUpdates.x = centerY - (textElement.height + diffHeight) / 2; + boundTextUpdates.y = centerX - (textElement.width + diffWidth) / 2; + } else { + const containerDims = getContainerDims(container); + let maxContainerHeight = getMaxContainerHeight(container); + + let nextHeight = containerDims.height; + if (metrics.height > maxContainerHeight) { + nextHeight = computeContainerHeightForBoundText( + container, + metrics.height, + ); + mutateElement(container, { height: nextHeight }); + maxContainerHeight = getMaxContainerHeight(container); + updateOriginalContainerCache(container.id, nextHeight); + } + const updatedTextElement = { + ...textElement, + ...boundTextUpdates, + } as ExcalidrawTextElementWithContainer; + const { x, y } = computeBoundTextPosition(container, updatedTextElement); + boundTextUpdates.x = x; + boundTextUpdates.y = y; } } - mutateElement(textElement, { - width: metrics.width, - height: metrics.height, - baseline: metrics.baseline, - y: coordY, - x: coordX, - text, - }); + + mutateElement(textElement, boundTextUpdates); }; export const bindTextToShapeAfterDuplication = ( @@ -197,7 +198,11 @@ export const handleBindTextResize = ( } // increase height in case text element height exceeds if (nextHeight > maxHeight) { - containerHeight = nextHeight + getBoundTextElementOffset(textElement) * 2; + containerHeight = computeContainerHeightForBoundText( + container, + nextHeight, + ); + const diff = containerHeight - containerDims.height; // fix the y coord when resizing from ne/nw/n const updatedY = @@ -217,48 +222,57 @@ export const handleBindTextResize = ( text, width: nextWidth, height: nextHeight, - baseline: nextBaseLine, }); + if (!isArrowElement(container)) { - updateBoundTextPosition( - container, - textElement as ExcalidrawTextElementWithContainer, + mutateElement( + textElement, + computeBoundTextPosition( + container, + textElement as ExcalidrawTextElementWithContainer, + ), ); } } }; -const updateBoundTextPosition = ( +const computeBoundTextPosition = ( container: ExcalidrawElement, boundTextElement: ExcalidrawTextElementWithContainer, ) => { - const containerDims = getContainerDims(container); - const boundTextElementPadding = getBoundTextElementOffset(boundTextElement); + const containerCoords = getContainerCoords(container); + const maxContainerHeight = getMaxContainerHeight(container); + const maxContainerWidth = getMaxContainerWidth(container); + const padding = container.type === "ellipse" ? 0 : BOUND_TEXT_PADDING; + + let x; let y; if (boundTextElement.verticalAlign === VERTICAL_ALIGN.TOP) { - y = container.y + boundTextElementPadding; + y = containerCoords.y + padding; } else if (boundTextElement.verticalAlign === VERTICAL_ALIGN.BOTTOM) { y = - container.y + - containerDims.height - - boundTextElement.height - - boundTextElementPadding; + containerCoords.y + + (maxContainerHeight - boundTextElement.height + padding); } else { - y = container.y + containerDims.height / 2 - boundTextElement.height / 2; + y = + containerCoords.y + + (maxContainerHeight / 2 - boundTextElement.height / 2 + padding); } - const x = - boundTextElement.textAlign === TEXT_ALIGN.LEFT - ? container.x + boundTextElementPadding - : boundTextElement.textAlign === TEXT_ALIGN.RIGHT - ? container.x + - containerDims.width - - boundTextElement.width - - boundTextElementPadding - : container.x + containerDims.width / 2 - boundTextElement.width / 2; - - mutateElement(boundTextElement, { x, y }); + if (boundTextElement.textAlign === TEXT_ALIGN.LEFT) { + x = containerCoords.x + padding; + } else if (boundTextElement.textAlign === TEXT_ALIGN.RIGHT) { + x = + containerCoords.x + + (maxContainerWidth - boundTextElement.width + padding); + } else { + x = + containerCoords.x + + (maxContainerWidth / 2 - boundTextElement.width / 2 + padding); + } + return { x, y }; }; + // https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js export const measureText = ( text: string, @@ -621,6 +635,24 @@ export const getContainerCenter = ( return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] }; }; +export const getContainerCoords = (container: NonDeletedExcalidrawElement) => { + if (container.type === "ellipse") { + // The derivation of coordinates is explained in https://github.com/excalidraw/excalidraw/pull/6172 + const offsetX = + (container.width / 2) * (1 - Math.sqrt(2) / 2) + BOUND_TEXT_PADDING; + const offsetY = + (container.height / 2) * (1 - Math.sqrt(2) / 2) + BOUND_TEXT_PADDING; + return { + x: container.x + offsetX, + y: container.y + offsetY, + }; + } + return { + x: container.x, + y: container.y, + }; +}; + export const getTextElementAngle = (textElement: ExcalidrawTextElement) => { const container = getContainerElement(textElement); if (!container || isArrowElement(container)) { @@ -633,12 +665,13 @@ export const getBoundTextElementOffset = ( boundTextElement: ExcalidrawTextElement | null, ) => { const container = getContainerElement(boundTextElement); - if (!container) { + if (!container || !boundTextElement) { return 0; } if (isArrowElement(container)) { return BOUND_TEXT_PADDING * 8; } + return BOUND_TEXT_PADDING; }; @@ -723,3 +756,16 @@ export const isValidTextContainer = (element: ExcalidrawElement) => { isArrowElement(element) ); }; + +export const computeContainerHeightForBoundText = ( + container: NonDeletedExcalidrawElement, + boundTextElementHeight: number, +) => { + if (container.type === "ellipse") { + return Math.round((boundTextElementHeight / Math.sqrt(2)) * 2); + } + if (isArrowElement(container)) { + return boundTextElementHeight + BOUND_TEXT_PADDING * 8 * 2; + } + return boundTextElementHeight + BOUND_TEXT_PADDING * 2; +}; diff --git a/src/element/textWysiwyg.test.tsx b/src/element/textWysiwyg.test.tsx index c61d4e68b..87a6e0bf5 100644 --- a/src/element/textWysiwyg.test.tsx +++ b/src/element/textWysiwyg.test.tsx @@ -791,9 +791,7 @@ describe("textWysiwyg", () => { text = h.elements[1] as ExcalidrawTextElementWithContainer; expect(text.text).toBe("Hello \nWorld!"); expect(text.originalText).toBe("Hello World!"); - expect(text.y).toBe( - rectangle.y + rectangle.height / 2 - (APPROX_LINE_HEIGHT * 2) / 2, - ); + expect(text.y).toBe(27.5); expect(text.x).toBe(rectangle.x + BOUND_TEXT_PADDING); expect(text.height).toBe(APPROX_LINE_HEIGHT * 2); expect(text.width).toBe(rectangle.width - BOUND_TEXT_PADDING * 2); @@ -827,9 +825,7 @@ describe("textWysiwyg", () => { expect(text.text).toBe("Hello"); expect(text.originalText).toBe("Hello"); - expect(text.y).toBe( - rectangle.y + rectangle.height / 2 - APPROX_LINE_HEIGHT / 2, - ); + expect(text.y).toBe(40); expect(text.x).toBe(rectangle.x + BOUND_TEXT_PADDING); expect(text.height).toBe(APPROX_LINE_HEIGHT); expect(text.width).toBe(rectangle.width - BOUND_TEXT_PADDING * 2); @@ -1248,7 +1244,7 @@ describe("textWysiwyg", () => { expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` Array [ 15, - 20, + 25, ] `); }); @@ -1259,7 +1255,7 @@ describe("textWysiwyg", () => { expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` Array [ 94.5, - 20, + 25, ] `); }); @@ -1269,22 +1265,22 @@ describe("textWysiwyg", () => { fireEvent.click(screen.getByTitle("Align top")); expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` - Array [ - 174, - 20, - ] - `); + Array [ + 174, + 25, + ] + `); }); it("when center left", async () => { fireEvent.click(screen.getByTitle("Center vertically")); fireEvent.click(screen.getByTitle("Left")); expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` - Array [ - 15, - 25, - ] - `); + Array [ + 15, + 20, + ] + `); }); it("when center center", async () => { @@ -1292,11 +1288,11 @@ describe("textWysiwyg", () => { fireEvent.click(screen.getByTitle("Center vertically")); expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` - Array [ - -25, - 25, - ] - `); + Array [ + -25, + 20, + ] + `); }); it("when center right", async () => { @@ -1304,11 +1300,11 @@ describe("textWysiwyg", () => { fireEvent.click(screen.getByTitle("Center vertically")); expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` - Array [ - 174, - 25, - ] - `); + Array [ + 174, + 20, + ] + `); }); it("when bottom left", async () => { diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index 7b0098ce0..5536632b4 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -25,6 +25,7 @@ import { getApproxLineHeight, getBoundTextElementId, getBoundTextElementOffset, + getContainerCoords, getContainerDims, getContainerElement, getTextElementAngle, @@ -230,19 +231,22 @@ export const textWysiwyg = ({ // Start pushing text upward until a diff of 30px (padding) // is reached else { + const padding = + container.type === "ellipse" + ? 0 + : getBoundTextElementOffset(updatedTextElement); + const containerCoords = getContainerCoords(container); + // vertically center align the text if (verticalAlign === VERTICAL_ALIGN.MIDDLE) { if (!isArrowElement(container)) { coordY = - container.y + containerDims.height / 2 - textElementHeight / 2; + containerCoords.y + maxHeight / 2 - textElementHeight / 2; } } if (verticalAlign === VERTICAL_ALIGN.BOTTOM) { coordY = - container.y + - containerDims.height - - textElementHeight - - getBoundTextElementOffset(updatedTextElement); + containerCoords.y + (maxHeight - textElementHeight + padding); } } } From 5368ddef74570abd3889e7db3133038b04b99cc7 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 22 Feb 2023 16:28:12 +0530 Subject: [PATCH 3/9] fix: improve text wrapping inside rhombus and more fixes (#6265) * fix: improve text wrapping inside rhombus * Add comments * specs * fix: shift resize and multiple element regression for ellipse and rhombus * use container width for scaling font size * fix * fix multiple resize * lint * redraw on submit * redraw only newly pasted elements * no padding when center * fix tests * fix * dont add padding in rhombus when aligning * refactor * fix * move getMaxContainerHeight and getMaxContainerWidth to textElement.ts * Add specs --- src/components/App.tsx | 9 +-- src/element/newElement.ts | 46 +----------- src/element/resizeElements.ts | 26 ++++--- src/element/textElement.test.ts | 81 +++++++++++++++++++-- src/element/textElement.ts | 99 ++++++++++++++++++++------ src/element/textWysiwyg.test.tsx | 12 ++-- src/element/textWysiwyg.tsx | 13 ++-- src/tests/linearElementEditor.test.tsx | 7 +- 8 files changed, 192 insertions(+), 101 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index effa604bf..3c0ac9dc4 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1627,6 +1627,7 @@ class App extends React.Component { oldIdToDuplicatedId.set(element.id, newElement.id); return newElement; }); + bindTextToShapeAfterDuplication(newElements, elements, oldIdToDuplicatedId); const nextElements = [ ...this.scene.getElementsIncludingDeleted(), @@ -1640,10 +1641,10 @@ class App extends React.Component { this.scene.replaceAllElements(nextElements); - nextElements.forEach((nextElement) => { - if (isTextElement(nextElement) && isBoundToContainer(nextElement)) { - const container = getContainerElement(nextElement); - redrawTextBoundingBox(nextElement, container); + newElements.forEach((newElement) => { + if (isTextElement(newElement) && isBoundToContainer(newElement)) { + const container = getContainerElement(newElement); + redrawTextBoundingBox(newElement, container); } }); diff --git a/src/element/newElement.ts b/src/element/newElement.ts index c7f33a462..6024765ec 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -22,15 +22,15 @@ import { getElementAbsoluteCoords } from "."; import { adjustXYWithRotation } from "../math"; import { getResizedElementAbsoluteCoords } from "./bounds"; import { - getBoundTextElement, getBoundTextElementOffset, getContainerDims, getContainerElement, measureText, normalizeText, wrapText, + getMaxContainerWidth, } from "./textElement"; -import { BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants"; +import { VERTICAL_ALIGN } from "../constants"; import { isArrowElement } from "./typeChecks"; type ElementConstructorOpts = MarkOptional< @@ -278,48 +278,6 @@ export const refreshTextDimensions = ( return { text, ...dimensions }; }; -export const getMaxContainerWidth = (container: ExcalidrawElement) => { - const width = getContainerDims(container).width; - if (isArrowElement(container)) { - const containerWidth = width - BOUND_TEXT_PADDING * 8 * 2; - if (containerWidth <= 0) { - const boundText = getBoundTextElement(container); - if (boundText) { - return boundText.width; - } - return BOUND_TEXT_PADDING * 8 * 2; - } - return containerWidth; - } else if (container.type === "ellipse") { - // The width of the largest rectangle inscribed inside an ellipse is - // Math.round((ellipse.width / 2) * Math.sqrt(2)) which is derived from - // equation of an ellipse -https://github.com/excalidraw/excalidraw/pull/6172 - return Math.round((width / 2) * Math.sqrt(2)) - BOUND_TEXT_PADDING * 2; - } - return width - BOUND_TEXT_PADDING * 2; -}; - -export const getMaxContainerHeight = (container: ExcalidrawElement) => { - const height = getContainerDims(container).height; - if (isArrowElement(container)) { - const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2; - if (containerHeight <= 0) { - const boundText = getBoundTextElement(container); - if (boundText) { - return boundText.height; - } - return BOUND_TEXT_PADDING * 8 * 2; - } - return height; - } else if (container.type === "ellipse") { - // The height of the largest rectangle inscribed inside an ellipse is - // Math.round((ellipse.height / 2) * Math.sqrt(2)) which is derived from - // equation of an ellipse - https://github.com/excalidraw/excalidraw/pull/6172 - return Math.round((height / 2) * Math.sqrt(2)) - BOUND_TEXT_PADDING * 2; - } - return height - BOUND_TEXT_PADDING * 2; -}; - export const updateTextElement = ( textElement: ExcalidrawTextElement, { diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 605ab0c2b..fe4f8e484 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -43,12 +43,12 @@ import { getApproxMinLineWidth, getBoundTextElement, getBoundTextElementId, - getBoundTextElementOffset, getContainerElement, handleBindTextResize, measureText, + getMaxContainerHeight, + getMaxContainerWidth, } from "./textElement"; -import { getMaxContainerWidth } from "./newElement"; export const normalizeAngle = (angle: number): number => { if (angle >= 2 * Math.PI) { @@ -427,12 +427,16 @@ export const resizeSingleElement = ( }; } if (shouldMaintainAspectRatio) { - const boundTextElementPadding = - getBoundTextElementOffset(boundTextElement); + const updatedElement = { + ...element, + width: eleNewWidth, + height: eleNewHeight, + }; + const nextFont = measureFontSizeFromWH( boundTextElement, - eleNewWidth - boundTextElementPadding * 2, - eleNewHeight - boundTextElementPadding * 2, + getMaxContainerWidth(updatedElement), + getMaxContainerHeight(updatedElement), ); if (nextFont === null) { return; @@ -697,11 +701,15 @@ const resizeMultipleElements = ( const boundTextElement = getBoundTextElement(element.latest); if (boundTextElement || isTextElement(element.orig)) { - const optionalPadding = getBoundTextElementOffset(boundTextElement) * 2; + const updatedElement = { + ...element.latest, + width, + height, + }; const textMeasurements = measureFontSizeFromWH( boundTextElement ?? (element.orig as ExcalidrawTextElement), - width - optionalPadding, - height - optionalPadding, + getMaxContainerWidth(updatedElement), + getMaxContainerHeight(updatedElement), ); if (!textMeasurements) { diff --git a/src/element/textElement.test.ts b/src/element/textElement.test.ts index 91974aca2..22086095f 100644 --- a/src/element/textElement.test.ts +++ b/src/element/textElement.test.ts @@ -3,6 +3,8 @@ import { API } from "../tests/helpers/api"; import { computeContainerHeightForBoundText, getContainerCoords, + getMaxContainerWidth, + getMaxContainerHeight, measureText, wrapText, } from "./textElement"; @@ -202,24 +204,37 @@ describe("Test measureText", () => { describe("Test getContainerCoords", () => { const params = { width: 200, height: 100, x: 10, y: 20 }; + it("should compute coords correctly when ellipse", () => { - const ellipse = API.createElement({ + const element = API.createElement({ type: "ellipse", ...params, }); - expect(getContainerCoords(ellipse)).toEqual({ + expect(getContainerCoords(element)).toEqual({ x: 44.2893218813452455, y: 39.64466094067262, }); }); + it("should compute coords correctly when rectangle", () => { - const rectangle = API.createElement({ + const element = API.createElement({ type: "rectangle", ...params, }); - expect(getContainerCoords(rectangle)).toEqual({ - x: 10, - y: 20, + expect(getContainerCoords(element)).toEqual({ + x: 15, + y: 25, + }); + }); + + it("should compute coords correctly when diamond", () => { + const element = API.createElement({ + type: "diamond", + ...params, + }); + expect(getContainerCoords(element)).toEqual({ + x: 65, + y: 50, }); }); }); @@ -229,6 +244,7 @@ describe("Test measureText", () => { width: 178, height: 194, }; + it("should compute container height correctly for rectangle", () => { const element = API.createElement({ type: "rectangle", @@ -236,6 +252,7 @@ describe("Test measureText", () => { }); expect(computeContainerHeightForBoundText(element, 150)).toEqual(160); }); + it("should compute container height correctly for ellipse", () => { const element = API.createElement({ type: "ellipse", @@ -243,5 +260,57 @@ describe("Test measureText", () => { }); expect(computeContainerHeightForBoundText(element, 150)).toEqual(212); }); + + it("should compute container height correctly for diamond", () => { + const element = API.createElement({ + type: "diamond", + ...params, + }); + expect(computeContainerHeightForBoundText(element, 150)).toEqual(300); + }); + }); + + describe("Test getMaxContainerWidth", () => { + const params = { + width: 178, + height: 194, + }; + + it("should return max width when container is rectangle", () => { + const container = API.createElement({ type: "rectangle", ...params }); + expect(getMaxContainerWidth(container)).toBe(168); + }); + + it("should return max width when container is ellipse", () => { + const container = API.createElement({ type: "ellipse", ...params }); + expect(getMaxContainerWidth(container)).toBe(116); + }); + + it("should return max width when container is diamond", () => { + const container = API.createElement({ type: "diamond", ...params }); + expect(getMaxContainerWidth(container)).toBe(79); + }); + }); + + describe("Test getMaxContainerHeight", () => { + const params = { + width: 178, + height: 194, + }; + + it("should return max height when container is rectangle", () => { + const container = API.createElement({ type: "rectangle", ...params }); + expect(getMaxContainerHeight(container)).toBe(184); + }); + + it("should return max height when container is ellipse", () => { + const container = API.createElement({ type: "ellipse", ...params }); + expect(getMaxContainerHeight(container)).toBe(127); + }); + + it("should return max height when container is diamond", () => { + const container = API.createElement({ type: "diamond", ...params }); + expect(getMaxContainerHeight(container)).toBe(87); + }); }); }); diff --git a/src/element/textElement.ts b/src/element/textElement.ts index ed4d27629..b91e9f2f6 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -12,7 +12,6 @@ import { BOUND_TEXT_PADDING, TEXT_ALIGN, VERTICAL_ALIGN } from "../constants"; import { MaybeTransformHandleType } from "./transformHandles"; import Scene from "../scene/Scene"; import { isTextElement } from "."; -import { getMaxContainerHeight, getMaxContainerWidth } from "./newElement"; import { isBoundToContainer, isImageElement, @@ -244,31 +243,25 @@ const computeBoundTextPosition = ( const containerCoords = getContainerCoords(container); const maxContainerHeight = getMaxContainerHeight(container); const maxContainerWidth = getMaxContainerWidth(container); - const padding = container.type === "ellipse" ? 0 : BOUND_TEXT_PADDING; let x; let y; if (boundTextElement.verticalAlign === VERTICAL_ALIGN.TOP) { - y = containerCoords.y + padding; + y = containerCoords.y; } else if (boundTextElement.verticalAlign === VERTICAL_ALIGN.BOTTOM) { - y = - containerCoords.y + - (maxContainerHeight - boundTextElement.height + padding); + y = containerCoords.y + (maxContainerHeight - boundTextElement.height); } else { y = containerCoords.y + - (maxContainerHeight / 2 - boundTextElement.height / 2 + padding); + (maxContainerHeight / 2 - boundTextElement.height / 2); } if (boundTextElement.textAlign === TEXT_ALIGN.LEFT) { - x = containerCoords.x + padding; + x = containerCoords.x; } else if (boundTextElement.textAlign === TEXT_ALIGN.RIGHT) { - x = - containerCoords.x + - (maxContainerWidth - boundTextElement.width + padding); + x = containerCoords.x + (maxContainerWidth - boundTextElement.width); } else { x = - containerCoords.x + - (maxContainerWidth / 2 - boundTextElement.width / 2 + padding); + containerCoords.x + (maxContainerWidth / 2 - boundTextElement.width / 2); } return { x, y }; }; @@ -636,20 +629,22 @@ export const getContainerCenter = ( }; export const getContainerCoords = (container: NonDeletedExcalidrawElement) => { + let offsetX = BOUND_TEXT_PADDING; + let offsetY = BOUND_TEXT_PADDING; + if (container.type === "ellipse") { // The derivation of coordinates is explained in https://github.com/excalidraw/excalidraw/pull/6172 - const offsetX = - (container.width / 2) * (1 - Math.sqrt(2) / 2) + BOUND_TEXT_PADDING; - const offsetY = - (container.height / 2) * (1 - Math.sqrt(2) / 2) + BOUND_TEXT_PADDING; - return { - x: container.x + offsetX, - y: container.y + offsetY, - }; + offsetX += (container.width / 2) * (1 - Math.sqrt(2) / 2); + offsetY += (container.height / 2) * (1 - Math.sqrt(2) / 2); + } + // The derivation of coordinates is explained in https://github.com/excalidraw/excalidraw/pull/6265 + if (container.type === "diamond") { + offsetX += container.width / 4; + offsetY += container.height / 4; } return { - x: container.x, - y: container.y, + x: container.x + offsetX, + y: container.y + offsetY, }; }; @@ -767,5 +762,63 @@ export const computeContainerHeightForBoundText = ( if (isArrowElement(container)) { return boundTextElementHeight + BOUND_TEXT_PADDING * 8 * 2; } + if (container.type === "diamond") { + return 2 * boundTextElementHeight; + } return boundTextElementHeight + BOUND_TEXT_PADDING * 2; }; + +export const getMaxContainerWidth = (container: ExcalidrawElement) => { + const width = getContainerDims(container).width; + if (isArrowElement(container)) { + const containerWidth = width - BOUND_TEXT_PADDING * 8 * 2; + if (containerWidth <= 0) { + const boundText = getBoundTextElement(container); + if (boundText) { + return boundText.width; + } + return BOUND_TEXT_PADDING * 8 * 2; + } + return containerWidth; + } + + if (container.type === "ellipse") { + // The width of the largest rectangle inscribed inside an ellipse is + // Math.round((ellipse.width / 2) * Math.sqrt(2)) which is derived from + // equation of an ellipse -https://github.com/excalidraw/excalidraw/pull/6172 + return Math.round((width / 2) * Math.sqrt(2)) - BOUND_TEXT_PADDING * 2; + } + if (container.type === "diamond") { + // The width of the largest rectangle inscribed inside a rhombus is + // Math.round(width / 2) - https://github.com/excalidraw/excalidraw/pull/6265 + return Math.round(width / 2) - BOUND_TEXT_PADDING * 2; + } + return width - BOUND_TEXT_PADDING * 2; +}; + +export const getMaxContainerHeight = (container: ExcalidrawElement) => { + const height = getContainerDims(container).height; + if (isArrowElement(container)) { + const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2; + if (containerHeight <= 0) { + const boundText = getBoundTextElement(container); + if (boundText) { + return boundText.height; + } + return BOUND_TEXT_PADDING * 8 * 2; + } + return height; + } + if (container.type === "ellipse") { + // The height of the largest rectangle inscribed inside an ellipse is + // Math.round((ellipse.height / 2) * Math.sqrt(2)) which is derived from + // equation of an ellipse - https://github.com/excalidraw/excalidraw/pull/6172 + return Math.round((height / 2) * Math.sqrt(2)) - BOUND_TEXT_PADDING * 2; + } + if (container.type === "diamond") { + // The height of the largest rectangle inscribed inside a rhombus is + // Math.round(height / 2) - https://github.com/excalidraw/excalidraw/pull/6265 + return Math.round(height / 2) - BOUND_TEXT_PADDING * 2; + } + return height - BOUND_TEXT_PADDING * 2; +}; diff --git a/src/element/textWysiwyg.test.tsx b/src/element/textWysiwyg.test.tsx index 87a6e0bf5..fb41a3813 100644 --- a/src/element/textWysiwyg.test.tsx +++ b/src/element/textWysiwyg.test.tsx @@ -791,7 +791,7 @@ describe("textWysiwyg", () => { text = h.elements[1] as ExcalidrawTextElementWithContainer; expect(text.text).toBe("Hello \nWorld!"); expect(text.originalText).toBe("Hello World!"); - expect(text.y).toBe(27.5); + expect(text.y).toBe(57.5); expect(text.x).toBe(rectangle.x + BOUND_TEXT_PADDING); expect(text.height).toBe(APPROX_LINE_HEIGHT * 2); expect(text.width).toBe(rectangle.width - BOUND_TEXT_PADDING * 2); @@ -825,7 +825,7 @@ describe("textWysiwyg", () => { expect(text.text).toBe("Hello"); expect(text.originalText).toBe("Hello"); - expect(text.y).toBe(40); + expect(text.y).toBe(57.5); expect(text.x).toBe(rectangle.x + BOUND_TEXT_PADDING); expect(text.height).toBe(APPROX_LINE_HEIGHT); expect(text.width).toBe(rectangle.width - BOUND_TEXT_PADDING * 2); @@ -930,6 +930,8 @@ describe("textWysiwyg", () => { editor.select(); fireEvent.click(screen.getByTitle("Left")); + await new Promise((r) => setTimeout(r, 0)); + fireEvent.click(screen.getByTitle("Align bottom")); await new Promise((r) => setTimeout(r, 0)); @@ -1278,7 +1280,7 @@ describe("textWysiwyg", () => { expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` Array [ 15, - 20, + 25, ] `); }); @@ -1290,7 +1292,7 @@ describe("textWysiwyg", () => { expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` Array [ -25, - 20, + 25, ] `); }); @@ -1302,7 +1304,7 @@ describe("textWysiwyg", () => { expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` Array [ 174, - 20, + 25, ] `); }); diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index 5536632b4..d04dfb8df 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -24,14 +24,16 @@ import { mutateElement } from "./mutateElement"; import { getApproxLineHeight, getBoundTextElementId, - getBoundTextElementOffset, getContainerCoords, getContainerDims, getContainerElement, getTextElementAngle, getTextWidth, normalizeText, + redrawTextBoundingBox, wrapText, + getMaxContainerHeight, + getMaxContainerWidth, } from "./textElement"; import { actionDecreaseFontSize, @@ -39,7 +41,6 @@ import { } from "../actions/actionProperties"; import { actionZoomIn, actionZoomOut } from "../actions/actionCanvas"; import App from "../components/App"; -import { getMaxContainerHeight, getMaxContainerWidth } from "./newElement"; import { LinearElementEditor } from "./linearElementEditor"; import { parseClipboard } from "../clipboard"; @@ -231,10 +232,6 @@ export const textWysiwyg = ({ // Start pushing text upward until a diff of 30px (padding) // is reached else { - const padding = - container.type === "ellipse" - ? 0 - : getBoundTextElementOffset(updatedTextElement); const containerCoords = getContainerCoords(container); // vertically center align the text @@ -245,8 +242,7 @@ export const textWysiwyg = ({ } } if (verticalAlign === VERTICAL_ALIGN.BOTTOM) { - coordY = - containerCoords.y + (maxHeight - textElementHeight + padding); + coordY = containerCoords.y + (maxHeight - textElementHeight); } } } @@ -616,6 +612,7 @@ export const textWysiwyg = ({ ), }); } + redrawTextBoundingBox(updateElement, container); } onSubmit({ diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx index d4b1d9038..3e5ebb8cc 100644 --- a/src/tests/linearElementEditor.test.tsx +++ b/src/tests/linearElementEditor.test.tsx @@ -17,8 +17,11 @@ import { KEYS } from "../keys"; import { LinearElementEditor } from "../element/linearElementEditor"; import { queryByTestId, queryByText } from "@testing-library/react"; import { resize, rotate } from "./utils"; -import { getBoundTextElementPosition, wrapText } from "../element/textElement"; -import { getMaxContainerWidth } from "../element/newElement"; +import { + getBoundTextElementPosition, + wrapText, + getMaxContainerWidth, +} from "../element/textElement"; import * as textElementUtils from "../element/textElement"; import { ROUNDNESS } from "../constants"; From 1e816e87bf6229632d6d90f3468f03e166bb12b2 Mon Sep 17 00:00:00 2001 From: Hikaru Yoshino <57059705+osushicrusher@users.noreply.github.com> Date: Wed, 22 Feb 2023 20:10:29 +0900 Subject: [PATCH 4/9] fix: indenting via `tab` clashing with IME compositor (#6258) --- src/element/textWysiwyg.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index d04dfb8df..1ce43b4d1 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -463,7 +463,9 @@ export const textWysiwyg = ({ event.code === CODES.BRACKET_RIGHT)) ) { event.preventDefault(); - if (event.shiftKey || event.code === CODES.BRACKET_LEFT) { + if (event.isComposing) { + return; + } else if (event.shiftKey || event.code === CODES.BRACKET_LEFT) { outdent(); } else { indent(); From e4506be3e8ef07190c8fd28ebd2750ec211d04de Mon Sep 17 00:00:00 2001 From: Excalidraw Bot <77840495+excalibot@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:23:10 +0100 Subject: [PATCH 5/9] chore: Update translations from Crowdin (#6191) --- src/locales/ar-SA.json | 3 +- src/locales/bg-BG.json | 3 +- src/locales/bn-BD.json | 3 +- src/locales/ca-ES.json | 53 +++++++++++++++--------------- src/locales/cs-CZ.json | 3 +- src/locales/da-DK.json | 3 +- src/locales/de-DE.json | 3 +- src/locales/el-GR.json | 3 +- src/locales/es-ES.json | 17 +++++----- src/locales/eu-ES.json | 9 +++--- src/locales/fa-IR.json | 3 +- src/locales/fi-FI.json | 63 ++++++++++++++++++------------------ src/locales/fr-FR.json | 3 +- src/locales/gl-ES.json | 3 +- src/locales/he-IL.json | 3 +- src/locales/hi-IN.json | 3 +- src/locales/hu-HU.json | 3 +- src/locales/id-ID.json | 3 +- src/locales/it-IT.json | 3 +- src/locales/ja-JP.json | 7 ++-- src/locales/kab-KAB.json | 3 +- src/locales/kk-KZ.json | 3 +- src/locales/ko-KR.json | 3 +- src/locales/ku-TR.json | 3 +- src/locales/lt-LT.json | 3 +- src/locales/lv-LV.json | 3 +- src/locales/mr-IN.json | 3 +- src/locales/my-MM.json | 3 +- src/locales/nb-NO.json | 3 +- src/locales/nl-NL.json | 3 +- src/locales/nn-NO.json | 3 +- src/locales/oc-FR.json | 11 ++++--- src/locales/pa-IN.json | 3 +- src/locales/percentages.json | 34 +++++++++---------- src/locales/pl-PL.json | 3 +- src/locales/pt-BR.json | 23 ++++++------- src/locales/pt-PT.json | 9 +++--- src/locales/ro-RO.json | 9 +++--- src/locales/ru-RU.json | 3 +- src/locales/si-LK.json | 3 +- src/locales/sk-SK.json | 3 +- src/locales/sl-SI.json | 3 +- src/locales/sv-SE.json | 3 +- src/locales/ta-IN.json | 21 ++++++------ src/locales/tr-TR.json | 3 +- src/locales/uk-UA.json | 9 +++--- src/locales/vi-VN.json | 3 +- src/locales/zh-CN.json | 5 +-- src/locales/zh-HK.json | 3 +- src/locales/zh-TW.json | 3 +- 50 files changed, 215 insertions(+), 166 deletions(-) diff --git a/src/locales/ar-SA.json b/src/locales/ar-SA.json index 2a682ad70..6c6c0cf39 100644 --- a/src/locales/ar-SA.json +++ b/src/locales/ar-SA.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "تعذر استيراد المشهد من عنوان URL المتوفر. إما أنها مشوهة، أو لا تحتوي على بيانات Excalidraw JSON صالحة.", "resetLibrary": "هذا سوف يمسح مكتبتك. هل أنت متأكد؟", "removeItemsFromsLibrary": "حذف {{count}} عنصر (عناصر) من المكتبة؟", - "invalidEncryptionKey": "مفتاح التشفير يجب أن يكون من 22 حرفاً. التعاون المباشر معطل." + "invalidEncryptionKey": "مفتاح التشفير يجب أن يكون من 22 حرفاً. التعاون المباشر معطل.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "نوع الملف غير مدعوم.", diff --git a/src/locales/bg-BG.json b/src/locales/bg-BG.json index 7f59678c0..ba052783d 100644 --- a/src/locales/bg-BG.json +++ b/src/locales/bg-BG.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Този файлов формат не се поддържа.", diff --git a/src/locales/bn-BD.json b/src/locales/bn-BD.json index 29c35eb63..47c6f02b2 100644 --- a/src/locales/bn-BD.json +++ b/src/locales/bn-BD.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "সরবরাহ করা লিঙ্ক থেকে দৃশ্য লোড করা যায়নি৷ এটি হয় বিকৃত, অথবা বৈধ এক্সক্যালিড্র জেসন তথ্য নেই৷", "resetLibrary": "এটি আপনার সংগ্রহ পরিষ্কার করবে। আপনি কি নিশ্চিত?", "removeItemsFromsLibrary": "সংগ্রহ থেকে {{count}} বস্তু বিয়োগ করা হবে। আপনি কি নিশ্চিত?", - "invalidEncryptionKey": "অবৈধ এনক্রীপশন কী।" + "invalidEncryptionKey": "অবৈধ এনক্রীপশন কী।", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "অসমর্থিত ফাইল।", diff --git a/src/locales/ca-ES.json b/src/locales/ca-ES.json index 82415ff97..e94523b29 100644 --- a/src/locales/ca-ES.json +++ b/src/locales/ca-ES.json @@ -1,7 +1,7 @@ { "labels": { "paste": "Enganxa", - "pasteAsPlaintext": "", + "pasteAsPlaintext": "Enganxar com a text pla", "pasteCharts": "Enganxa els diagrames", "selectAll": "Selecciona-ho tot", "multiSelect": "Afegeix un element a la selecció", @@ -72,7 +72,7 @@ "layers": "Capes", "actions": "Accions", "language": "Llengua", - "liveCollaboration": "", + "liveCollaboration": "Col·laboració en directe...", "duplicateSelection": "Duplica", "untitled": "Sense títol", "name": "Nom", @@ -116,8 +116,8 @@ "label": "Enllaç" }, "lineEditor": { - "edit": "", - "exit": "" + "edit": "Editar línia", + "exit": "Sortir de l'editor de línia" }, "elementLock": { "lock": "Bloca", @@ -136,8 +136,8 @@ "buttons": { "clearReset": "Neteja el llenç", "exportJSON": "Exporta a un fitxer", - "exportImage": "", - "export": "", + "exportImage": "Exporta la imatge...", + "export": "Guardar a...", "exportToPng": "Exporta a PNG", "exportToSvg": "Exporta a SNG", "copyToClipboard": "Copia al porta-retalls", @@ -145,7 +145,7 @@ "scale": "Escala", "save": "Desa al fitxer actual", "saveAs": "Anomena i desa", - "load": "", + "load": "Obrir", "getShareableLink": "Obté l'enllaç per a compartir", "close": "Tanca", "selectLanguage": "Trieu la llengua", @@ -192,7 +192,8 @@ "invalidSceneUrl": "No s'ha pogut importar l'escena des de l'adreça URL proporcionada. Està malformada o no conté dades Excalidraw JSON vàlides.", "resetLibrary": "Això buidarà la biblioteca. N'esteu segur?", "removeItemsFromsLibrary": "Suprimir {{count}} element(s) de la biblioteca?", - "invalidEncryptionKey": "La clau d'encriptació ha de tenir 22 caràcters. La col·laboració en directe està desactivada." + "invalidEncryptionKey": "La clau d'encriptació ha de tenir 22 caràcters. La col·laboració en directe està desactivada.", + "collabOfflineWarning": "Sense connexió a internet disponible.\nEls vostres canvis no seran guardats!" }, "errors": { "unsupportedFileType": "Tipus de fitxer no suportat.", @@ -202,8 +203,8 @@ "invalidSVGString": "SVG no vàlid.", "cannotResolveCollabServer": "No ha estat possible connectar amb el servidor collab. Si us plau recarregueu la pàgina i torneu a provar.", "importLibraryError": "No s'ha pogut carregar la biblioteca", - "collabSaveFailed": "", - "collabSaveFailed_sizeExceeded": "" + "collabSaveFailed": "No s'ha pogut desar a la base de dades de fons. Si els problemes persisteixen, hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball.", + "collabSaveFailed_sizeExceeded": "No s'ha pogut desar a la base de dades de fons, sembla que el llenç és massa gran. Hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball." }, "toolBar": { "selection": "Selecció", @@ -217,10 +218,10 @@ "text": "Text", "library": "Biblioteca", "lock": "Mantenir activa l'eina seleccionada desprès de dibuixar", - "penMode": "", + "penMode": "Mode de llapis - evita tocar", "link": "Afegeix / actualitza l'enllaç per a la forma seleccionada", "eraser": "Esborrador", - "hand": "" + "hand": "Mà (eina de desplaçament)" }, "headings": { "canvasActions": "Accions del llenç", @@ -228,7 +229,7 @@ "shapes": "Formes" }, "hints": { - "canvasPanning": "", + "canvasPanning": "Per moure el llenç, manteniu premuda la roda del ratolí o la barra espaiadora mentre arrossegueu o utilitzeu l'eina manual", "linearElement": "Feu clic per a dibuixar múltiples punts; arrossegueu per a una sola línia", "freeDraw": "Feu clic i arrossegueu, deixeu anar per a finalitzar", "text": "Consell: també podeu afegir text fent doble clic en qualsevol lloc amb l'eina de selecció", @@ -239,7 +240,7 @@ "resize": "Per restringir les proporcions mentres es canvia la mida, mantenir premut el majúscul (SHIFT); per canviar la mida des del centre, mantenir premut ALT", "resizeImage": "Podeu redimensionar lliurement prement MAJÚSCULA;\nper a redimensionar des del centre, premeu ALT", "rotate": "Per restringir els angles mentre gira, mantenir premut el majúscul (SHIFT)", - "lineEditor_info": "", + "lineEditor_info": "Mantingueu premut Ctrl o Cmd i feu doble clic o premeu Ctrl o Cmd + Retorn per editar els punts", "lineEditor_pointSelected": "Premeu Suprimir per a eliminar el(s) punt(s), CtrlOrCmd+D per a duplicar-lo, o arrossegueu-lo per a moure'l", "lineEditor_nothingSelected": "Seleccioneu un punt per a editar-lo (premeu SHIFT si voleu\nselecció múltiple), o manteniu Alt i feu clic per a afegir més punts", "placeImage": "Feu clic per a col·locar la imatge o clic i arrossegar per a establir-ne la mida manualment", @@ -247,7 +248,7 @@ "bindTextToElement": "Premeu enter per a afegir-hi text", "deepBoxSelect": "Manteniu CtrlOrCmd per a selecció profunda, i per a evitar l'arrossegament", "eraserRevert": "Mantingueu premuda Alt per a revertir els elements seleccionats per a esborrar", - "firefox_clipboard_write": "" + "firefox_clipboard_write": "És probable que aquesta funció es pugui activar posant la marca \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Per canviar les marques del navegador al Firefox, visiteu la pàgina \"about:config\"." }, "canvasError": { "cannotShowPreview": "No es pot mostrar la previsualització", @@ -295,7 +296,7 @@ "blog": "Llegiu el nostre blog", "click": "clic", "deepSelect": "Selecció profunda", - "deepBoxSelect": "", + "deepBoxSelect": "Seleccioneu profundament dins del quadre i eviteu arrossegar", "curvedArrow": "Fletxa corba", "curvedLine": "Línia corba", "documentation": "Documentació", @@ -316,8 +317,8 @@ "zoomToFit": "Zoom per veure tots els elements", "zoomToSelection": "Zoom per veure la selecció", "toggleElementLock": "Blocar/desblocar la selecció", - "movePageUpDown": "", - "movePageLeftRight": "" + "movePageUpDown": "Mou la pàgina cap amunt/a baix", + "movePageLeftRight": "Mou la pàgina cap a l'esquerra/dreta" }, "clearCanvasDialog": { "title": "Neteja el llenç" @@ -399,7 +400,7 @@ "fileSavedToFilename": "S'ha desat a {filename}", "canvas": "el llenç", "selection": "la selecció", - "pasteAsSingleElement": "" + "pasteAsSingleElement": "Fer servir {{shortcut}} per enganxar com un sol element,\no enganxeu-lo en un editor de text existent" }, "colors": { "ffffff": "Blanc", @@ -450,15 +451,15 @@ }, "welcomeScreen": { "app": { - "center_heading": "", - "center_heading_plus": "", - "menuHint": "" + "center_heading": "Totes les vostres dades es guarden localment al vostre navegador.", + "center_heading_plus": "Vols anar a Excalidraw+ en comptes?", + "menuHint": "Exportar, preferències, llenguatges..." }, "defaults": { - "menuHint": "", - "center_heading": "", - "toolbarHint": "", - "helpHint": "" + "menuHint": "Exportar, preferències i més...", + "center_heading": "Diagrames. Fer. Simple.", + "toolbarHint": "Selecciona una eina i comença a dibuixar!", + "helpHint": "Dreceres i ajuda" } } } diff --git a/src/locales/cs-CZ.json b/src/locales/cs-CZ.json index fa225d2a1..718d974b2 100644 --- a/src/locales/cs-CZ.json +++ b/src/locales/cs-CZ.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/da-DK.json b/src/locales/da-DK.json index 5b41ddb1e..8e541f90d 100644 --- a/src/locales/da-DK.json +++ b/src/locales/da-DK.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/de-DE.json b/src/locales/de-DE.json index a14f52859..b389fe5a4 100644 --- a/src/locales/de-DE.json +++ b/src/locales/de-DE.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Die Szene konnte nicht von der angegebenen URL importiert werden. Sie ist entweder fehlerhaft oder enthält keine gültigen Excalidraw JSON-Daten.", "resetLibrary": "Dieses löscht deine Bibliothek. Bist du sicher?", "removeItemsFromsLibrary": "{{count}} Element(e) aus der Bibliothek löschen?", - "invalidEncryptionKey": "Verschlüsselungsschlüssel muss 22 Zeichen lang sein. Die Live-Zusammenarbeit ist deaktiviert." + "invalidEncryptionKey": "Verschlüsselungsschlüssel muss 22 Zeichen lang sein. Die Live-Zusammenarbeit ist deaktiviert.", + "collabOfflineWarning": "Keine Internetverbindung verfügbar.\nDeine Änderungen werden nicht gespeichert!" }, "errors": { "unsupportedFileType": "Nicht unterstützter Dateityp.", diff --git a/src/locales/el-GR.json b/src/locales/el-GR.json index 02534c326..d478be3e7 100644 --- a/src/locales/el-GR.json +++ b/src/locales/el-GR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Δεν ήταν δυνατή η εισαγωγή σκηνής από το URL που δώσατε. Είτε έχει λάθος μορφή, είτε δεν περιέχει έγκυρα δεδομένα JSON Excalidraw.", "resetLibrary": "Αυτό θα καθαρίσει τη βιβλιοθήκη σας. Είστε σίγουροι;", "removeItemsFromsLibrary": "Διαγραφή {{count}} αντικειμένου(ων) από τη βιβλιοθήκη;", - "invalidEncryptionKey": "Το κλειδί κρυπτογράφησης πρέπει να είναι 22 χαρακτήρες. Η ζωντανή συνεργασία είναι απενεργοποιημένη." + "invalidEncryptionKey": "Το κλειδί κρυπτογράφησης πρέπει να είναι 22 χαρακτήρες. Η ζωντανή συνεργασία είναι απενεργοποιημένη.", + "collabOfflineWarning": "Δεν υπάρχει διαθέσιμη σύνδεση στο internet.\nΟι αλλαγές σας δεν θα αποθηκευτούν!" }, "errors": { "unsupportedFileType": "Μη υποστηριζόμενος τύπος αρχείου.", diff --git a/src/locales/es-ES.json b/src/locales/es-ES.json index e70ad64e5..eceb38ae3 100644 --- a/src/locales/es-ES.json +++ b/src/locales/es-ES.json @@ -103,7 +103,7 @@ "share": "Compartir", "showStroke": "Mostrar selector de color de trazo", "showBackground": "Mostrar el selector de color de fondo", - "toggleTheme": "Alternar tema", + "toggleTheme": "Cambiar tema", "personalLib": "Biblioteca personal", "excalidrawLib": "Biblioteca Excalidraw", "decreaseFontSize": "Disminuir tamaño de letra", @@ -192,7 +192,8 @@ "invalidSceneUrl": "No se ha podido importar la escena desde la URL proporcionada. Está mal formada, o no contiene datos de Excalidraw JSON válidos.", "resetLibrary": "Esto borrará tu biblioteca. ¿Estás seguro?", "removeItemsFromsLibrary": "¿Eliminar {{count}} elemento(s) de la biblioteca?", - "invalidEncryptionKey": "La clave de cifrado debe tener 22 caracteres. La colaboración en vivo está deshabilitada." + "invalidEncryptionKey": "La clave de cifrado debe tener 22 caracteres. La colaboración en vivo está deshabilitada.", + "collabOfflineWarning": "No hay conexión a internet disponible.\n¡No se guardarán los cambios!" }, "errors": { "unsupportedFileType": "Tipo de archivo no admitido.", @@ -233,7 +234,7 @@ "freeDraw": "Haz clic y arrastra, suelta al terminar", "text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección", "text_selected": "Doble clic o pulse ENTER para editar el texto", - "text_editing": "Pulse Escape o CtrlOrCmd+ENTER para terminar de editar", + "text_editing": "Pulse Escape o Ctrl/Cmd + ENTER para terminar de editar", "linearElementMulti": "Haz clic en el último punto o presiona Escape o Enter para finalizar", "lockAngle": "Puedes restringir el ángulo manteniendo presionado el botón SHIFT", "resize": "Para mantener las proporciones mantén SHIFT presionado mientras modificas el tamaño, \nmantén presionado ALT para modificar el tamaño desde el centro", @@ -314,7 +315,7 @@ "title": "Ayuda", "view": "Vista", "zoomToFit": "Ajustar la vista para mostrar todos los elementos", - "zoomToSelection": "Zoom a la selección", + "zoomToSelection": "Ampliar selección", "toggleElementLock": "Bloquear/desbloquear selección", "movePageUpDown": "Mover página hacia arriba/abajo", "movePageLeftRight": "Mover página hacia la izquierda/derecha" @@ -326,9 +327,9 @@ "title": "Publicar biblioteca", "itemName": "Nombre del artículo", "authorName": "Nombre del autor", - "githubUsername": "Nombre de usuario de Github", + "githubUsername": "Nombre de usuario de GitHub", "twitterUsername": "Nombre de usuario de Twitter", - "libraryName": "Nombre de la librería", + "libraryName": "Nombre de la biblioteca", "libraryDesc": "Descripción de la biblioteca", "website": "Sitio Web", "placeholder": { @@ -336,7 +337,7 @@ "libraryName": "Nombre de tu biblioteca", "libraryDesc": "Descripción de su biblioteca para ayudar a la gente a entender su uso", "githubHandle": "Nombre de usuario de GitHub (opcional), así podrá editar la biblioteca una vez enviada para su revisión", - "twitterHandle": "Nombre de usuario de Twitter (opcional), así que sabemos a quién acreditar cuando se promociona en Twitter", + "twitterHandle": "Nombre de usuario de Twitter (opcional), así sabemos a quién acreditar cuando se promociona en Twitter", "website": "Enlace a su sitio web personal o en cualquier otro lugar (opcional)" }, "errors": { @@ -458,7 +459,7 @@ "menuHint": "Exportar, preferencias y más...", "center_heading": "Diagramas. Hecho. Simplemente.", "toolbarHint": "¡Elige una herramienta y empieza a dibujar!", - "helpHint": "Atajos & ayuda" + "helpHint": "Atajos y ayuda" } } } diff --git a/src/locales/eu-ES.json b/src/locales/eu-ES.json index e35e8c40c..d35baf4e4 100644 --- a/src/locales/eu-ES.json +++ b/src/locales/eu-ES.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Ezin izan da eszena inportatu emandako URLtik. Gaizki eratuta dago edo ez du baliozko Excalidraw JSON daturik.", "resetLibrary": "Honek zure liburutegia garbituko du. Ziur zaude?", "removeItemsFromsLibrary": "Liburutegitik {{count}} elementu ezabatu?", - "invalidEncryptionKey": "Enkriptazio-gakoak 22 karaktere izan behar ditu. Zuzeneko lankidetza desgaituta dago." + "invalidEncryptionKey": "Enkriptazio-gakoak 22 karaktere izan behar ditu. Zuzeneko lankidetza desgaituta dago.", + "collabOfflineWarning": "Ez dago Interneteko konexiorik.\nZure aldaketak ez dira gordeko!" }, "errors": { "unsupportedFileType": "Onartu gabeko fitxategi mota.", @@ -220,7 +221,7 @@ "penMode": "Luma modua - ukipena saihestu", "link": "Gehitu / Eguneratu esteka hautatutako forma baterako", "eraser": "Borragoma", - "hand": "" + "hand": "Eskua (panoratze tresna)" }, "headings": { "canvasActions": "Canvas ekintzak", @@ -228,7 +229,7 @@ "shapes": "Formak" }, "hints": { - "canvasPanning": "", + "canvasPanning": "Oihala mugitzeko, eutsi saguaren gurpila edo zuriune-barra arrastatzean, edo erabili esku tresna", "linearElement": "Egin klik hainbat puntu hasteko, arrastatu lerro bakarrerako", "freeDraw": "Egin klik eta arrastatu, askatu amaitutakoan", "text": "Aholkua: testua gehitu dezakezu edozein lekutan klik bikoitza eginez hautapen tresnarekin", @@ -247,7 +248,7 @@ "bindTextToElement": "Sakatu Sartu testua gehitzeko", "deepBoxSelect": "Eutsi Ctrl edo Cmd sakatuta aukeraketa sakona egiteko eta arrastatzea saihesteko", "eraserRevert": "Eduki Alt sakatuta ezabatzeko markatutako elementuak leheneratzeko", - "firefox_clipboard_write": "" + "firefox_clipboard_write": "Ezaugarri hau \"dom.events.asyncClipboard.clipboardItem\" marka \"true\" gisa ezarrita gaitu daiteke. Firefox-en arakatzailearen banderak aldatzeko, bisitatu \"about:config\" orrialdera." }, "canvasError": { "cannotShowPreview": "Ezin da oihala aurreikusi", diff --git a/src/locales/fa-IR.json b/src/locales/fa-IR.json index 817ed51dd..cc7de0dee 100644 --- a/src/locales/fa-IR.json +++ b/src/locales/fa-IR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "بوم نقاشی از آدرس ارائه شده وارد نشد. این یا نادرست است، یا حاوی داده Excalidraw JSON معتبر نیست.", "resetLibrary": "ین کار کل صفحه را پاک میکند. آیا مطمئنید?", "removeItemsFromsLibrary": "حذف {{count}} آیتم(ها) از کتابخانه?", - "invalidEncryptionKey": "کلید رمزگذاری باید 22 کاراکتر باشد. همکاری زنده غیرفعال است." + "invalidEncryptionKey": "کلید رمزگذاری باید 22 کاراکتر باشد. همکاری زنده غیرفعال است.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "نوع فایل پشتیبانی نشده.", diff --git a/src/locales/fi-FI.json b/src/locales/fi-FI.json index 2346a018c..6c357b167 100644 --- a/src/locales/fi-FI.json +++ b/src/locales/fi-FI.json @@ -1,7 +1,7 @@ { "labels": { "paste": "Liitä", - "pasteAsPlaintext": "", + "pasteAsPlaintext": "Liitä pelkkänä tekstinä", "pasteCharts": "Liitä kaaviot", "selectAll": "Valitse kaikki", "multiSelect": "Lisää kohde valintaan", @@ -72,7 +72,7 @@ "layers": "Tasot", "actions": "Toiminnot", "language": "Kieli", - "liveCollaboration": "", + "liveCollaboration": "Live Yhteistyö...", "duplicateSelection": "Monista", "untitled": "Nimetön", "name": "Nimi", @@ -116,14 +116,14 @@ "label": "Linkki" }, "lineEditor": { - "edit": "", - "exit": "" + "edit": "Muokkaa riviä", + "exit": "Poistu rivieditorista" }, "elementLock": { - "lock": "", - "unlock": "", - "lockAll": "", - "unlockAll": "" + "lock": "Lukitse", + "unlock": "Poista lukitus", + "lockAll": "Lukitse kaikki", + "unlockAll": "Poista lukitus kaikista" }, "statusPublished": "Julkaistu", "sidebarLock": "Pidä sivupalkki avoinna" @@ -136,8 +136,8 @@ "buttons": { "clearReset": "Tyhjennä piirtoalue", "exportJSON": "Vie tiedostoon", - "exportImage": "", - "export": "", + "exportImage": "Vie kuva...", + "export": "Tallenna nimellä...", "exportToPng": "Vie PNG-tiedostona", "exportToSvg": "Vie SVG-tiedostona", "copyToClipboard": "Kopioi leikepöydälle", @@ -145,7 +145,7 @@ "scale": "Koko", "save": "Tallenna nykyiseen tiedostoon", "saveAs": "Tallenna nimellä", - "load": "", + "load": "Avaa", "getShareableLink": "Hae jaettava linkki", "close": "Sulje", "selectLanguage": "Valitse kieli", @@ -192,7 +192,8 @@ "invalidSceneUrl": "Teosta ei voitu tuoda annetusta URL-osoitteesta. Tallenne on vioittunut, tai osoitteessa ei ole Excalidraw JSON-dataa.", "resetLibrary": "Tämä tyhjentää kirjastosi. Jatketaanko?", "removeItemsFromsLibrary": "Poista {{count}} kohdetta kirjastosta?", - "invalidEncryptionKey": "Salausavaimen on oltava 22 merkkiä pitkä. Live-yhteistyö ei ole käytössä." + "invalidEncryptionKey": "Salausavaimen on oltava 22 merkkiä pitkä. Live-yhteistyö ei ole käytössä.", + "collabOfflineWarning": "Internet-yhteyttä ei ole saatavilla.\nMuutoksiasi ei tallenneta!" }, "errors": { "unsupportedFileType": "Tiedostotyyppiä ei tueta.", @@ -201,9 +202,9 @@ "svgImageInsertError": "SVG- kuvaa ei voitu lisätä. Tiedoston SVG-sisältö näyttää virheelliseltä.", "invalidSVGString": "Virheellinen SVG.", "cannotResolveCollabServer": "Yhteyden muodostaminen collab-palvelimeen epäonnistui. Virkistä sivu ja yritä uudelleen.", - "importLibraryError": "", - "collabSaveFailed": "", - "collabSaveFailed_sizeExceeded": "" + "importLibraryError": "Kokoelman lataaminen epäonnistui", + "collabSaveFailed": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi.", + "collabSaveFailed_sizeExceeded": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi." }, "toolBar": { "selection": "Valinta", @@ -217,10 +218,10 @@ "text": "Teksti", "library": "Kirjasto", "lock": "Pidä valittu työkalu aktiivisena piirron jälkeen", - "penMode": "", + "penMode": "Kynätila - estä kosketus", "link": "Lisää/päivitä linkki valitulle muodolle", "eraser": "Poistotyökalu", - "hand": "" + "hand": "Käsi (panning-työkalu)" }, "headings": { "canvasActions": "Piirtoalueen toiminnot", @@ -228,7 +229,7 @@ "shapes": "Muodot" }, "hints": { - "canvasPanning": "", + "canvasPanning": "Piirtoalueen liikuttamiseksi pidä hiiren pyörää tai välilyöntiä pohjassa tai käytä käsityökalua", "linearElement": "Klikkaa piirtääksesi useampi piste, raahaa piirtääksesi yksittäinen viiva", "freeDraw": "Paina ja raahaa, päästä irti kun olet valmis", "text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla", @@ -239,7 +240,7 @@ "resize": "Voit rajoittaa mittasuhteet pitämällä SHIFT-näppäintä alaspainettuna kun muutat kokoa, pidä ALT-näppäintä alaspainettuna muuttaaksesi kokoa keskipisteen suhteen", "resizeImage": "Voit muuttaa kokoa vapaasti pitämällä SHIFTiä pohjassa, pidä ALT pohjassa muuttaaksesi kokoa keskipisteen ympäri", "rotate": "Voit rajoittaa kulman pitämällä SHIFT pohjassa pyörittäessäsi", - "lineEditor_info": "", + "lineEditor_info": "Pidä CtrlOrCmd pohjassa ja kaksoisnapsauta tai paina CtrlOrCmd + Enter muokataksesi pisteitä", "lineEditor_pointSelected": "Poista piste(et) painamalla delete, monista painamalla CtrlOrCmd+D, tai liikuta raahaamalla", "lineEditor_nothingSelected": "Valitse muokattava piste (monivalinta pitämällä SHIFT pohjassa), tai paina Alt ja klikkaa lisätäksesi uusia pisteitä", "placeImage": "Klikkaa asettaaksesi kuvan, tai klikkaa ja raahaa asettaaksesi sen koon manuaalisesti", @@ -247,7 +248,7 @@ "bindTextToElement": "Lisää tekstiä painamalla enter", "deepBoxSelect": "Käytä syvävalintaa ja estä raahaus painamalla CtrlOrCmd", "eraserRevert": "Pidä Alt alaspainettuna, kumotaksesi merkittyjen elementtien poistamisen", - "firefox_clipboard_write": "" + "firefox_clipboard_write": "Tämä ominaisuus voidaan todennäköisesti ottaa käyttöön asettamalla \"dom.events.asyncClipboard.clipboardItem\" kohta \"true\":ksi. Vaihtaaksesi selaimen kohdan Firefoxissa, käy \"about:config\" sivulla." }, "canvasError": { "cannotShowPreview": "Esikatselua ei voitu näyttää", @@ -315,9 +316,9 @@ "view": "Näkymä", "zoomToFit": "Näytä kaikki elementit", "zoomToSelection": "Näytä valinta", - "toggleElementLock": "", - "movePageUpDown": "", - "movePageLeftRight": "" + "toggleElementLock": "Lukitse / poista lukitus valinta", + "movePageUpDown": "Siirrä sivua ylös/alas", + "movePageLeftRight": "Siirrä sivua vasemmalle/oikealle" }, "clearCanvasDialog": { "title": "Pyyhi piirtoalue" @@ -399,7 +400,7 @@ "fileSavedToFilename": "Tallennettiin kohteeseen {filename}", "canvas": "piirtoalue", "selection": "valinta", - "pasteAsSingleElement": "" + "pasteAsSingleElement": "Käytä {{shortcut}} liittääksesi yhtenä elementtinä,\ntai liittääksesi olemassa olevaan tekstieditoriin" }, "colors": { "ffffff": "Valkoinen", @@ -450,15 +451,15 @@ }, "welcomeScreen": { "app": { - "center_heading": "", - "center_heading_plus": "", - "menuHint": "" + "center_heading": "Kaikki tietosi on tallennettu paikallisesti selaimellesi.", + "center_heading_plus": "Haluatko sen sijaan mennä Excalidraw+:aan?", + "menuHint": "Vie, asetukset, kielet, ..." }, "defaults": { - "menuHint": "", - "center_heading": "", - "toolbarHint": "", - "helpHint": "" + "menuHint": "Vie, asetukset ja lisää...", + "center_heading": "Kaaviot. Tehty. Yksinkertaiseksi.", + "toolbarHint": "Valitse työkalu ja aloita piirtäminen!", + "helpHint": "Pikanäppäimet & ohje" } } } diff --git a/src/locales/fr-FR.json b/src/locales/fr-FR.json index 57afcf9b3..573e3fd1a 100644 --- a/src/locales/fr-FR.json +++ b/src/locales/fr-FR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Impossible d'importer la scène depuis l'URL fournie. Elle est soit incorrecte, soit ne contient pas de données JSON Excalidraw valides.", "resetLibrary": "Cela va effacer votre bibliothèque. Êtes-vous sûr·e ?", "removeItemsFromsLibrary": "Supprimer {{count}} élément(s) de la bibliothèque ?", - "invalidEncryptionKey": "La clé de chiffrement doit comporter 22 caractères. La collaboration en direct est désactivée." + "invalidEncryptionKey": "La clé de chiffrement doit comporter 22 caractères. La collaboration en direct est désactivée.", + "collabOfflineWarning": "Aucune connexion internet disponible.\nVos modifications ne seront pas enregistrées !" }, "errors": { "unsupportedFileType": "Type de fichier non supporté.", diff --git a/src/locales/gl-ES.json b/src/locales/gl-ES.json index d7053c961..373fe4032 100644 --- a/src/locales/gl-ES.json +++ b/src/locales/gl-ES.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Non se puido importar a escena dende a URL proporcionada. Ou ben está malformada ou non contén un JSON con información válida para Excalidraw.", "resetLibrary": "Isto limpará a súa biblioteca. Está seguro?", "removeItemsFromsLibrary": "Eliminar {{count}} elemento(s) da biblioteca?", - "invalidEncryptionKey": "A clave de cifrado debe ter 22 caracteres. A colaboración en directo está desactivada." + "invalidEncryptionKey": "A clave de cifrado debe ter 22 caracteres. A colaboración en directo está desactivada.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Tipo de ficheiro non soportado.", diff --git a/src/locales/he-IL.json b/src/locales/he-IL.json index faa0013b6..9414da375 100644 --- a/src/locales/he-IL.json +++ b/src/locales/he-IL.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "ייבוא המידע מן סצינה מכתובת האינטרנט נכשלה. המידע בנוי באופן משובש או שהוא אינו קובץ JSON תקין של Excalidraw.", "resetLibrary": "פעולה זו תנקה את כל הלוח. אתה בטוח?", "removeItemsFromsLibrary": "מחיקת {{count}} פריטים(ים) מתוך הספריה?", - "invalidEncryptionKey": "מפתח ההצפנה חייב להיות בן 22 תוים. השיתוף החי מבוטל." + "invalidEncryptionKey": "מפתח ההצפנה חייב להיות בן 22 תוים. השיתוף החי מבוטל.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "סוג הקובץ אינו נתמך.", diff --git a/src/locales/hi-IN.json b/src/locales/hi-IN.json index c0aca8982..9e3a14d64 100644 --- a/src/locales/hi-IN.json +++ b/src/locales/hi-IN.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "दिये गये युआरेल से दृश्य आयात नहीं किया जा सका. यह या तो अनुचित है, या इसमें उचित Excalidraw JSON डेटा नहीं है।", "resetLibrary": "यह पूरा संग्रह रिक्त करेगा. क्या आपको यक़ीन हैं?", "removeItemsFromsLibrary": "{{count}} वस्तु(यें) संग्रह से हटायें?", - "invalidEncryptionKey": "कूटलेखन कुंजी 22 अक्षरों की होनी चाहिये, इसलिये जीवंत सहयोग अक्षम हैं" + "invalidEncryptionKey": "कूटलेखन कुंजी 22 अक्षरों की होनी चाहिये, इसलिये जीवंत सहयोग अक्षम हैं", + "collabOfflineWarning": "कोई इंटरनेट कनेक्शन उपलब्ध नहीं है।\nआपके बदलाव सहेजे नहीं जाएंगे!" }, "errors": { "unsupportedFileType": "असमर्थित फाइल प्रकार", diff --git a/src/locales/hu-HU.json b/src/locales/hu-HU.json index faaafed90..a3cbd852c 100644 --- a/src/locales/hu-HU.json +++ b/src/locales/hu-HU.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Nem sikerült importálni a jelenetet a megadott URL-ről. Rossz formátumú, vagy nem tartalmaz érvényes Excalidraw JSON-adatokat.", "resetLibrary": "Ezzel törlöd a könyvtárát. biztos vagy ebben?", "removeItemsFromsLibrary": "{{count}} elemet törölsz a könyvtárból?", - "invalidEncryptionKey": "A titkosítási kulcsnak 22 karakterből kell állnia. Az élő együttműködés le van tiltva." + "invalidEncryptionKey": "A titkosítási kulcsnak 22 karakterből kell állnia. Az élő együttműködés le van tiltva.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Nem támogatott fájltípus.", diff --git a/src/locales/id-ID.json b/src/locales/id-ID.json index 0632a87eb..cd722c6a3 100644 --- a/src/locales/id-ID.json +++ b/src/locales/id-ID.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Tidak dapat impor pemandangan dari URL. Kemungkinan URL itu rusak atau tidak berisi data JSON Excalidraw yang valid.", "resetLibrary": "Ini akan menghapus pustaka Anda. Anda yakin?", "removeItemsFromsLibrary": "Hapus {{count}} item dari pustaka?", - "invalidEncryptionKey": "Sandi enkripsi harus 22 karakter. Kolaborasi langsung dinonaktifkan." + "invalidEncryptionKey": "Sandi enkripsi harus 22 karakter. Kolaborasi langsung dinonaktifkan.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Tipe file tidak didukung.", diff --git a/src/locales/it-IT.json b/src/locales/it-IT.json index 790c63fe6..3c6cf93a8 100644 --- a/src/locales/it-IT.json +++ b/src/locales/it-IT.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Impossibile importare la scena dall'URL fornito. Potrebbe essere malformato o non contenere dati JSON Excalidraw validi.", "resetLibrary": "Questa azione cancellerà l'intera libreria. Sei sicuro?", "removeItemsFromsLibrary": "Eliminare {{count}} elementi dalla libreria?", - "invalidEncryptionKey": "La chiave di cifratura deve essere composta da 22 caratteri. La collaborazione live è disabilitata." + "invalidEncryptionKey": "La chiave di cifratura deve essere composta da 22 caratteri. La collaborazione live è disabilitata.", + "collabOfflineWarning": "Nessuna connessione internet disponibile.\nLe tue modifiche non verranno salvate!" }, "errors": { "unsupportedFileType": "Tipo di file non supportato.", diff --git a/src/locales/ja-JP.json b/src/locales/ja-JP.json index fad9b6f10..4f7a76afe 100644 --- a/src/locales/ja-JP.json +++ b/src/locales/ja-JP.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "指定された URL からシーンをインポートできませんでした。不正な形式であるか、有効な Excalidraw JSON データが含まれていません。", "resetLibrary": "ライブラリを消去します。本当によろしいですか?", "removeItemsFromsLibrary": "{{count}} 個のアイテムをライブラリから削除しますか?", - "invalidEncryptionKey": "暗号化キーは22文字でなければなりません。ライブコラボレーションは無効化されています。" + "invalidEncryptionKey": "暗号化キーは22文字でなければなりません。ライブコラボレーションは無効化されています。", + "collabOfflineWarning": "インターネットに接続されていません。\n変更は保存されません!" }, "errors": { "unsupportedFileType": "サポートされていないファイル形式です。", @@ -220,7 +221,7 @@ "penMode": "ペンモード - タッチ防止", "link": "選択した図形のリンクを追加/更新", "eraser": "消しゴム", - "hand": "" + "hand": "手 (パンニングツール)" }, "headings": { "canvasActions": "キャンバス操作", @@ -228,7 +229,7 @@ "shapes": "図形" }, "hints": { - "canvasPanning": "", + "canvasPanning": "キャンバスを移動するには、マウスホイールまたはスペースバーを押しながらドラッグするか、手ツールを使用します", "linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線", "freeDraw": "クリックしてドラッグします。離すと終了します", "text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます", diff --git a/src/locales/kab-KAB.json b/src/locales/kab-KAB.json index fdf94fa94..3603e88c7 100644 --- a/src/locales/kab-KAB.json +++ b/src/locales/kab-KAB.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Ulamek taktert n usayes seg URL i d-ittunefken. Ahat mačči d tameɣtut neɣ ur tegbir ara isefka JSON n Excalidraw.", "resetLibrary": "Ayagi ad isfeḍ tamkarḍit-inek•m. Tetḥeqqeḍ?", "removeItemsFromsLibrary": "Ad tekkseḍ {{count}} n uferdis (en) si temkarḍit?", - "invalidEncryptionKey": "Tasarut n uwgelhen isefk ad tesɛu 22 n yiekkilen. Amɛiwen srid yensa." + "invalidEncryptionKey": "Tasarut n uwgelhen isefk ad tesɛu 22 n yiekkilen. Amɛiwen srid yensa.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Anaw n ufaylu ur yettwasefrak ara.", diff --git a/src/locales/kk-KZ.json b/src/locales/kk-KZ.json index 25071aa5e..ee1e44083 100644 --- a/src/locales/kk-KZ.json +++ b/src/locales/kk-KZ.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/ko-KR.json b/src/locales/ko-KR.json index a0368663c..9c02b3ea1 100644 --- a/src/locales/ko-KR.json +++ b/src/locales/ko-KR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "제공된 URL에서 화면을 가져오는데 실패했습니다. 주소가 잘못되거나, 유효한 Excalidraw JSON 데이터를 포함하고 있지 않은 것일 수 있습니다.", "resetLibrary": "당신의 라이브러리를 초기화 합니다. 계속하시겠습니까?", "removeItemsFromsLibrary": "{{count}}개의 아이템을 라이브러리에서 삭제하시겠습니까?", - "invalidEncryptionKey": "암호화 키는 반드시 22글자여야 합니다. 실시간 협업이 비활성화됩니다." + "invalidEncryptionKey": "암호화 키는 반드시 22글자여야 합니다. 실시간 협업이 비활성화됩니다.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "지원하지 않는 파일 형식 입니다.", diff --git a/src/locales/ku-TR.json b/src/locales/ku-TR.json index 636a832af..4a8ebc95c 100644 --- a/src/locales/ku-TR.json +++ b/src/locales/ku-TR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "ناتوانێت دیمەنەکە هاوردە بکات لە URL ی دابینکراو. یان نادروستە، یان داتای \"ئێکسکالیدراو\" JSON ی دروستی تێدا نییە.", "resetLibrary": "ئەمە کتێبخانەکەت خاوێن دەکاتەوە. ئایا دڵنیایت?", "removeItemsFromsLibrary": "سڕینەوەی {{count}} ئایتم(ەکان) لە کتێبخانە؟", - "invalidEncryptionKey": "کلیلی رەمزاندن دەبێت لە 22 پیت بێت. هاوکاری ڕاستەوخۆ لە کارخراوە." + "invalidEncryptionKey": "کلیلی رەمزاندن دەبێت لە 22 پیت بێت. هاوکاری ڕاستەوخۆ لە کارخراوە.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "جۆری فایلی پشتگیری نەکراو.", diff --git a/src/locales/lt-LT.json b/src/locales/lt-LT.json index 6eb83b7ac..a8a8d6639 100644 --- a/src/locales/lt-LT.json +++ b/src/locales/lt-LT.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Nepavyko suimportuoti scenos iš pateiktos nuorodos (URL). Ji arba blogai suformatuota, arba savyje neturi teisingų Excalidraw JSON duomenų.", "resetLibrary": "Tai išvalys tavo biblioteką. Ar tikrai to nori?", "removeItemsFromsLibrary": "Ištrinti {{count}} elementą/-us iš bibliotekos?", - "invalidEncryptionKey": "Šifravimo raktas turi būti iš 22 simbolių. Redagavimas gyvai yra išjungtas." + "invalidEncryptionKey": "Šifravimo raktas turi būti iš 22 simbolių. Redagavimas gyvai yra išjungtas.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Nepalaikomas failo tipas.", diff --git a/src/locales/lv-LV.json b/src/locales/lv-LV.json index 7341fd3fd..ea3180149 100644 --- a/src/locales/lv-LV.json +++ b/src/locales/lv-LV.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Nevarēja importēt ainu no norādītā URL. Vai nu tas ir nederīgs, vai nesatur derīgus Excalidraw JSON datus.", "resetLibrary": "Šī funkcija iztukšos bibliotēku. Vai turpināt?", "removeItemsFromsLibrary": "Vai izņemt {{count}} vienumu(s) no bibliotēkas?", - "invalidEncryptionKey": "Šifrēšanas atslēgai jābūt 22 simbolus garai. Tiešsaistes sadarbība ir izslēgta." + "invalidEncryptionKey": "Šifrēšanas atslēgai jābūt 22 simbolus garai. Tiešsaistes sadarbība ir izslēgta.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Neatbalstīts datnes veids.", diff --git a/src/locales/mr-IN.json b/src/locales/mr-IN.json index 6e198493d..e67fc3159 100644 --- a/src/locales/mr-IN.json +++ b/src/locales/mr-IN.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "दिलेल्या यू-आर-एल पासून दृश्य आणू शकलो नाही. तो एकतर बरोबार नाही आहे किंवा त्यात वैध एक्सकेलीड्रॉ जेसन डेटा नाही.", "resetLibrary": "पटल स्वच्छ होणार, तुम्हाला खात्री आहे का?", "removeItemsFromsLibrary": "संग्रहातून {{count}} तत्व (एक किव्हा अनेक) काढू?", - "invalidEncryptionKey": "कूटबद्धन कुंजी 22 अक्षरांची असणे आवश्यक आहे. थेट सहयोग अक्षम केले आहे." + "invalidEncryptionKey": "कूटबद्धन कुंजी 22 अक्षरांची असणे आवश्यक आहे. थेट सहयोग अक्षम केले आहे.", + "collabOfflineWarning": "इंटरनेट कनेक्शन उपलब्ध नाही.\nतुमचे बदल जतन केले जाणार नाहीत!" }, "errors": { "unsupportedFileType": "असमर्थित फाइल प्रकार.", diff --git a/src/locales/my-MM.json b/src/locales/my-MM.json index 8b5e6859f..e479d3702 100644 --- a/src/locales/my-MM.json +++ b/src/locales/my-MM.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/nb-NO.json b/src/locales/nb-NO.json index f8ac1e307..653779a56 100644 --- a/src/locales/nb-NO.json +++ b/src/locales/nb-NO.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Kunne ikke importere scene fra den oppgitte URL-en. Den er enten ødelagt, eller inneholder ikke gyldig Excalidraw JSON-data.", "resetLibrary": "Dette vil tømme biblioteket ditt. Er du sikker?", "removeItemsFromsLibrary": "Slett {{count}} element(er) fra biblioteket?", - "invalidEncryptionKey": "Krypteringsnøkkel må ha 22 tegn. Live-samarbeid er deaktivert." + "invalidEncryptionKey": "Krypteringsnøkkel må ha 22 tegn. Live-samarbeid er deaktivert.", + "collabOfflineWarning": "Ingen Internett-tilkobling tilgjengelig.\nEndringer dine vil ikke bli lagret!" }, "errors": { "unsupportedFileType": "Filtypen støttes ikke.", diff --git a/src/locales/nl-NL.json b/src/locales/nl-NL.json index 9152d3bbb..313c8dc47 100644 --- a/src/locales/nl-NL.json +++ b/src/locales/nl-NL.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Kan scène niet importeren vanuit de opgegeven URL. Het is onjuist of bevat geen geldige Excalidraw JSON-gegevens.", "resetLibrary": "Dit zal je bibliotheek wissen. Weet je het zeker?", "removeItemsFromsLibrary": "Verwijder {{count}} item(s) uit bibliotheek?", - "invalidEncryptionKey": "Encryptiesleutel moet 22 tekens zijn. Live samenwerking is uitgeschakeld." + "invalidEncryptionKey": "Encryptiesleutel moet 22 tekens zijn. Live samenwerking is uitgeschakeld.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Niet-ondersteund bestandstype.", diff --git a/src/locales/nn-NO.json b/src/locales/nn-NO.json index fa740d258..0311cc2fe 100644 --- a/src/locales/nn-NO.json +++ b/src/locales/nn-NO.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Kunne ikkje hente noko scene frå den URL-en. Ho er anten øydelagd eller inneheld ikkje gyldig Excalidraw JSON-data.", "resetLibrary": "Dette vil fjerne alt innhald frå biblioteket. Er du sikker?", "removeItemsFromsLibrary": "Slette {{count}} element frå biblioteket?", - "invalidEncryptionKey": "Krypteringsnøkkelen må ha 22 teikn. Sanntidssamarbeid er deaktivert." + "invalidEncryptionKey": "Krypteringsnøkkelen må ha 22 teikn. Sanntidssamarbeid er deaktivert.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Filtypen er ikkje støtta.", diff --git a/src/locales/oc-FR.json b/src/locales/oc-FR.json index c1d3ab341..51a5b9018 100644 --- a/src/locales/oc-FR.json +++ b/src/locales/oc-FR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Importacion impossibla de la scèna a partir de l’URL provesida. Es siá mal formatada o siá conten pas cap de donada JSON Excalidraw valida.", "resetLibrary": "Aquò suprimirà vòstra bibliotèca. O volètz vertadièrament ?", "removeItemsFromsLibrary": "Suprimir {{count}} element(s) de la bibliotèca ?", - "invalidEncryptionKey": "La clau de chiframent deu conténer 22 caractèrs. La collaboracion en dirèct es desactivada." + "invalidEncryptionKey": "La clau de chiframent deu conténer 22 caractèrs. La collaboracion en dirèct es desactivada.", + "collabOfflineWarning": "Cap de connexion pas disponibla.\nVòstras modificacions seràn pas salvadas !" }, "errors": { "unsupportedFileType": "Tipe de fichièr pas pres en carga.", @@ -220,7 +221,7 @@ "penMode": "Mòde estilo - empachar lo contact", "link": "Apondre/Actualizar lo ligam per una fòrma seleccionada", "eraser": "Goma", - "hand": "" + "hand": "Man (aisina de desplaçament de la vista)" }, "headings": { "canvasActions": "Accions del canabàs", @@ -239,7 +240,7 @@ "resize": "Podètz servar las proporcions en mantenent la tòca MAJ pendent lo redimensionament,\nmantenètz la tòca ALT per redimensionar a partir del centre", "resizeImage": "Podètz retalhar liurament en quichant CTRL,\nquichatz ALT per retalhar a partir del centre", "rotate": "Podètz restrénger los angles en mantenent MAJ pendent la rotacion", - "lineEditor_info": "", + "lineEditor_info": "Tenètz quichat Ctrl o Cmd e doble clic o quichatz Ctrl o Cmd + Entrada per modificar los ponches", "lineEditor_pointSelected": "Quichar Suprimir per tirar lo(s) punt(s),\nCtrlOCmd+D per duplicar, o lisatz per desplaçar", "lineEditor_nothingSelected": "Seleccionar un punt d’editar (manténer Maj. per ne seleccionar mantun),\no manténer Alt e clicar per n’apondre de novèls", "placeImage": "Clicatz per plaçar l’imatge, o clicatz e lisatz per definir sa talha manualament", @@ -316,8 +317,8 @@ "zoomToFit": "Zoomar per veire totes los elements", "zoomToSelection": "Zoomar la seleccion", "toggleElementLock": "Verrolhar/Desverrolhar la seleccion", - "movePageUpDown": "", - "movePageLeftRight": "" + "movePageUpDown": "Desplaçar la pagina ennaut/enbàs", + "movePageLeftRight": "Desplaçar la pagina a esquèrra/drecha" }, "clearCanvasDialog": { "title": "Escafar canabàs" diff --git a/src/locales/pa-IN.json b/src/locales/pa-IN.json index d5055a917..3bf61d83a 100644 --- a/src/locales/pa-IN.json +++ b/src/locales/pa-IN.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "ਦਿੱਤੀ ਗਈ URL 'ਚੋਂ ਦ੍ਰਿਸ਼ ਨੂੰ ਆਯਾਤ ਨਹੀਂ ਕਰ ਸਕੇ। ਇਹ ਜਾਂ ਤਾਂ ਖਰਾਬ ਹੈ, ਜਾਂ ਇਸ ਵਿੱਚ ਜਾਇਜ਼ Excalidraw JSON ਡਾਟਾ ਸ਼ਾਮਲ ਨਹੀਂ ਹੈ।", "resetLibrary": "ਇਹ ਤੁਹਾਡੀ ਲਾਇਬ੍ਰੇਰੀ ਨੂੰ ਸਾਫ ਕਰ ਦੇਵੇਗਾ। ਕੀ ਤੁਸੀਂ ਪੱਕਾ ਇੰਝ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?", "removeItemsFromsLibrary": "ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚੋਂ {{count}} ਚੀਜ਼(-ਜ਼ਾਂ) ਮਿਟਾਉਣੀਆਂ ਹਨ?", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/percentages.json b/src/locales/percentages.json index 997a62077..29a0c1f2b 100644 --- a/src/locales/percentages.json +++ b/src/locales/percentages.json @@ -1,26 +1,26 @@ { "ar-SA": 92, "bg-BG": 54, - "bn-BD": 60, - "ca-ES": 93, - "cs-CZ": 75, - "da-DK": 33, + "bn-BD": 59, + "ca-ES": 100, + "cs-CZ": 74, + "da-DK": 32, "de-DE": 100, "el-GR": 99, "en": 100, "es-ES": 100, - "eu-ES": 99, + "eu-ES": 100, "fa-IR": 95, - "fi-FI": 92, + "fi-FI": 100, "fr-FR": 100, - "gl-ES": 100, + "gl-ES": 99, "he-IL": 89, "hi-IN": 71, - "hu-HU": 89, + "hu-HU": 88, "id-ID": 99, "it-IT": 100, - "ja-JP": 99, - "kab-KAB": 94, + "ja-JP": 100, + "kab-KAB": 93, "kk-KZ": 20, "ko-KR": 98, "ku-TR": 95, @@ -31,22 +31,22 @@ "nb-NO": 100, "nl-NL": 90, "nn-NO": 89, - "oc-FR": 97, - "pa-IN": 83, + "oc-FR": 98, + "pa-IN": 82, "pl-PL": 84, - "pt-BR": 97, - "pt-PT": 99, - "ro-RO": 99, + "pt-BR": 100, + "pt-PT": 100, + "ro-RO": 100, "ru-RU": 100, "si-LK": 8, "sk-SK": 100, "sl-SI": 100, "sv-SE": 100, - "ta-IN": 92, + "ta-IN": 94, "tr-TR": 97, "uk-UA": 96, "vi-VN": 20, "zh-CN": 100, - "zh-HK": 26, + "zh-HK": 25, "zh-TW": 100 } diff --git a/src/locales/pl-PL.json b/src/locales/pl-PL.json index 04cc86898..89a52b493 100644 --- a/src/locales/pl-PL.json +++ b/src/locales/pl-PL.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Nie udało się zaimportować sceny z podanego adresu URL. Jest ona wadliwa lub nie zawiera poprawnych danych Excalidraw w formacie JSON.", "resetLibrary": "To wyczyści twoją bibliotekę. Jesteś pewien?", "removeItemsFromsLibrary": "Usunąć {{count}} element(ów) z biblioteki?", - "invalidEncryptionKey": "Klucz szyfrowania musi składać się z 22 znaków. Współpraca na żywo jest wyłączona." + "invalidEncryptionKey": "Klucz szyfrowania musi składać się z 22 znaków. Współpraca na żywo jest wyłączona.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Nieobsługiwany typ pliku.", diff --git a/src/locales/pt-BR.json b/src/locales/pt-BR.json index dd3362137..cb7f045de 100644 --- a/src/locales/pt-BR.json +++ b/src/locales/pt-BR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Não foi possível importar a cena da URL fornecida. Ela está incompleta ou não contém dados JSON válidos do Excalidraw.", "resetLibrary": "Isto limpará a sua biblioteca. Você tem certeza?", "removeItemsFromsLibrary": "Excluir {{count}} item(ns) da biblioteca?", - "invalidEncryptionKey": "A chave de encriptação deve ter 22 caracteres. A colaboração ao vivo está desabilitada." + "invalidEncryptionKey": "A chave de encriptação deve ter 22 caracteres. A colaboração ao vivo está desabilitada.", + "collabOfflineWarning": "Sem conexão com a internet disponível.\nSuas alterações não serão salvas!" }, "errors": { "unsupportedFileType": "Tipo de arquivo não suportado.", @@ -220,7 +221,7 @@ "penMode": "Modo caneta — impede o toque", "link": "Adicionar/Atualizar link para uma forma selecionada", "eraser": "Borracha", - "hand": "" + "hand": "Mão (ferramenta de rolagem)" }, "headings": { "canvasActions": "Ações da tela", @@ -228,7 +229,7 @@ "shapes": "Formas" }, "hints": { - "canvasPanning": "", + "canvasPanning": "Para mover a tela, segure a roda do mouse ou a barra de espaço enquanto arrasta ou use a ferramenta de mão", "linearElement": "Clique para iniciar vários pontos, arraste para uma única linha", "freeDraw": "Toque e arraste, solte quando terminar", "text": "Dica: você também pode adicionar texto clicando duas vezes em qualquer lugar com a ferramenta de seleção", @@ -247,7 +248,7 @@ "bindTextToElement": "Pressione Enter para adicionar o texto", "deepBoxSelect": "Segure Ctrl/Cmd para seleção profunda e para evitar arrastar", "eraserRevert": "Segure a tecla Alt para inverter os elementos marcados para exclusão", - "firefox_clipboard_write": "" + "firefox_clipboard_write": "Esse recurso pode ser ativado configurando a opção \"dom.events.asyncClipboard.clipboardItem\" como \"true\". Para alterar os sinalizadores do navegador no Firefox, visite a página \"about:config\"." }, "canvasError": { "cannotShowPreview": "Não é possível mostrar pré-visualização", @@ -450,15 +451,15 @@ }, "welcomeScreen": { "app": { - "center_heading": "", - "center_heading_plus": "", - "menuHint": "" + "center_heading": "Todos os dados são salvos localmente no seu navegador.", + "center_heading_plus": "Você queria ir para o Excalidraw+ em vez disso?", + "menuHint": "Exportar, preferências, idiomas..." }, "defaults": { - "menuHint": "", - "center_heading": "", - "toolbarHint": "", - "helpHint": "" + "menuHint": "Exportar, preferências e mais...", + "center_heading": "Diagramas, Feito. Simples.", + "toolbarHint": "Escolha uma ferramenta e comece a desenhar!", + "helpHint": "Atalhos e ajuda" } } } diff --git a/src/locales/pt-PT.json b/src/locales/pt-PT.json index d72c7b98f..f713b22d3 100644 --- a/src/locales/pt-PT.json +++ b/src/locales/pt-PT.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Não foi possível importar a cena a partir do URL fornecido. Ou está mal formado ou não contém dados JSON do Excalidraw válidos.", "resetLibrary": "Isto irá limpar a sua biblioteca. Tem a certeza?", "removeItemsFromsLibrary": "Apagar {{count}} item(ns) da biblioteca?", - "invalidEncryptionKey": "Chave de encriptação deve ter 22 caracteres. A colaboração ao vivo está desativada." + "invalidEncryptionKey": "Chave de encriptação deve ter 22 caracteres. A colaboração ao vivo está desativada.", + "collabOfflineWarning": "Sem ligação à internet disponível.\nAs suas alterações não serão salvas!" }, "errors": { "unsupportedFileType": "Tipo de ficheiro não suportado.", @@ -220,7 +221,7 @@ "penMode": "Modo caneta - impedir toque", "link": "Acrescentar/ Adicionar ligação para uma forma seleccionada", "eraser": "Borracha", - "hand": "" + "hand": "Mão (ferramenta de movimento da tela)" }, "headings": { "canvasActions": "Ações da área de desenho", @@ -228,7 +229,7 @@ "shapes": "Formas" }, "hints": { - "canvasPanning": "", + "canvasPanning": "Para mover a tela, carregue na roda do rato ou na barra de espaço enquanto arrasta, ou use a ferramenta da mão", "linearElement": "Clique para iniciar vários pontos, arraste para uma única linha", "freeDraw": "Clique e arraste, large quando terminar", "text": "Dica: também pode adicionar texto clicando duas vezes em qualquer lugar com a ferramenta de seleção", @@ -247,7 +248,7 @@ "bindTextToElement": "Carregue Enter para acrescentar texto", "deepBoxSelect": "Mantenha a tecla CtrlOrCmd carregada para selecção profunda, impedindo o arrastamento", "eraserRevert": "Carregue também em Alt para reverter os elementos marcados para serem apagados", - "firefox_clipboard_write": "" + "firefox_clipboard_write": "Esta função pode provavelmente ser ativada definindo a opção \"dom.events.asyncClipboard.clipboardItem\" como \"true\". Para alterar os sinalizadores do navegador no Firefox, visite a página \"about:config\"." }, "canvasError": { "cannotShowPreview": "Não é possível mostrar uma pré-visualização", diff --git a/src/locales/ro-RO.json b/src/locales/ro-RO.json index ff76eea60..8eda2385b 100644 --- a/src/locales/ro-RO.json +++ b/src/locales/ro-RO.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Scena nu a putut fi importată din URL-ul furnizat. Este fie incorect formată, fie nu conține date JSON Excalidraw valide.", "resetLibrary": "Această opțiune va elimina conținutul din bibliotecă. Confirmi?", "removeItemsFromsLibrary": "Ștergi {{count}} element(e) din bibliotecă?", - "invalidEncryptionKey": "Cheia de criptare trebuie să aibă 22 de caractere. Colaborarea în direct este dezactivată." + "invalidEncryptionKey": "Cheia de criptare trebuie să aibă 22 de caractere. Colaborarea în direct este dezactivată.", + "collabOfflineWarning": "Nu este disponibilă nicio conexiune la internet.\nModificările nu vor fi salvate!" }, "errors": { "unsupportedFileType": "Tip de fișier neacceptat.", @@ -220,7 +221,7 @@ "penMode": "Mod stilou – împiedică atingerea", "link": "Adăugare/actualizare URL pentru forma selectată", "eraser": "Radieră", - "hand": "" + "hand": "Mână (instrument de panoramare)" }, "headings": { "canvasActions": "Acțiuni pentru pânză", @@ -228,7 +229,7 @@ "shapes": "Forme" }, "hints": { - "canvasPanning": "", + "canvasPanning": "Pentru a muta pânză, ține apăsată rotița mausului sau bara de spațiu sau folosește instrumentul în formă de mână", "linearElement": "Dă clic pentru a crea mai multe puncte, glisează pentru a forma o singură linie", "freeDraw": "Dă clic pe pânză și glisează cursorul, apoi eliberează-l când ai terminat", "text": "Sfat: poți adăuga text și dând dublu clic oriunde cu instrumentul de selecție", @@ -247,7 +248,7 @@ "bindTextToElement": "Apasă tasta Enter pentru a adăuga text", "deepBoxSelect": "Ține apăsată tasta Ctrl sau Cmd pentru a efectua selectarea de adâncime și pentru a preveni glisarea", "eraserRevert": "Ține apăsată tasta Alt pentru a anula elementele marcate pentru ștergere", - "firefox_clipboard_write": "" + "firefox_clipboard_write": "Această caracteristică poate fi probabil activată prin setarea preferinței „dom.events.asyncClipboard.clipboardItem” ca „true”. Pentru a schimba preferințele navigatorului în Firefox, accesează pagina „about:config”." }, "canvasError": { "cannotShowPreview": "Nu se poate afișa previzualizarea", diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json index ce785dc83..cd0609505 100644 --- a/src/locales/ru-RU.json +++ b/src/locales/ru-RU.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Невозможно импортировать сцену с предоставленного URL. Неверный формат, или не содержит верных Excalidraw JSON данных.", "resetLibrary": "Это очистит вашу библиотеку. Вы уверены?", "removeItemsFromsLibrary": "Удалить {{count}} объект(ов) из библиотеки?", - "invalidEncryptionKey": "Ключ шифрования должен состоять из 22 символов. Одновременное редактирование отключено." + "invalidEncryptionKey": "Ключ шифрования должен состоять из 22 символов. Одновременное редактирование отключено.", + "collabOfflineWarning": "Отсутствует интернет-соединение.\nВаши изменения не будут сохранены!" }, "errors": { "unsupportedFileType": "Неподдерживаемый тип файла.", diff --git a/src/locales/si-LK.json b/src/locales/si-LK.json index dd458b31a..ee6ad46f7 100644 --- a/src/locales/si-LK.json +++ b/src/locales/si-LK.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/sk-SK.json b/src/locales/sk-SK.json index 783de30af..2b78b4539 100644 --- a/src/locales/sk-SK.json +++ b/src/locales/sk-SK.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Nepodarilo sa načítať scénu z poskytnutej URL. Je nevalidná alebo neobsahuje žiadne validné Excalidraw JSON dáta.", "resetLibrary": "Týmto vyprázdnite vašu knižnicu. Ste si istý?", "removeItemsFromsLibrary": "Odstrániť {{count}} položiek z knižnice?", - "invalidEncryptionKey": "Šifrovací kľúč musí mať 22 znakov. Živá spolupráca je vypnutá." + "invalidEncryptionKey": "Šifrovací kľúč musí mať 22 znakov. Živá spolupráca je vypnutá.", + "collabOfflineWarning": "Internetové pripojenie nie je dostupné.\nVaše zmeny nebudú uložené!" }, "errors": { "unsupportedFileType": "Nepodporovaný typ súboru.", diff --git a/src/locales/sl-SI.json b/src/locales/sl-SI.json index 54274932f..01c8ba4ca 100644 --- a/src/locales/sl-SI.json +++ b/src/locales/sl-SI.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "S priloženega URL-ja ni bilo mogoče uvoziti scene. Je napačno oblikovana ali pa ne vsebuje veljavnih podatkov Excalidraw JSON.", "resetLibrary": "To bo počistilo vašo knjižnico. Ali ste prepričani?", "removeItemsFromsLibrary": "Izbriši elemente ({{count}}) iz knjižnice?", - "invalidEncryptionKey": "Ključ za šifriranje mora vsebovati 22 znakov. Sodelovanje v živo je onemogočeno." + "invalidEncryptionKey": "Ključ za šifriranje mora vsebovati 22 znakov. Sodelovanje v živo je onemogočeno.", + "collabOfflineWarning": "Internetna povezava ni na voljo.\nVaše spremembe ne bodo shranjene!" }, "errors": { "unsupportedFileType": "Nepodprt tip datoteke.", diff --git a/src/locales/sv-SE.json b/src/locales/sv-SE.json index 30638b9e4..3cf3b0cbd 100644 --- a/src/locales/sv-SE.json +++ b/src/locales/sv-SE.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Det gick inte att importera skiss från den angivna webbadressen. Antingen har den fel format, eller så innehåller den ingen giltig Excalidraw JSON data.", "resetLibrary": "Detta kommer att rensa ditt bibliotek. Är du säker?", "removeItemsFromsLibrary": "Ta bort {{count}} objekt från biblioteket?", - "invalidEncryptionKey": "Krypteringsnyckeln måste vara 22 tecken. Livesamarbetet är inaktiverat." + "invalidEncryptionKey": "Krypteringsnyckeln måste vara 22 tecken. Livesamarbetet är inaktiverat.", + "collabOfflineWarning": "Ingen internetanslutning tillgänglig.\nDina ändringar kommer inte att sparas!" }, "errors": { "unsupportedFileType": "Filtypen stöds inte.", diff --git a/src/locales/ta-IN.json b/src/locales/ta-IN.json index 2ff76b058..493dfa560 100644 --- a/src/locales/ta-IN.json +++ b/src/locales/ta-IN.json @@ -1,7 +1,7 @@ { "labels": { "paste": "ஒட்டு", - "pasteAsPlaintext": "", + "pasteAsPlaintext": "அலங்காரமின்றி ஒட்டு", "pasteCharts": "விளக்கப்படங்களை ஒட்டு", "selectAll": "எல்லாம் தேர்ந்தெடு", "multiSelect": "உறுப்பைத் தெரிவில் சேர்", @@ -54,7 +54,7 @@ "veryLarge": "மிகப் பெரிய", "solid": "திடமான", "hachure": "மலைக்குறிக்கோடு", - "crossHatch": "", + "crossHatch": "குறுக்குகதவு", "thin": "மெல்லிய", "bold": "பட்டை", "left": "இடது", @@ -72,7 +72,7 @@ "layers": "அடுக்குகள்", "actions": "செயல்கள்", "language": "மொழி", - "liveCollaboration": "", + "liveCollaboration": "நேரடி கூட்டுப்பணி...", "duplicateSelection": "நகலாக்கு", "untitled": "தலைப்பற்றது", "name": "பெயர்", @@ -116,7 +116,7 @@ "label": "தொடுப்பு" }, "lineEditor": { - "edit": "", + "edit": "தொடுப்பைத் திருத்து", "exit": "" }, "elementLock": { @@ -137,7 +137,7 @@ "clearReset": "கித்தானை அகரமாக்கு", "exportJSON": "கோப்புக்கு ஏற்றுமதிசெய்", "exportImage": "", - "export": "", + "export": "இதில் சேமி...", "exportToPng": "PNGக்கு ஏற்றுமதிசெய்", "exportToSvg": "SVGக்கு ஏற்றுமதிசெய்", "copyToClipboard": "நகலகத்திற்கு நகலெடு", @@ -145,7 +145,7 @@ "scale": "அளவு", "save": "தற்போதைய கோப்புக்குச் சேமி", "saveAs": "இப்படி சேமி", - "load": "", + "load": "திற", "getShareableLink": "பகிரக்கூடிய தொடுப்பைப் பெறு", "close": "மூடு", "selectLanguage": "மொழியைத் தேர்ந்தெடு", @@ -192,7 +192,8 @@ "invalidSceneUrl": "வழங்கப்பட்ட உரலியிலிருந்து காட்சியை இறக்கவியலா. இது தவறான வடிவத்தில் உள்ளது, அ செல்லத்தக்க எக்ஸ்கேலிட்ரா JSON தரவைக் கொண்டில்லை.", "resetLibrary": "இது உங்கள் நுலகத்தைத் துடைக்கும். நீங்கள் உறுதியா?", "removeItemsFromsLibrary": "{{count}} உருப்படி(கள்)-ஐ உம் நூலகத்திலிருந்து அழிக்கவா?", - "invalidEncryptionKey": "மறையாக்க விசை 22 வரியுருக்கள் கொண்டிருக்கவேண்டும். நேரடி கூட்டுப்பணி முடக்கப்பட்டது." + "invalidEncryptionKey": "மறையாக்க விசை 22 வரியுருக்கள் கொண்டிருக்கவேண்டும். நேரடி கூட்டுப்பணி முடக்கப்பட்டது.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "ஆதரிக்கப்படா கோப்பு வகை.", @@ -456,9 +457,9 @@ }, "defaults": { "menuHint": "", - "center_heading": "", - "toolbarHint": "", - "helpHint": "" + "center_heading": "எளிமையாக வரைபடங்கள் உருவாக்க!", + "toolbarHint": "கருவியைத் தேர்ந்தெடு & வரை!", + "helpHint": "குறுக்குவழிகள் & உதவி" } } } diff --git a/src/locales/tr-TR.json b/src/locales/tr-TR.json index 00260919c..a05de9a54 100644 --- a/src/locales/tr-TR.json +++ b/src/locales/tr-TR.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "Verilen bağlantıdan çalışma alanı yüklenemedi. Dosya bozuk olabilir veya geçerli bir Excalidraw JSON verisi bulundurmuyor olabilir.", "resetLibrary": "Bu işlem kütüphanenizi sıfırlayacak. Emin misiniz?", "removeItemsFromsLibrary": "{{count}} öğe(ler) kitaplıktan kaldırılsın mı?", - "invalidEncryptionKey": "Şifreleme anahtarı 22 karakter olmalı. Canlı işbirliği devre dışı bırakıldı." + "invalidEncryptionKey": "Şifreleme anahtarı 22 karakter olmalı. Canlı işbirliği devre dışı bırakıldı.", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "Desteklenmeyen dosya türü.", diff --git a/src/locales/uk-UA.json b/src/locales/uk-UA.json index 73597ce9d..ac0bfcfee 100644 --- a/src/locales/uk-UA.json +++ b/src/locales/uk-UA.json @@ -72,7 +72,7 @@ "layers": "Шари", "actions": "Дії", "language": "Мова", - "liveCollaboration": "", + "liveCollaboration": "Спільна робота у режимі реального часу...", "duplicateSelection": "Дублювати", "untitled": "Без назви", "name": "Ім’я", @@ -192,7 +192,8 @@ "invalidSceneUrl": "Не вдалося імпортувати сцену з наданого URL. Він або недоформований, або не містить дійсних даних Excalidraw JSON.", "resetLibrary": "Це призведе до очищення бібліотеки. Ви впевнені?", "removeItemsFromsLibrary": "Видалити {{count}} елемент(ів) з бібліотеки?", - "invalidEncryptionKey": "Ключ шифрування повинен бути довжиною до 22 символів. Спільну роботу вимкнено." + "invalidEncryptionKey": "Ключ шифрування повинен бути довжиною до 22 символів. Спільну роботу вимкнено.", + "collabOfflineWarning": "Немає підключення до Інтернету.\nВаші зміни не будуть збережені!" }, "errors": { "unsupportedFileType": "Непідтримуваний тип файлу.", @@ -457,8 +458,8 @@ "defaults": { "menuHint": "", "center_heading": "", - "toolbarHint": "", - "helpHint": "" + "toolbarHint": "Оберіть інструмент і почніть малювати!", + "helpHint": "Гарячі клавіші і допомога" } } } diff --git a/src/locales/vi-VN.json b/src/locales/vi-VN.json index be5edd61d..1921035bc 100644 --- a/src/locales/vi-VN.json +++ b/src/locales/vi-VN.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 6020688a2..2e5debd16 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "无法从提供的 URL 导入场景。它或者格式不正确,或者不包含有效的 Excalidraw JSON 数据。", "resetLibrary": "这将会清除你的素材库。你确定要这么做吗?", "removeItemsFromsLibrary": "确定要从素材库中删除 {{count}} 个项目吗?", - "invalidEncryptionKey": "密钥必须包含22个字符。实时协作已被禁用。" + "invalidEncryptionKey": "密钥必须包含22个字符。实时协作已被禁用。", + "collabOfflineWarning": "无网络连接。\n您的改动将不会被保存!" }, "errors": { "unsupportedFileType": "不支持的文件格式。", @@ -239,7 +240,7 @@ "resize": "您可以按住SHIFT来限制比例大小,\n按住ALT来调整中心大小", "resizeImage": "按住SHIFT可以自由缩放,\n按住ALT可以从中间缩放", "rotate": "旋转时可以按住 Shift 来约束角度", - "lineEditor_info": "按住 CtrlOrCmd 并双击或按 CtrlOrmd + Enter 来编辑点", + "lineEditor_info": "按住 CtrlOrCmd 并双击或按 CtrlOrCmd + Enter 来编辑点", "lineEditor_pointSelected": "按下 Delete 移除点,Ctrl 或 Cmd+D 以复制,拖动以移动", "lineEditor_nothingSelected": "选择要编辑的点 (按住 SHIFT 选择多个),\n或按住 Alt 并点击以添加新点", "placeImage": "点击放置图像,或者点击并拖动以手动设置图像大小", diff --git a/src/locales/zh-HK.json b/src/locales/zh-HK.json index 4ff1176be..e87a0d3e1 100644 --- a/src/locales/zh-HK.json +++ b/src/locales/zh-HK.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "", "resetLibrary": "", "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "" + "invalidEncryptionKey": "", + "collabOfflineWarning": "" }, "errors": { "unsupportedFileType": "", diff --git a/src/locales/zh-TW.json b/src/locales/zh-TW.json index 28ac95a26..3293fad4a 100644 --- a/src/locales/zh-TW.json +++ b/src/locales/zh-TW.json @@ -192,7 +192,8 @@ "invalidSceneUrl": "無法由提供的 URL 匯入場景。可能是發生異常,或未包含有效的 Excalidraw JSON 資料。", "resetLibrary": "這會清除您的資料庫,是否確定?", "removeItemsFromsLibrary": "從資料庫刪除 {{count}} 項?", - "invalidEncryptionKey": "加密鍵必須為22字元。即時協作已停用。" + "invalidEncryptionKey": "加密鍵必須為22字元。即時協作已停用。", + "collabOfflineWarning": "沒有可用的網路連線。\n變更無法儲存!" }, "errors": { "unsupportedFileType": "不支援的檔案類型。", From 04a8c22f3908614243273f4614a983fc04f59e24 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Wed, 22 Feb 2023 15:01:23 +0100 Subject: [PATCH 6/9] fix: rerender i18n in host components on lang change (#6224) --- src/components/main-menu/DefaultItems.tsx | 33 +++++++------------ .../welcome-screen/WelcomeScreen.Center.tsx | 7 ++-- src/excalidraw-app/app-jotai.ts | 3 ++ src/excalidraw-app/collab/Collab.tsx | 12 +++---- src/excalidraw-app/collab/RoomDialog.tsx | 3 +- .../components/AppWelcomeScreen.tsx | 3 +- .../components/EncryptedIcon.tsx | 32 ++++++++++-------- .../components/ExportToExcalidrawPlus.tsx | 3 +- .../components/LanguageList.tsx | 15 +++++---- src/excalidraw-app/index.tsx | 17 +++++----- src/i18n.ts | 16 +++++++++ src/jotai.ts | 4 +-- src/packages/excalidraw/CHANGELOG.md | 2 ++ src/packages/excalidraw/index.tsx | 10 +++--- 14 files changed, 88 insertions(+), 72 deletions(-) create mode 100644 src/excalidraw-app/app-jotai.ts diff --git a/src/components/main-menu/DefaultItems.tsx b/src/components/main-menu/DefaultItems.tsx index 6e89b598c..b3cc23b90 100644 --- a/src/components/main-menu/DefaultItems.tsx +++ b/src/components/main-menu/DefaultItems.tsx @@ -1,5 +1,5 @@ import { getShortcutFromShortcutName } from "../../actions/shortcuts"; -import { t } from "../../i18n"; +import { useI18n } from "../../i18n"; import { useExcalidrawAppState, useExcalidrawSetAppState, @@ -33,9 +33,7 @@ import { useSetAtom } from "jotai"; import { activeConfirmDialogAtom } from "../ActiveConfirmDialog"; export const LoadScene = () => { - // FIXME Hack until we tie "t" to lang state - // eslint-disable-next-line - const appState = useExcalidrawAppState(); + const { t } = useI18n(); const actionManager = useExcalidrawActionManager(); if (!actionManager.isActionEnabled(actionLoadScene)) { @@ -57,9 +55,7 @@ export const LoadScene = () => { LoadScene.displayName = "LoadScene"; export const SaveToActiveFile = () => { - // FIXME Hack until we tie "t" to lang state - // eslint-disable-next-line - const appState = useExcalidrawAppState(); + const { t } = useI18n(); const actionManager = useExcalidrawActionManager(); if (!actionManager.isActionEnabled(actionSaveToActiveFile)) { @@ -80,9 +76,7 @@ SaveToActiveFile.displayName = "SaveToActiveFile"; export const SaveAsImage = () => { const setAppState = useExcalidrawSetAppState(); - // FIXME Hack until we tie "t" to lang state - // eslint-disable-next-line - const appState = useExcalidrawAppState(); + const { t } = useI18n(); return ( { SaveAsImage.displayName = "SaveAsImage"; export const Help = () => { - // FIXME Hack until we tie "t" to lang state - // eslint-disable-next-line - const appState = useExcalidrawAppState(); + const { t } = useI18n(); const actionManager = useExcalidrawActionManager(); @@ -119,9 +111,8 @@ export const Help = () => { Help.displayName = "Help"; export const ClearCanvas = () => { - // FIXME Hack until we tie "t" to lang state - // eslint-disable-next-line - const appState = useExcalidrawAppState(); + const { t } = useI18n(); + const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom); const actionManager = useExcalidrawActionManager(); @@ -143,6 +134,7 @@ export const ClearCanvas = () => { ClearCanvas.displayName = "ClearCanvas"; export const ToggleTheme = () => { + const { t } = useI18n(); const appState = useExcalidrawAppState(); const actionManager = useExcalidrawActionManager(); @@ -175,6 +167,7 @@ export const ToggleTheme = () => { ToggleTheme.displayName = "ToggleTheme"; export const ChangeCanvasBackground = () => { + const { t } = useI18n(); const appState = useExcalidrawAppState(); const actionManager = useExcalidrawActionManager(); @@ -195,9 +188,7 @@ export const ChangeCanvasBackground = () => { ChangeCanvasBackground.displayName = "ChangeCanvasBackground"; export const Export = () => { - // FIXME Hack until we tie "t" to lang state - // eslint-disable-next-line - const appState = useExcalidrawAppState(); + const { t } = useI18n(); const setAppState = useExcalidrawSetAppState(); return ( void; isCollaborating: boolean; }) => { - // FIXME Hack until we tie "t" to lang state - // eslint-disable-next-line - const appState = useExcalidrawAppState(); + const { t } = useI18n(); return ( any; }) => { - // FIXME when we tie t() to lang state - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const appState = useExcalidrawAppState(); - + const { t } = useI18n(); return ( {t("labels.liveCollaboration")} diff --git a/src/excalidraw-app/app-jotai.ts b/src/excalidraw-app/app-jotai.ts new file mode 100644 index 000000000..8c6c796f6 --- /dev/null +++ b/src/excalidraw-app/app-jotai.ts @@ -0,0 +1,3 @@ +import { unstable_createStore } from "jotai"; + +export const appJotaiStore = unstable_createStore(); diff --git a/src/excalidraw-app/collab/Collab.tsx b/src/excalidraw-app/collab/Collab.tsx index 22f748773..30c9846c8 100644 --- a/src/excalidraw-app/collab/Collab.tsx +++ b/src/excalidraw-app/collab/Collab.tsx @@ -70,7 +70,7 @@ import { decryptData } from "../../data/encryption"; import { resetBrowserStateVersions } from "../data/tabSync"; import { LocalData } from "../data/LocalData"; import { atom, useAtom } from "jotai"; -import { jotaiStore } from "../../jotai"; +import { appJotaiStore } from "../app-jotai"; export const collabAPIAtom = atom(null); export const collabDialogShownAtom = atom(false); @@ -167,7 +167,7 @@ class Collab extends PureComponent { setUsername: this.setUsername, }; - jotaiStore.set(collabAPIAtom, collabAPI); + appJotaiStore.set(collabAPIAtom, collabAPI); this.onOfflineStatusToggle(); if ( @@ -185,7 +185,7 @@ class Collab extends PureComponent { } onOfflineStatusToggle = () => { - jotaiStore.set(isOfflineAtom, !window.navigator.onLine); + appJotaiStore.set(isOfflineAtom, !window.navigator.onLine); }; componentWillUnmount() { @@ -208,10 +208,10 @@ class Collab extends PureComponent { } } - isCollaborating = () => jotaiStore.get(isCollaboratingAtom)!; + isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!; private setIsCollaborating = (isCollaborating: boolean) => { - jotaiStore.set(isCollaboratingAtom, isCollaborating); + appJotaiStore.set(isCollaboratingAtom, isCollaborating); }; private onUnload = () => { @@ -804,7 +804,7 @@ class Collab extends PureComponent { ); handleClose = () => { - jotaiStore.set(collabDialogShownAtom, false); + appJotaiStore.set(collabDialogShownAtom, false); }; setUsername = (username: string) => { diff --git a/src/excalidraw-app/collab/RoomDialog.tsx b/src/excalidraw-app/collab/RoomDialog.tsx index 2c6949aac..50f586efc 100644 --- a/src/excalidraw-app/collab/RoomDialog.tsx +++ b/src/excalidraw-app/collab/RoomDialog.tsx @@ -10,13 +10,13 @@ import { shareWindows, } from "../../components/icons"; import { ToolButton } from "../../components/ToolButton"; -import { t } from "../../i18n"; import "./RoomDialog.scss"; import Stack from "../../components/Stack"; import { AppState } from "../../types"; import { trackEvent } from "../../analytics"; import { getFrame } from "../../utils"; import DialogActionButton from "../../components/DialogActionButton"; +import { useI18n } from "../../i18n"; const getShareIcon = () => { const navigator = window.navigator as any; @@ -51,6 +51,7 @@ const RoomDialog = ({ setErrorMessage: (message: string) => void; theme: AppState["theme"]; }) => { + const { t } = useI18n(); const roomLinkInput = useRef(null); const copyRoomLink = async () => { diff --git a/src/excalidraw-app/components/AppWelcomeScreen.tsx b/src/excalidraw-app/components/AppWelcomeScreen.tsx index 9e760f734..1e34fa819 100644 --- a/src/excalidraw-app/components/AppWelcomeScreen.tsx +++ b/src/excalidraw-app/components/AppWelcomeScreen.tsx @@ -1,12 +1,13 @@ import React from "react"; import { PlusPromoIcon } from "../../components/icons"; -import { t } from "../../i18n"; +import { useI18n } from "../../i18n"; import { WelcomeScreen } from "../../packages/excalidraw/index"; import { isExcalidrawPlusSignedUser } from "../app_constants"; export const AppWelcomeScreen: React.FC<{ setCollabDialogShown: (toggle: boolean) => any; }> = React.memo((props) => { + const { t } = useI18n(); let headingContent; if (isExcalidrawPlusSignedUser) { diff --git a/src/excalidraw-app/components/EncryptedIcon.tsx b/src/excalidraw-app/components/EncryptedIcon.tsx index a3e6ff0ba..a91768917 100644 --- a/src/excalidraw-app/components/EncryptedIcon.tsx +++ b/src/excalidraw-app/components/EncryptedIcon.tsx @@ -1,17 +1,21 @@ import { shield } from "../../components/icons"; import { Tooltip } from "../../components/Tooltip"; -import { t } from "../../i18n"; +import { useI18n } from "../../i18n"; -export const EncryptedIcon = () => ( - - - {shield} - - -); +export const EncryptedIcon = () => { + const { t } = useI18n(); + + return ( + + + {shield} + + + ); +}; diff --git a/src/excalidraw-app/components/ExportToExcalidrawPlus.tsx b/src/excalidraw-app/components/ExportToExcalidrawPlus.tsx index 049a4ddf7..daf4b95c3 100644 --- a/src/excalidraw-app/components/ExportToExcalidrawPlus.tsx +++ b/src/excalidraw-app/components/ExportToExcalidrawPlus.tsx @@ -6,7 +6,7 @@ import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase"; import { FileId, NonDeletedExcalidrawElement } from "../../element/types"; import { AppState, BinaryFileData, BinaryFiles } from "../../types"; import { nanoid } from "nanoid"; -import { t } from "../../i18n"; +import { useI18n } from "../../i18n"; import { excalidrawPlusIcon } from "./icons"; import { encryptData, generateEncryptionKey } from "../../data/encryption"; import { isInitializedImageElement } from "../../element/typeChecks"; @@ -79,6 +79,7 @@ export const ExportToExcalidrawPlus: React.FC<{ files: BinaryFiles; onError: (error: Error) => void; }> = ({ elements, appState, files, onError }) => { + const { t } = useI18n(); return (
{excalidrawPlusIcon}
diff --git a/src/excalidraw-app/components/LanguageList.tsx b/src/excalidraw-app/components/LanguageList.tsx index 1b3606b57..aaa5f2137 100644 --- a/src/excalidraw-app/components/LanguageList.tsx +++ b/src/excalidraw-app/components/LanguageList.tsx @@ -1,22 +1,23 @@ -import { useAtom } from "jotai"; +import { useSetAtom } from "jotai"; import React from "react"; -import { langCodeAtom } from ".."; -import * as i18n from "../../i18n"; +import { appLangCodeAtom } from ".."; +import { defaultLang, useI18n } from "../../i18n"; import { languages } from "../../i18n"; export const LanguageList = ({ style }: { style?: React.CSSProperties }) => { - const [langCode, setLangCode] = useAtom(langCodeAtom); + const { t, langCode } = useI18n(); + const setLangCode = useSetAtom(appLangCodeAtom); return (