added more tools, fixed locales
This commit is contained in:
parent
c83454ae2b
commit
7edbc28a9c
|
|
@ -3,6 +3,8 @@
|
|||
// Configuration constants
|
||||
const CONFIG = {
|
||||
AUTOSAVE_DELAY: 1000, // ms - Debounce before note save (autoSave) and drawing PNG autosave (_drawingScheduleAutosave)
|
||||
/** Must match drawingRedraw() fill and eraser stroke color (opaque “whiteboard”). */
|
||||
DRAWING_BACKGROUND: '#ffffff',
|
||||
SEARCH_DEBOUNCE_DELAY: 500, // ms - Delay before running note search while typing
|
||||
SAVE_INDICATOR_DURATION: 2000, // ms - How long to show "saved" indicator
|
||||
SCROLL_SYNC_DELAY: 50, // ms - Delay to prevent scroll sync interference
|
||||
|
|
@ -504,6 +506,8 @@ function noteApp() {
|
|||
drawingRedoStack: [],
|
||||
drawingDraft: null,
|
||||
drawingIsPointerDown: false,
|
||||
/** True after the PNG from disk has been decoded into _drawingBaseImage; false after Clear. */
|
||||
drawingHasRasterFromFile: false,
|
||||
_drawingAutosaveTimeout: null,
|
||||
|
||||
// DOM element cache (to avoid repeated querySelector calls)
|
||||
|
|
@ -2904,7 +2908,7 @@ function noteApp() {
|
|||
const dpr = this._drawingDpr;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillStyle = CONFIG.DRAWING_BACKGROUND;
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
if (this._drawingBaseImage && this._drawingBaseImage.complete) {
|
||||
ctx.drawImage(this._drawingBaseImage, 0, 0, w, h);
|
||||
|
|
@ -3016,6 +3020,7 @@ function noteApp() {
|
|||
this.drawingDraft = null;
|
||||
this.drawingIsPointerDown = false;
|
||||
this._drawingBaseImage = null;
|
||||
this.drawingHasRasterFromFile = false;
|
||||
this._drawingLoadToken = Symbol();
|
||||
const token = this._drawingLoadToken;
|
||||
|
||||
|
|
@ -3050,28 +3055,98 @@ function noteApp() {
|
|||
});
|
||||
if (token !== this._drawingLoadToken) return;
|
||||
this._drawingBaseImage = img;
|
||||
this.drawingHasRasterFromFile = true;
|
||||
this._drawingLayoutCanvas();
|
||||
} catch (e) {
|
||||
if (token !== this._drawingLoadToken) return;
|
||||
ErrorHandler.handle('load drawing', e);
|
||||
this.drawingHasRasterFromFile = false;
|
||||
this._drawingLayoutCanvas();
|
||||
}
|
||||
},
|
||||
|
||||
_drawingRgbToHex(r, g, b) {
|
||||
const h = (n) => {
|
||||
const s = n.toString(16);
|
||||
return s.length === 1 ? `0${s}` : s;
|
||||
};
|
||||
return `#${h(r)}${h(g)}${h(b)}`;
|
||||
},
|
||||
|
||||
/** Sample visible canvas color at logical (css) coordinates; sets drawingColor. */
|
||||
drawingSampleColor(e) {
|
||||
const canvas = this._drawingCanvasEl;
|
||||
const ctx = this._drawingCtx;
|
||||
if (!canvas || !ctx) return;
|
||||
const { x, y } = this._drawingCanvasCoords(e);
|
||||
this.drawingRedraw();
|
||||
const dpr = this._drawingDpr || 1;
|
||||
let ix = Math.floor(x * dpr);
|
||||
let iy = Math.floor(y * dpr);
|
||||
ix = Math.max(0, Math.min(ix, canvas.width - 1));
|
||||
iy = Math.max(0, Math.min(iy, canvas.height - 1));
|
||||
const pix = ctx.getImageData(ix, iy, 1, 1).data;
|
||||
this.drawingColor = this._drawingRgbToHex(pix[0], pix[1], pix[2]);
|
||||
},
|
||||
|
||||
/** True when there is a loaded bitmap and/or session strokes to clear away. */
|
||||
drawingClearEnabled() {
|
||||
if (this.currentMediaType !== 'drawing') return false;
|
||||
return !!(this.drawingHasRasterFromFile || (this.drawingOps && this.drawingOps.length > 0));
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace the in-memory drawing with a blank canvas and schedule save so the file on disk
|
||||
* becomes a fresh white PNG (same dimensions as the viewer). Drops the loaded raster.
|
||||
*/
|
||||
async drawingClear() {
|
||||
if (this.currentMediaType !== 'drawing' || !this.currentMedia) return;
|
||||
if (!this._drawingBaseImage && this.drawingOps.length === 0) return;
|
||||
const ok = await this.confirmModalAsk({
|
||||
title: this.t('drawing.clear_title'),
|
||||
message: this.t('drawing.clear_confirm'),
|
||||
danger: true,
|
||||
confirmLabel: this.t('drawing.clear'),
|
||||
});
|
||||
if (!ok) return;
|
||||
this._drawingCancelAutosave();
|
||||
if (this._drawingObjectURL) {
|
||||
try {
|
||||
URL.revokeObjectURL(this._drawingObjectURL);
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
this._drawingObjectURL = null;
|
||||
}
|
||||
this._drawingBaseImage = null;
|
||||
this.drawingHasRasterFromFile = false;
|
||||
this.drawingDraft = null;
|
||||
this.drawingIsPointerDown = false;
|
||||
this.drawingOps = [];
|
||||
this.drawingRedoStack = [];
|
||||
this.drawingRedraw();
|
||||
this._drawingScheduleAutosave();
|
||||
},
|
||||
|
||||
drawingPointerDown(e) {
|
||||
if (this.currentMediaType !== 'drawing' || e.button !== 0) return;
|
||||
const canvas = this._drawingCanvasEl;
|
||||
if (!canvas) return;
|
||||
const tool = this.drawingTool;
|
||||
if (tool === 'eyedropper') {
|
||||
e.preventDefault();
|
||||
this.drawingSampleColor(e);
|
||||
return;
|
||||
}
|
||||
this._drawingCancelAutosave();
|
||||
canvas.setPointerCapture(e.pointerId);
|
||||
this._drawingPointerId = e.pointerId;
|
||||
const { x, y } = this._drawingCanvasCoords(e);
|
||||
this.drawingIsPointerDown = true;
|
||||
this.drawingRedoStack = [];
|
||||
const color = this.drawingColor;
|
||||
const lw = this.drawingLineWidth;
|
||||
const tool = this.drawingTool;
|
||||
if (tool === 'freehand') {
|
||||
const color = tool === 'eraser' ? CONFIG.DRAWING_BACKGROUND : this.drawingColor;
|
||||
if (tool === 'freehand' || tool === 'eraser') {
|
||||
this.drawingDraft = { type: 'stroke', color, lineWidth: lw, points: [[x, y]] };
|
||||
} else if (tool === 'line') {
|
||||
this.drawingDraft = { type: 'line', color, lineWidth: lw, x1: x, y1: y, x2: x, y2: y };
|
||||
|
|
@ -3187,7 +3262,7 @@ function noteApp() {
|
|||
},
|
||||
|
||||
/**
|
||||
* Persist the flattened canvas to disk (Ctrl+S, toolbar, autosave). Same feedback as saveNote:
|
||||
* Persist the flattened canvas to disk (Ctrl+S, autosave). Same feedback as saveNote:
|
||||
* header "Saved" only — never clears stroke undo/redo or reloads the image; stacks reset when
|
||||
* opening another drawing via initDrawingViewer().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -2705,27 +2705,77 @@
|
|||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="w-px h-5 mx-1" style="background-color: var(--border-primary);"></div>
|
||||
<label class="flex items-center gap-1 text-xs cursor-pointer" style="color: var(--text-secondary);">
|
||||
<span x-text="t('drawing.color')"></span>
|
||||
<input type="color" x-model="drawingColor" class="w-8 h-7 p-0 border rounded cursor-pointer" style="border-color: var(--border-primary);" />
|
||||
</label>
|
||||
<label class="flex items-center gap-1 text-xs" style="color: var(--text-secondary);">
|
||||
<span x-text="t('drawing.width')"></span>
|
||||
<input type="range" min="1" max="32" x-model.number="drawingLineWidth" class="w-24 md:w-32 align-middle" />
|
||||
<span class="tabular-nums w-5" x-text="drawingLineWidth"></span>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 rounded-lg ml-auto transition-colors"
|
||||
style="background-color: var(--accent-primary); color: white;"
|
||||
@click="drawingSave()"
|
||||
:title="t('drawing.save_hint')"
|
||||
class="p-2 rounded-lg border transition-colors"
|
||||
:style="drawingTool === 'eraser' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
|
||||
@click="drawingTool = 'eraser'"
|
||||
:title="t('drawing.tool_eraser')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21M22 21H7M5 11l9 9" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 rounded-lg border transition-colors"
|
||||
:style="drawingTool === 'eyedropper' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
|
||||
@click="drawingTool = 'eyedropper'"
|
||||
:title="t('drawing.tool_eyedropper')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 11.25 1.425 1.425c.671.671.671 1.757 0 2.428l-1.06 1.06c-.671.671-1.757.671-2.428 0l-1.425-1.425m6-6 1.425-1.425c.671-.671 1.757-.671 2.428 0l1.06 1.06c.671.671.671 1.757 0 2.428L21 15.75m-6-6L8.25 3.75a2.121 2.121 0 0 0-3 3L12 15.75l3-3m-3-3-6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 rounded-lg border transition-colors"
|
||||
:class="!drawingClearEnabled() ? 'opacity-40 cursor-not-allowed' : ''"
|
||||
:style="!drawingClearEnabled()
|
||||
? 'border-color: var(--border-primary); color: var(--text-secondary);'
|
||||
: 'border-color: var(--border-primary); color: var(--text-secondary); background-color: var(--bg-tertiary);'"
|
||||
:disabled="!drawingClearEnabled()"
|
||||
@click="drawingClear()"
|
||||
:title="t('drawing.clear_hint')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<label
|
||||
class="flex h-9 items-center gap-2 rounded-lg border px-2 shrink-0 cursor-pointer"
|
||||
style="border-color: var(--border-primary); background-color: var(--bg-tertiary);"
|
||||
:title="t('drawing.color')"
|
||||
>
|
||||
<span class="text-xs select-none" style="color: var(--text-secondary);" x-text="t('drawing.color')"></span>
|
||||
<input
|
||||
type="color"
|
||||
x-model="drawingColor"
|
||||
class="h-7 w-8 shrink-0 cursor-pointer rounded border p-0"
|
||||
style="border-color: var(--border-primary);"
|
||||
:aria-label="t('drawing.color')"
|
||||
/>
|
||||
</label>
|
||||
<div
|
||||
class="flex h-9 min-w-0 shrink items-center gap-2 rounded-lg border px-2"
|
||||
style="border-color: var(--border-primary); background-color: var(--bg-tertiary);"
|
||||
:title="t('drawing.width')"
|
||||
>
|
||||
<span class="text-xs whitespace-nowrap select-none shrink-0" style="color: var(--text-secondary);" x-text="t('drawing.width')"></span>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="32"
|
||||
x-model.number="drawingLineWidth"
|
||||
class="w-24 md:w-36 align-middle min-w-0 flex-1"
|
||||
style="accent-color: var(--accent-primary);"
|
||||
:aria-label="t('drawing.width')"
|
||||
:aria-valuemin="1"
|
||||
:aria-valuemax="32"
|
||||
:aria-valuenow="drawingLineWidth"
|
||||
/>
|
||||
<span class="text-xs tabular-nums shrink-0 w-5 text-right" style="color: var(--text-secondary);" x-text="drawingLineWidth"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor/Preview/Graph -->
|
||||
|
|
@ -3011,7 +3061,9 @@
|
|||
<canvas
|
||||
x-ref="drawingCanvas"
|
||||
class="drawing-surface block w-full h-full"
|
||||
style="touch-action: none; cursor: crosshair; background: #fff;"
|
||||
:style="drawingTool === 'eyedropper'
|
||||
? 'touch-action: none; cursor: copy; background: #fff;'
|
||||
: 'touch-action: none; cursor: crosshair; background: #fff;'"
|
||||
@pointerdown.prevent="drawingPointerDown($event)"
|
||||
@pointermove.prevent="drawingPointerMove($event)"
|
||||
@pointerup.prevent="drawingPointerUp($event)"
|
||||
|
|
|
|||
|
|
@ -215,7 +215,12 @@
|
|||
"tool_ellipse": "Kreis",
|
||||
"color": "Farbe",
|
||||
"width": "Strichstärke",
|
||||
"save_hint": "Zeichnung speichern (Strg+S)"
|
||||
"tool_eraser": "Radierer (malt mit Hintergrundfarbe)",
|
||||
"tool_eyedropper": "Pipette — Farbe vom Bild aufnehmen",
|
||||
"clear": "Leeren",
|
||||
"clear_hint": "Durch leeres Bild ersetzen",
|
||||
"clear_title": "Durch leere Leinwand ersetzen?",
|
||||
"clear_confirm": "Die gespeicherte PNG-Datei und alle Striche in dieser Sitzung werden durch ein weißes Bild ersetzt. Dies kann nicht rückgängig gemacht werden."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "\"{{name}}\" löschen?",
|
||||
|
|
|
|||
|
|
@ -214,7 +214,12 @@
|
|||
"tool_ellipse": "Circle",
|
||||
"color": "Colour",
|
||||
"width": "Stroke width",
|
||||
"save_hint": "Save drawing (Ctrl+S)"
|
||||
"tool_eraser": "Eraser (paints with background colour)",
|
||||
"tool_eyedropper": "Eyedropper — pick a colour from the canvas",
|
||||
"clear": "Clear",
|
||||
"clear_hint": "Replace with a blank image",
|
||||
"clear_title": "Replace with a blank canvas?",
|
||||
"clear_confirm": "The saved PNG and all strokes in this session will be replaced with a blank white image. You cannot undo this."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "Delete \"{{name}}\"?",
|
||||
|
|
|
|||
|
|
@ -215,7 +215,12 @@
|
|||
"tool_ellipse": "Circle",
|
||||
"color": "Color",
|
||||
"width": "Stroke width",
|
||||
"save_hint": "Save drawing (Ctrl+S)"
|
||||
"tool_eraser": "Eraser (paints with background color)",
|
||||
"tool_eyedropper": "Eyedropper — pick a color from the canvas",
|
||||
"clear": "Clear",
|
||||
"clear_hint": "Replace with a blank image",
|
||||
"clear_title": "Replace with a blank canvas?",
|
||||
"clear_confirm": "The saved PNG and all strokes in this session will be replaced with a blank white image. You cannot undo this."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "Delete \"{{name}}\"?",
|
||||
|
|
|
|||
|
|
@ -215,7 +215,12 @@
|
|||
"tool_ellipse": "Círculo",
|
||||
"color": "Color",
|
||||
"width": "Grosor del trazo",
|
||||
"save_hint": "Guardar dibujo (Ctrl+S)"
|
||||
"tool_eraser": "Borrador (pinta con el color de fondo)",
|
||||
"tool_eyedropper": "Cuentagotas — tomar color del lienzo",
|
||||
"clear": "Borrar",
|
||||
"clear_hint": "Sustituir por imagen en blanco",
|
||||
"clear_title": "¿Sustituir por lienzo en blanco?",
|
||||
"clear_confirm": "El PNG guardado y todos los trazos de esta sesión se sustituirán por una imagen en blanco. No se puede deshacer."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "¿Eliminar \"{{name}}\"?",
|
||||
|
|
|
|||
|
|
@ -215,7 +215,12 @@
|
|||
"tool_ellipse": "Cercle",
|
||||
"color": "Couleur",
|
||||
"width": "Épaisseur du trait",
|
||||
"save_hint": "Enregistrer le dessin (Ctrl+S)"
|
||||
"tool_eraser": "Gomme (peint avec la couleur de fond)",
|
||||
"tool_eyedropper": "Pipette — choisir une couleur sur le canevas",
|
||||
"clear": "Effacer",
|
||||
"clear_hint": "Remplacer par une image vide",
|
||||
"clear_title": "Remplacer par un canevas vide ?",
|
||||
"clear_confirm": "Le PNG enregistré et tous les traits de cette session seront remplacés par une image blanche vide. Cette action est irréversible."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "Supprimer \"{{name}}\" ?",
|
||||
|
|
|
|||
|
|
@ -215,7 +215,12 @@
|
|||
"tool_ellipse": "Kör",
|
||||
"color": "Szín",
|
||||
"width": "Vonalvastagság",
|
||||
"save_hint": "Rajz mentése (Ctrl+S)"
|
||||
"tool_eraser": "Radír (háttérszínnel fest)",
|
||||
"tool_eyedropper": "Pipetta — szín mintavétele a vászonról",
|
||||
"clear": "Törlés",
|
||||
"clear_hint": "Üres képre cserélés",
|
||||
"clear_title": "Üres vászonra cserélés?",
|
||||
"clear_confirm": "A mentett PNG és az ebben a munkamenetben rajzolt vonalak fehér üres képre cserélődnek. Ez nem vonható vissza."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "Biztos törlöd a következőt: \"{{name}}\"?",
|
||||
|
|
|
|||
|
|
@ -214,7 +214,12 @@
|
|||
"tool_ellipse": "Cerchio",
|
||||
"color": "Colore",
|
||||
"width": "Spessore tratto",
|
||||
"save_hint": "Salva disegno (Ctrl+S)"
|
||||
"tool_eraser": "Gomma (disegna col colore di sfondo)",
|
||||
"tool_eyedropper": "Contagocce — prendi un colore dal canvas",
|
||||
"clear": "Svuota",
|
||||
"clear_hint": "Sostituisci con immagine vuota",
|
||||
"clear_title": "Sostituire con tela bianca?",
|
||||
"clear_confirm": "Il PNG salvato e tutti i tratti di questa sessione saranno sostituiti da un'immagine bianca vuota. Non è possibile annullare."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "Eliminare \"{{name}}\"?",
|
||||
|
|
|
|||
|
|
@ -214,7 +214,12 @@
|
|||
"tool_ellipse": "円",
|
||||
"color": "色",
|
||||
"width": "線の太さ",
|
||||
"save_hint": "お絵かきを保存(Ctrl+S)"
|
||||
"tool_eraser": "消しゴム(背景色で塗る)",
|
||||
"tool_eyedropper": "スポイト — キャンバスから色を取得",
|
||||
"clear": "クリア",
|
||||
"clear_hint": "白紙の画像に置き換え",
|
||||
"clear_title": "白紙のキャンバスに置き換えますか?",
|
||||
"clear_confirm": "保存済みのPNGとこの作業中の線は、白い空白画像に置き換えられます。元に戻せません。"
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "「{{name}}」を削除しますか?",
|
||||
|
|
|
|||
|
|
@ -214,7 +214,12 @@
|
|||
"tool_ellipse": "Круг",
|
||||
"color": "Цвет",
|
||||
"width": "Толщина линии",
|
||||
"save_hint": "Сохранить рисунок (Ctrl+S)"
|
||||
"tool_eraser": "Ластик (рисует цветом фона)",
|
||||
"tool_eyedropper": "Пипетка — взять цвет с холста",
|
||||
"clear": "Очистить",
|
||||
"clear_hint": "Заменить пустым изображением",
|
||||
"clear_title": "Заменить пустым холстом?",
|
||||
"clear_confirm": "Сохранённый PNG и все штрихи этого сеанса будут заменены пустым белым изображением. Это действие нельзя отменить."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "Удалить «{{name}}»?",
|
||||
|
|
|
|||
|
|
@ -214,7 +214,12 @@
|
|||
"tool_ellipse": "Krog",
|
||||
"color": "Barva",
|
||||
"width": "Debelina črte",
|
||||
"save_hint": "Shrani risbo (Ctrl+S)"
|
||||
"tool_eraser": "Radirka (riše z barvo ozadja)",
|
||||
"tool_eyedropper": "Kapalka — vzemi barvo s platna",
|
||||
"clear": "Počisti",
|
||||
"clear_hint": "Zamenjaj s prazno sliko",
|
||||
"clear_title": "Zamenjaj s praznim platnom?",
|
||||
"clear_confirm": "Shranjena datoteka PNG in vse poteze v tej seji bodo zamenjane s prazno belo sliko. Tega ni mogoče razveljaviti."
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "Izbrišem \"{{name}}\"?",
|
||||
|
|
|
|||
|
|
@ -214,7 +214,12 @@
|
|||
"tool_ellipse": "圆形",
|
||||
"color": "颜色",
|
||||
"width": "线条粗细",
|
||||
"save_hint": "保存绘图(Ctrl+S)"
|
||||
"tool_eraser": "橡皮擦(使用背景色绘制)",
|
||||
"tool_eyedropper": "吸管 — 从画布取色",
|
||||
"clear": "清除",
|
||||
"clear_hint": "替换为空白图像",
|
||||
"clear_title": "替换为空白画布?",
|
||||
"clear_confirm": "已保存的 PNG 和本会话中的笔划都将被替换为空白白色图像,且无法撤销。"
|
||||
},
|
||||
"media": {
|
||||
"confirm_delete": "删除 \"{{name}}\"?",
|
||||
|
|
|
|||
Loading…
Reference in New Issue