From 295a2aecad0b6f13b957e5b2d21c6a31ccb79bd7 Mon Sep 17 00:00:00 2001 From: lightscape-jm Date: Wed, 4 Nov 2026 01:04:54 -0600 Subject: [PATCH 1/2] feat(tools): Metadata tool A palette tool that shows every metadata field of a picture, PDF, video or sound file, grouped by where it lives, and lets you edit, remove or add them, or clear the location or everything at once. Saving is an ordinary conversion carrying the changes, written by ExifTool through a route edge of its own, so the file is never re-encoded and the copy is read back before it counts. ExifTool 13.60 is a new optional engine; without it MetadataExtractor shows the fields read-only (#362). Co-Authored-By: Claude Opus 5.5 --- Directory.Packages.props | 1 + THIRD-PARTY-NOTICES.md | 2 + build/fetch-engines.ps1 | 10 + docs/ARCHITECTURE.md | 48 + docs/ENGINES.md | 4 + src/Filee.App/Assets/i18n/ar.json | 44 + src/Filee.App/Assets/i18n/de.json | 44 + src/Filee.App/Assets/i18n/en.json | 44 + src/Filee.App/Assets/i18n/es.json | 44 + src/Filee.App/Assets/i18n/fr.json | 44 + src/Filee.App/Assets/i18n/hi.json | 44 + src/Filee.App/Assets/i18n/id.json | 44 + src/Filee.App/Assets/i18n/it.json | 44 + src/Filee.App/Assets/i18n/ja.json | 44 + src/Filee.App/Assets/i18n/ko.json | 44 + src/Filee.App/Assets/i18n/ne.json | 44 + src/Filee.App/Assets/i18n/pl.json | 44 + src/Filee.App/Assets/i18n/pt-BR.json | 44 + src/Filee.App/Assets/i18n/pt-PT.json | 44 + src/Filee.App/Assets/i18n/ru.json | 44 + src/Filee.App/Assets/i18n/th.json | 44 + src/Filee.App/Assets/i18n/tr.json | 44 + src/Filee.App/Assets/i18n/vi.json | 44 + src/Filee.App/Assets/i18n/zh-CN.json | 44 + src/Filee.App/Assets/i18n/zh-TW.json | 44 + src/Filee.App/Services/PresetAvailability.cs | 4 + src/Filee.App/Services/WindowService.cs | 35 + src/Filee.App/ViewModels/MetadataViewModel.cs | 783 ++++++++++++ src/Filee.App/Views/MetadataWindow.axaml | 259 ++++ src/Filee.App/Views/MetadataWindow.axaml.cs | 192 +++ src/Filee.Cli/Cli.cs | 2 +- src/Filee.Core/Conversion/ConversionJob.cs | 2 +- src/Filee.Core/Conversion/IConverter.cs | 7 +- src/Filee.Core/Conversion/JobQueue.cs | 7 +- src/Filee.Core/Conversion/RoutePlanner.cs | 26 +- src/Filee.Core/History/HistoryEntry.cs | 6 +- src/Filee.Core/Presets/BuiltInData.cs | 17 + src/Filee.Core/Presets/FolderCopy.cs | 3 + src/Filee.Core/Presets/MetadataEdits.cs | 65 + src/Filee.Core/Presets/MetadataTool.cs | 36 + src/Filee.Core/Presets/Preset.cs | 26 +- src/Filee.Core/Settings/AppSettings.cs | 2 +- src/Filee.Core/Settings/UserDataStore.cs | 8 + src/Filee.Engines/EngineRegistry.cs | 2 + src/Filee.Engines/Filee.Engines.csproj | 1 + .../Infrastructure/EngineDownloads.cs | 3 + .../Infrastructure/EngineEnvironment.cs | 1 + .../Infrastructure/EngineInstaller.cs | 12 + src/Filee.Engines/Infrastructure/engines.json | 35 + src/Filee.Engines/Metadata/ExifTool.cs | 517 ++++++++ .../Metadata/ExifToolConverter.cs | 50 + src/Filee.Engines/Metadata/MetadataField.cs | 34 + src/Filee.Engines/Metadata/MetadataReader.cs | 73 ++ tests/Filee.App.Tests/MetadataWindowTests.cs | 1062 +++++++++++++++++ .../ToolbarPaletteViewTests.cs | 8 +- tests/Filee.Core.Tests/MetadataToolTests.cs | 251 ++++ .../EngineInstallerTests.cs | 38 + tests/Filee.Engines.Tests/MetadataTests.cs | 1018 ++++++++++++++++ 58 files changed, 5511 insertions(+), 19 deletions(-) create mode 100644 src/Filee.App/ViewModels/MetadataViewModel.cs create mode 100644 src/Filee.App/Views/MetadataWindow.axaml create mode 100644 src/Filee.App/Views/MetadataWindow.axaml.cs create mode 100644 src/Filee.Core/Presets/MetadataEdits.cs create mode 100644 src/Filee.Core/Presets/MetadataTool.cs create mode 100644 src/Filee.Engines/Metadata/ExifTool.cs create mode 100644 src/Filee.Engines/Metadata/ExifToolConverter.cs create mode 100644 src/Filee.Engines/Metadata/MetadataField.cs create mode 100644 src/Filee.Engines/Metadata/MetadataReader.cs create mode 100644 tests/Filee.App.Tests/MetadataWindowTests.cs create mode 100644 tests/Filee.Core.Tests/MetadataToolTests.cs create mode 100644 tests/Filee.Engines.Tests/MetadataTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 977efd16f..1812eb44f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -38,6 +38,7 @@ + diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 9ca80c47d..e94553803 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -22,6 +22,8 @@ Each component remains under its own license. | SharpCompress (zstd decompression for engine downloads; RAR, 7z and TAR comics) | MIT | https://github.com/adamhathcock/sharpcompress | | Unhwp | MIT | https://github.com/iyulab/unhwp | | Markdig | BSD-2-Clause | https://github.com/xoofx/markdig | +| MetadataExtractor (reading metadata for the Metadata tool when ExifTool is not installed) | Apache-2.0 | https://github.com/drewnoakes/metadata-extractor-dotnet | +| ExifTool (optional engine, downloaded on request: the Metadata tool reads and writes with it) | Perl's licence (Artistic or GPL), separate program | https://exiftool.org | | ExcelNumberFormat | MIT | https://github.com/andersnm/ExcelNumberFormat | | ExcelDataReader | MIT | https://github.com/ExcelDataReader/ExcelDataReader | | PdfPig (PDF text, layout and pictures) | Apache-2.0 | https://github.com/UglyToad/PdfPig | diff --git a/build/fetch-engines.ps1 b/build/fetch-engines.ps1 index 6b5805e8e..bd140fc02 100644 --- a/build/fetch-engines.ps1 +++ b/build/fetch-engines.ps1 @@ -20,6 +20,8 @@ install: ebook-convert for LIT, LRF, PDB, ... and MOBI / AZW3 output tesseract/ Tesseract OCR (Apache-2.0, separate program), UB Mannheim's Windows build unpacked from its installer with 7-Zip (never run); the language data is downloaded by the app when needed + exiftool/ ExifTool (Perl's licence, separate program), the Windows build: exiftool(-k).exe renamed + exiftool.exe (the "-k" makes it wait for a key), with its exiftool_files/ beside it Every download is pinned to a version and verified with SHA-256 (src/Filee.Engines/Infrastructure/engines.json, shared with the app). Downloads are cached in build/.cache. @@ -370,6 +372,14 @@ if ('tesseract' -in $selected) { if (-not (Test-Path (Join-Path $target 'tesseract.exe'))) { throw 'tesseract.exe not found in the installer.' } } +if ('exiftool' -in $selected) { + # As EngineInstaller does: the one top folder unpacked, the program renamed so it doesn't wait for a key. + $target = Join-Path $Destination 'exiftool' + Expand-Flat (Get-Engine 'exiftool') $target + Move-Item -LiteralPath (Join-Path $target 'exiftool(-k).exe') -Destination (Join-Path $target 'exiftool.exe') -Force + if (-not (Test-Path (Join-Path $target 'exiftool.exe'))) { throw 'exiftool.exe not found in the archive.' } +} + if ('calibre' -in $selected) { $msi = Get-Engine 'calibre' $tmp = Join-Path $cache 'calibre-admin' diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9f29e4c34..cf769c987 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -203,6 +203,54 @@ the picture (padding of its longer side, corners of its shorter side), so a look isn't told apart, and the picture itself edited meanwhile isn't looked for (as in Pixelate / blur and Annotate). One look for all pictures or one each; failures end on a summary, as in Pixelate / blur. +## Metadata tool + +"Metadata" (`tool-metadata`, palette only, settings schema 68) shows every metadata field of pictures, PDFs, video and +sound, grouped by where it lives (ExifTool's family-1 groups: GPS, IFD0, ExifIFD, XMP-dc, IPTC, PDF …), and edits them +without touching the file's content. + +- Saving is an ordinary conversion, as for Pixelate / blur, Annotate and Add background: `MetadataTool.ConversionOf` + makes the tool into a conversion to the file's own format carrying the changes (`Preset.Metadata`, + `MetadataEdits`), so the toast, History, cancelling and the output rules (where the copy goes, its name + `{name}_metadata`, a name already taken, "Replace the original" sending the original to the Recycle Bin / Trash) + work as for any other file. "Replace the original" is the tool's own output rule, kept on disk. History offers no + Retry for it: a Filee from before it would re-encode the file without the changes. +- Only an engine that writes metadata and nothing else takes such a conversion: `ConversionEdge.Metadata`, never part + of an ordinary route, and with `RoutePlanner.Plan(metadata: true)` the whole route or none. Without one the file + fails (`error.no_route`, with the route asked for, so `filee` can name the package), never re-encoded instead. +- `Engines/Metadata/ExifToolConverter` is that engine (`exiftool` package: Windows build renamed from + `exiftool(-k).exe`, the Perl version elsewhere, or one on the PATH). Its edges are the formats `exiftool -listwf` + names; a file of an extension it doesn't write shows read-only, saying so. `ExifTool` reads with `-j -G0:1` (lists as + JSON arrays, so an entry keeps its commas; "Group:Tag" where family and group share a name, as IPTC and PDF do). +- It writes by editing a copy in a work folder beside the output, on its disk, under a plain name, so nothing in the + file's name reaches ExifTool (`%` is expanded in paths it takes, Perl on Windows reads its command line in the ANSI + code page, and `-o` refuses WebP). A `-config` of its own makes the `GPS…Ref` fields macOS puts in XMP writable, so + they can be removed. Edits go through an argument file, one argument per line taken literally, a value of several + lines through `-Tag<=file`, a list one assignment per entry. IPTC text outside Latin-1 switches the file's IPTC to + UTF-8 with its other IPTC text written again, so none is garbled. +- What can be edited is a choice, not what's left over: IFDs, PDFs and PNGs offer only the fields that describe the file + (`IfdFields`, `PdfFields`, `PngFields`). The rest says how to read it (a TIFF's layout, a DNG's colour calibration, a + PDF's page mode, a PNG's colours), and a video's or HEIC's header and tracks can't be changed. Those show a lock. +- `-m` lets a file with a small fault of its own be written. The copy is then read back: a warning naming a changed + field, a field unknown or not writable, text that doesn't fit its character set, a field set that isn't there, one + removed that still is, or an editable field Remove location or Remove everything should have taken refuses the copy + (`error.metadata_refused`) and leaves none. Remove location takes GPS and the XMP, IPTC and QuickTime place fields, and + every editable field `ExifTool.IsLocation` finds, the one rule the window strikes through by; a locked one (a camera's + focus location, in its maker notes) stays, in plain sight. Remove everything is `-all=` keeping the orientation and the + colour profile, a TIFF's descriptive IFD fields by name on every page. It may also take locked rows (maker notes, cover + art, a JPEG's DPI, a PNG's custom text); others stay, a video's header dates and a TIFF's other tags among them, and the + hint says so. A file that holds more than its picture (`ExifTool.HoldsMore`: a Motion Photo's video, an MPF second + picture, a gain map, a Samsung trailer) doesn't offer it, and the window says why: it would orphan what's there and + leave that metadata, which no field shows. Camera RAW and DNG don't offer it either. +- Without ExifTool, `Engines/Metadata/MetadataReader` (MetadataExtractor) lists the fields read-only, and the window + offers Settings → Engines; it reads the file again with ExifTool once it is installed. +- `MetadataViewModel` keeps one row per field (edited, removed, added; a list one entry per line) and turns them into + `MetadataEdits`. Add field offers XMP's descriptive fields, IPTC's where the kind of file has IPTC (JPEG, TIFF, PSD), + a PDF's document info for a PDF. The keys, the summary of files that could not be opened or saved, and texts that + follow a language change are as in Add background. +- PDFs: ExifTool appends its changes as an update, so earlier values stay recoverable inside the file; the window says + so. + ## Crop overlay and PDF page editor The image Crop tool (`tool-crop`) and PDF Crop (`tool-pdf-crop`) open `Views/CropOverlayWindow` instead of running a job: diff --git a/docs/ENGINES.md b/docs/ENGINES.md index 9d7c61e3f..4ee603d06 100644 --- a/docs/ENGINES.md +++ b/docs/ENGINES.md @@ -39,6 +39,7 @@ Filee chooses engines automatically. Settings → *Engines* shows their status, | FFmpeg | **optional download** (~100 MB, 272 MB on disk) | **all video and audio**: video ↔ video (H.264, HEVC, AV1), video → animated GIF / APNG, a still frame or every frame, audio and subtitle extraction, audio ↔ audio (also APE, WavPack, TTA, DSD, DTS, ALAC, …), GIF, APNG and photos → MP4/WEBM/MOV, Live / Motion Photos → their video, a clip of a video → an iPhone Live Photo or Android Motion Photo (experimental), images → HEIC, clips or audio files joined into one, pictures → a slideshow MP4, copies without metadata (see "Media tools") | | Calibre | **optional download** (~216 MB, 660 MB on disk) | rare e-book formats: LIT, LRF, CHM, PDB, PML, RB, SNB, TCR, OEB → EPUB; EPUB → MOBI, AZW3, LIT, LRF, PDB, PML, RB, SNB, TCR; e-books → PDF (ranked above the HWPX writer, see below) | | Whisper | **optional download** (Windows only; ~157 MB with model, ~160 MB on disk) | **Speech-to-text transcription**: video and audio → SRT subtitles in ~100 languages using OpenAI Whisper (whisper.cpp); runs locally and offline; needs FFmpeg | +| ExifTool | **optional download** (~11 MB on Windows, ~8 MB elsewhere; 30 MB on disk) | **the Metadata tool**: reads every metadata field of pictures, PDFs, video and sound, and writes changes into a copy in the file's own format without re-encoding it (JPG, PNG, TIFF, WEBP, HEIC, AVIF, JPEG XL, GIF, PSD, camera RAW, PDF, MP4, MOV, M4A, 3GP, …; not MP3, FLAC, WAV, MKV, AVI or BMP, which it shows read-only). Without it the Metadata tool shows the fields read-only (MetadataExtractor, built in) | DOCX, XLSX, XLS, ODS and PPTX → PDF need neither Microsoft Office nor LibreOffice: they are read in-process, written as HWPX and rendered by rhwp. Anything the built-in readers understand (including PDF) is written as DOCX by the DOCX writer. @@ -109,6 +110,9 @@ they can be installed or removed later in Settings → *Engines*. A conversion t installer files and bundled language data are removed (`TrimTesseract`, ~80 MB). The languages come from `tessdata_fast` 4.1.0, pinned like the engines in `Infrastructure/ocr-languages.json` (`traineddata` kind, one folder per language under `engines/ocr-languages`), and are downloaded the first time a preset uses one. +- ExifTool is Phil Harvey's Windows build (a Perl bundled with it), whose `exiftool(-k).exe` is renamed `exiftool.exe`: + the "-k" makes it wait for a key before it exits. On macOS and Linux it is the Perl distribution + (`Image-ExifTool-*.tar.gz`), run by the Perl both come with. ### Microsoft C++ runtime (Windows) diff --git a/src/Filee.App/Assets/i18n/ar.json b/src/Filee.App/Assets/i18n/ar.json index bb8d7fc50..eea354fdc 100644 --- a/src/Filee.App/Assets/i18n/ar.json +++ b/src/Filee.App/Assets/i18n/ar.json @@ -243,6 +243,7 @@ "engine.calibre.description": "صيغ الكتب الإلكترونية النادرة (LIT وLRF وCHM وPDB وRB…) وإخراج Kindle ‏(MOBI وAZW3).\nعند وضعه فوق محرك كتابة HWPX، يُنشئ أيضًا PDF من الكتب الإلكترونية (بسرعة أكبر بكثير للكتب الطويلة).", "engine.libreoffice.description": "قراءة الصيغ القديمة والأقل شيوعًا: DOC وPPT وPages وKeynote وNumbers وWordPerfect وClarisWorks وPublisher وVisio…\nوالحفظ بصيغ DOC وXLS وPPT وODT وODP وقوالب OpenDocument وEMF.", "engine.translator.description": "يقوم بترجمة المستندات والكتب الإلكترونية محلياً باستخدام نماذج Ollama دون اتصال بالإنترنت.\nيترجم أيضاً ملفات الترجمة بصيغتي SRT وWebVTT: يُرسَل نص المقاطع فقط إلى النموذج، وتبقى أرقام المقاطع وتوقيتاتها كما هي.", + "engine.exiftool.description": "يقرأ كل حقول البيانات الوصفية للصور وملفات PDF والفيديو والصوت، ويعدّلها في معظم أنواع الملفات دون المساس بمحتوى الملف.\nتستخدمه أداة البيانات الوصفية؛ ومن دونه تُعرض الحقول للقراءة فقط.", "profile.images": "الصور", "profile.pdf": "PDF", "profile.office": "المستندات", @@ -299,6 +300,7 @@ "error.cannot_hide": "البكسلة والتمويه متاحان للصور فقط", "error.background_picture": "تعذّرت قراءة الصورة المراد وضعها في الخلفية", "error.background_picture_changed": "تغيّرت الصورة المراد وضعها في الخلفية بعد عرضها. أضف الخلفية مرة أخرى.", + "error.metadata_refused": "تعذّرت كتابة بعض تغييرات البيانات الوصفية كما طُلبت", "hide.save": "إخفاء وحفظ", "hide.save_next": "إخفاء والتالي", "hide.pixelate": "بكسلة", @@ -415,6 +417,7 @@ "preset.group_flip": "انعكاس", "preset.annotate": "تعليقات", "preset.background": "إضافة خلفية", + "preset.metadata": "البيانات الوصفية", "preset.flip_horizontal": "انعكاس أفقي", "preset.flip_horizontal.short": "أفقي", "preset.flip_vertical": "انعكاس عمودي", @@ -1057,6 +1060,43 @@ "background.color_sage": "مريمية", "background.picture_unreadable": "تعذّرت قراءة هذه الصورة. اختر صورة أخرى، أو تدرّجًا أو لونًا.", "background.keys": "Enter يحفظ · Esc يغلق · Tab يتنقّل بين خيارات المظهر · الأسهم تختار أو تحرّك شريط التمرير", + "metadata.search": "ابحث في الحقول والقيم", + "metadata.fields": "الحقول: {0}", + "metadata.location_warning": "يستطيع أي شخص لديه هذا الملف معرفة مكان إنشائه.", + "metadata.read_only_banner": "تُعرض كل الحقول. لتعديلها، ثبّت ExifTool من الإعدادات › المحركات.", + "metadata.unwritable": "تُعرض كل الحقول. لا يستطيع Filee تغيير البيانات الوصفية لهذا النوع من الملفات.", + "metadata.needs_exiftool": "لا يمكن قراءة حقول هذا الملف من دون ExifTool. ثبّته من الإعدادات › المحركات.", + "metadata.embedded": "يحتوي هذا الملف أيضًا على فيديو أو صورة ثانية، ولها بيانات وصفية خاصة لا تظهر هنا ولا يمكن مسحها هنا. ينشئ «{0}» نسخة نظيفة من دونها.", + "metadata.install": "تثبيت ExifTool", + "metadata.remove_location": "إزالة الموقع", + "metadata.remove_all": "إزالة الكل", + "metadata.remove_all_hint": "يحتفظ بالاتجاه وملف تعريف الألوان. قد تبقى الحقول المقفلة.", + "metadata.changes_one": "تغيير واحد", + "metadata.changes": "التغييرات: {0}", + "metadata.replace": "استبدال الأصل", + "metadata.replace_hint": "تحلّ النسخة محلّ الأصل، ويُنقل الأصل إلى سلة المحذوفات", + "metadata.save": "حفظ", + "metadata.save_copy": "حفظ نسخة", + "metadata.save_next": "حفظ والتالي", + "metadata.add_field": "إضافة حقل", + "metadata.one_per_line": "واحد في كل سطر", + "metadata.remove_field": "إزالة الحقل", + "metadata.remove_named": "إزالة {0}", + "metadata.undo": "تراجع", + "metadata.undo_named": "إعادة {0}", + "metadata.spoken_removed": "{0}، أُزيل", + "metadata.read_only": "لا يمكن تغييره هنا", + "metadata.pdf_note": "تبقى القيم السابقة قابلة للاسترجاع داخل ملف PDF. يزيلها «إزالة البيانات الوصفية» نهائيًا.", + "metadata.keys": "Enter يعدّل حقلًا أو يحفظ · Shift+Enter يبدأ سطرًا جديدًا · Esc يلغي أو يغلق · Ctrl+F يبحث", + "metadata.empty": "لا توجد بيانات وصفية في هذا الملف.", + "metadata.unreadable": "تعذّرت قراءة بياناته الوصفية", + "metadata.group.location": "الموقع", + "metadata.group.camera": "الكاميرا", + "metadata.group.exposure": "التعريض", + "metadata.group.description": "الوصف", + "metadata.group.keywords": "الكلمات المفتاحية", + "metadata.group.document": "المستند", + "metadata.group.file": "الملف", "presets.editor_title": "تعديل الإعداد المسبق", "presets.delete_confirm": "هل تريد حذف «{0}»؟ سيُزال أيضًا من جميع أشرطة الأدوات والمجموعات.", "presets.name_required": "أدخل اسمًا", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "تحويل EPS وPS وملفات Illustrator القديمة إلى PDF وصور، وتحويل PDF أو SVG إلى EPS وPS.\nتُحوَّل ملفات SVG وAI المتوافقة مع PDF بدونه.", "engines.package.tesseract.name": "Tesseract (التعرّف على النص، OCR)", "engines.package.tesseract.description": "يقرأ نص ملفات PDF الممسوحة والصور: PDF قابل للبحث وDOCX وTXT.\nتُنزَّل كل لغة عند استخدامها أول مرة.", + "engines.package.exiftool.name": "ExifTool (البيانات الوصفية)", + "engines.package.exiftool.description": "يقرأ كل حقول البيانات الوصفية للصور وملفات PDF والفيديو والصوت، ويعدّلها في معظم أنواع الملفات دون المساس بمحتوى الملف.\nتستخدمه أداة البيانات الوصفية؛ ومن دونه تُعرض الحقول للقراءة فقط.", "engines.package.ffmpeg.name": "FFmpeg (الفيديو والصوت)", "engines.package.ffmpeg.description": "يضيف تحويل الفيديو والصوت، وتحويل الفيديو إلى GIF أو صورة ثابتة، واستخراج الصوت من الفيديو، وتحويل GIF إلى MP4.\nمطلوب للفيديو والصوت فقط.", "engines.package.whisper.name": "Whisper (ترجمة بالذكاء الاصطناعي)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "ثبّته باستخدام Homebrew ‏(brew install ffmpeg)، ثم اختر «التحقق مجددًا».\nأو اختر «استخدام نسختي…» وحدّد مجلد bin الخاص به.", "engines.guidance.macos.ghostscript": "ثبّته باستخدام Homebrew ‏(brew install ghostscript)، ثم اختر «التحقق مجددًا».\nأو اختر «استخدام نسختي…» وحدّد مجلد bin الخاص به.", "engines.guidance.macos.tesseract": "ثبّته باستخدام Homebrew ‏(brew install tesseract)، ثم اختر «التحقق مجددًا».\nأو اختر «استخدام نسختي…» وحدّد مجلد bin الخاص به.", + "engines.guidance.macos.exiftool": "ثبّته باستخدام Homebrew ‏(brew install exiftool)، ثم اختر «تحقق مرة أخرى».\nأو اختر «استخدام نسختي…» وحدّد مجلده.", "engines.guidance.macos.calibre": "يتطلب calibre الذي ينزّله Filee نظام macOS 14 أو أحدث. ثبّت calibre 7.26، آخر إصدار لهذا النظام، من calibre-ebook.com، ثم اختر «استخدام نسختي…» وحدّد مجلد ‎.app الخاص به.", "engines.guidance.linux": "ثبّت {0} باستخدام مدير الحزم في توزيعتك، ثم اختر «استخدام نسختي…» وحدّد مجلد البرنامج.", "engines.guidance.linux.libreoffice": "ثبّته باستخدام مدير الحزم (على Ubuntu/Debian: sudo apt install libreoffice)، ثم اختر «التحقق مجددًا».\nيتطلب فتح HWP أيضًا Java وإضافة H2Orestart.", "engines.guidance.linux.ghostscript": "ثبّته باستخدام مدير الحزم (على Ubuntu/Debian: sudo apt install ghostscript)، ثم اختر «التحقق مجددًا».", "engines.guidance.linux.tesseract": "ثبّته باستخدام مدير الحزم (على Ubuntu/Debian: sudo apt install tesseract-ocr)، ثم اختر «التحقق مجددًا».", + "engines.guidance.linux.exiftool": "ثبّته بمدير الحزم (Ubuntu/Debian: sudo apt install libimage-exiftool-perl)، ثم اختر «تحقق مرة أخرى».", "engines.guidance.windows": "لا يوفر Filee تنزيلًا لـ {0} لهذا الحاسوب.\nاختر «استخدام نسختي…» وحدّد نسخة مثبّتة.", "engines.use_own_app_folder": "اختر المجلد الذي يحتوي على {0}.app", "engines.progress_detail": "{0} من {1} · {2}/ث · {3}", diff --git a/src/Filee.App/Assets/i18n/de.json b/src/Filee.App/Assets/i18n/de.json index 5f4b681d2..31a16a222 100644 --- a/src/Filee.App/Assets/i18n/de.json +++ b/src/Filee.App/Assets/i18n/de.json @@ -243,6 +243,7 @@ "engine.calibre.description": "Seltene E-Book-Formate (LIT, LRF, CHM, PDB, RB…) und Kindle-Ausgabe (MOBI, AZW3).\nOberhalb des HWPX-Writers erstellt es auch PDFs aus E-Books (bei langen Büchern deutlich schneller).", "engine.libreoffice.description": "Liest ältere und weniger verbreitete Formate: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nSpeichert auch als DOC, XLS, PPT, ODT, ODP, OpenDocument-Vorlagen und EMF.", "engine.translator.description": "Übersetzt Dokumente und E-Books lokal mit Offline-Ollama-Modellen.\nÜbersetzt auch SRT- und WebVTT-Untertitel: Nur der Text der Einträge wird an das Modell gesendet, Nummern und Zeitstempel der Einträge bleiben erhalten.", + "engine.exiftool.description": "Liest alle Metadatenfelder von Bildern, PDFs, Video und Audio und bearbeitet sie in den meisten Dateiarten, ohne den Inhalt der Datei zu verändern.\nVom Metadaten-Werkzeug genutzt; ohne es sind die Felder schreibgeschützt.", "profile.images": "Bilder", "profile.pdf": "PDF", "profile.office": "Dokumente", @@ -299,6 +300,7 @@ "error.cannot_hide": "Verpixeln und Weichzeichnen ist nur bei Bildern möglich", "error.background_picture": "Das Bild für den Hintergrund kann nicht gelesen werden", "error.background_picture_changed": "Das Bild für den Hintergrund hat sich geändert, nachdem es angezeigt wurde. Füge den Hintergrund noch einmal hinzu.", + "error.metadata_refused": "Einige Metadatenänderungen ließen sich nicht wie gewünscht schreiben", "hide.save": "Verbergen & Speichern", "hide.save_next": "Verbergen & Weiter", "hide.pixelate": "Verpixeln", @@ -415,6 +417,7 @@ "preset.group_flip": "Spiegeln", "preset.annotate": "Beschriften", "preset.background": "Hintergrund hinzufügen", + "preset.metadata": "Metadaten", "preset.flip_horizontal": "Horizontal spiegeln", "preset.flip_horizontal.short": "Horizontal", "preset.flip_vertical": "Vertikal spiegeln", @@ -1057,6 +1060,43 @@ "background.color_sage": "Salbei", "background.picture_unreadable": "Dieses Bild kann nicht gelesen werden. Wähle ein anderes, einen Verlauf oder eine Farbe.", "background.keys": "Eingabe speichert · Esc schließt · Tab geht durch den Look · Pfeiltasten wählen oder bewegen einen Regler", + "metadata.search": "Felder und Werte suchen", + "metadata.fields": "{0} Felder", + "metadata.location_warning": "Jeder mit dieser Datei sieht, wo sie entstanden ist.", + "metadata.read_only_banner": "Alle Felder werden angezeigt. Zum Bearbeiten ExifTool unter Einstellungen › Engines installieren.", + "metadata.unwritable": "Alle Felder werden angezeigt. Filee kann die Metadaten dieser Dateiart nicht ändern.", + "metadata.needs_exiftool": "Die Felder dieser Datei lassen sich ohne ExifTool nicht lesen. Installiere es unter Einstellungen › Engines.", + "metadata.embedded": "Diese Datei enthält auch ein Video oder ein zweites Bild mit eigenen Metadaten, die hier weder angezeigt noch entfernt werden können. „{0}“ erstellt eine saubere Kopie ohne sie.", + "metadata.install": "ExifTool installieren", + "metadata.remove_location": "Ort entfernen", + "metadata.remove_all": "Alles entfernen", + "metadata.remove_all_hint": "Behält Ausrichtung und Farbprofil. Gesperrte Felder können bleiben.", + "metadata.changes_one": "1 Änderung", + "metadata.changes": "{0} Änderungen", + "metadata.replace": "Original ersetzen", + "metadata.replace_hint": "Die Kopie tritt an die Stelle des Originals, das Original kommt in den Papierkorb", + "metadata.save": "Speichern", + "metadata.save_copy": "Kopie speichern", + "metadata.save_next": "Speichern & weiter", + "metadata.add_field": "Feld hinzufügen", + "metadata.one_per_line": "Eins pro Zeile", + "metadata.remove_field": "Feld entfernen", + "metadata.remove_named": "{0} entfernen", + "metadata.undo": "Rückgängig", + "metadata.undo_named": "{0} wiederherstellen", + "metadata.spoken_removed": "{0}, entfernt", + "metadata.read_only": "Kann hier nicht geändert werden", + "metadata.pdf_note": "Frühere Werte bleiben im PDF wiederherstellbar. „Metadaten entfernen“ löscht sie endgültig.", + "metadata.keys": "Enter bearbeitet ein Feld oder speichert · Umschalt+Enter beginnt eine neue Zeile · Esc bricht ab oder schließt · Strg+F sucht", + "metadata.empty": "Diese Datei hat keine Metadaten.", + "metadata.unreadable": "Die Metadaten konnten nicht gelesen werden", + "metadata.group.location": "Ort", + "metadata.group.camera": "Kamera", + "metadata.group.exposure": "Belichtung", + "metadata.group.description": "Beschreibung", + "metadata.group.keywords": "Stichwörter", + "metadata.group.document": "Dokument", + "metadata.group.file": "Datei", "presets.editor_title": "Vorlage bearbeiten", "presets.delete_confirm": "„{0}“ löschen? Die Vorlage wird auch aus allen Werkzeugleisten und Gruppen entfernt.", "presets.name_required": "Namen eingeben", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "EPS, PS und ältere Illustrator-Dateien → PDF und Bilder, PDF oder SVG → EPS/PS.\nSVG und PDF-kompatible AI-Dateien lassen sich auch ohne Ghostscript konvertieren.", "engines.package.tesseract.name": "Tesseract (Texterkennung, OCR)", "engines.package.tesseract.description": "Liest Text aus gescannten PDFs und Bildern: durchsuchbare PDF, DOCX, TXT.\nJede Sprache wird bei der ersten Verwendung heruntergeladen.", + "engines.package.exiftool.name": "ExifTool (Metadaten)", + "engines.package.exiftool.description": "Liest alle Metadatenfelder von Bildern, PDFs, Video und Audio und bearbeitet sie in den meisten Dateiarten, ohne den Inhalt der Datei zu verändern.\nVom Metadaten-Werkzeug genutzt; ohne es sind die Felder schreibgeschützt.", "engines.package.ffmpeg.name": "FFmpeg (Video und Audio)", "engines.package.ffmpeg.description": "Ergänzt Video- und Audiokonvertierung, Video → GIF oder Einzelbild, Ton aus Videos und GIF → MP4.\nNur für Video und Audio erforderlich.", "engines.package.whisper.name": "Whisper (KI-Untertitel)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "Mit Homebrew installieren (brew install ffmpeg), dann „Erneut prüfen“ wählen.\nOder „Eigene Version…“ wählen und den bin-Ordner auswählen.", "engines.guidance.macos.ghostscript": "Mit Homebrew installieren (brew install ghostscript), dann „Erneut prüfen“ wählen.\nOder „Eigene Version…“ wählen und den bin-Ordner auswählen.", "engines.guidance.macos.tesseract": "Mit Homebrew installieren (brew install tesseract), dann „Erneut prüfen“ wählen.\nOder „Eigene Installation verwenden…“ wählen und den bin-Ordner auswählen.", + "engines.guidance.macos.exiftool": "Mit Homebrew installieren (brew install exiftool), dann „Erneut prüfen“ wählen.\nOder „Meine Kopie verwenden…“ wählen und den Ordner auswählen.", "engines.guidance.macos.calibre": "Die von Filee heruntergeladene Calibre-Version benötigt macOS 14 oder neuer. Installiere Calibre 7.26, die letzte Version für dieses macOS, von calibre-ebook.com. Wähle dann „Eigene Version…“ und den zugehörigen .app-Ordner.", "engines.guidance.linux": "Installiere {0} über die Paketverwaltung deiner Distribution. Wähle dann „Eigene Version…“ und den zugehörigen Programmordner.", "engines.guidance.linux.libreoffice": "Über die Paketverwaltung installieren (Ubuntu/Debian: sudo apt install libreoffice), dann „Erneut prüfen“ wählen.\nZum Öffnen von HWP werden außerdem Java und die Erweiterung H2Orestart benötigt.", "engines.guidance.linux.ghostscript": "Über die Paketverwaltung installieren (Ubuntu/Debian: sudo apt install ghostscript), dann „Erneut prüfen“ wählen.", "engines.guidance.linux.tesseract": "Über die Paketverwaltung installieren (Ubuntu/Debian: sudo apt install tesseract-ocr), dann „Erneut prüfen“ wählen.", + "engines.guidance.linux.exiftool": "Mit der Paketverwaltung installieren (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), dann „Erneut prüfen“ wählen.", "engines.guidance.windows": "Filee bietet für diesen PC keinen Download von {0} an.\nWähle „Eigene Version…“ und eine installierte Version.", "engines.use_own_app_folder": "Ordner auswählen, der {0}.app enthält", "engines.progress_detail": "{0} von {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/en.json b/src/Filee.App/Assets/i18n/en.json index baf4cd0b5..b71f50399 100644 --- a/src/Filee.App/Assets/i18n/en.json +++ b/src/Filee.App/Assets/i18n/en.json @@ -250,6 +250,7 @@ "engine.calibre.description": "Rare e-book formats (LIT, LRF, CHM, PDB, RB…) and Kindle output (MOBI, AZW3).\nAbove the HWPX writer, it also makes the PDFs of e-books (much faster for long books).", "engine.libreoffice.description": "Reads older and less common formats: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nAlso saves as DOC, XLS, PPT, ODT, ODP, OpenDocument templates and EMF.", "engine.translator.description": "Translates documents and e-books locally using offline Ollama models.\nAlso translates SRT and WebVTT subtitles: only the cue text is sent to the model, cue numbers and timestamps are kept.", + "engine.exiftool.description": "Reads every metadata field of pictures, PDFs, video and sound, and edits them in most kinds of file without touching the file's content.\nUsed by the Metadata tool; without it the fields are shown read-only.", "profile.images": "Images", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "Only pictures can be pixelated or blurred", "error.background_picture": "The picture to put behind can't be read", "error.background_picture_changed": "The picture to put behind changed after it was shown. Add the background again.", + "error.metadata_refused": "Some of the metadata changes couldn't be written as asked", "hide.save": "Hide & Save", "hide.save_next": "Hide & Next", "hide.pixelate": "Pixelate", @@ -425,6 +427,7 @@ "preset.group_flip": "Flip", "preset.annotate": "Annotate", "preset.background": "Add background", + "preset.metadata": "Metadata", "preset.flip_horizontal": "Flip horizontal", "preset.flip_horizontal.short": "Horizontal", "preset.flip_vertical": "Flip vertical", @@ -1072,6 +1075,43 @@ "background.color_sage": "Sage", "background.picture_unreadable": "This picture can't be read. Choose another one, or a gradient or a colour.", "background.keys": "Enter saves · Esc closes · Tab goes through the look · arrows choose or move a slider", + "metadata.search": "Search fields and values", + "metadata.fields": "{0} fields", + "metadata.location_warning": "Anyone with this file can see where it was made.", + "metadata.read_only_banner": "Showing every field. To edit them, install ExifTool in Settings › Engines.", + "metadata.unwritable": "Showing every field. Filee can't change the metadata of this kind of file.", + "metadata.needs_exiftool": "This file's fields can't be read without ExifTool. Install it in Settings › Engines.", + "metadata.embedded": "This file also holds a video or a second picture, with metadata of its own that isn't shown here and can't be cleared here. “{0}” makes a clean copy, without them.", + "metadata.install": "Install ExifTool", + "metadata.remove_location": "Remove location", + "metadata.remove_all": "Remove everything", + "metadata.remove_all_hint": "Keeps the orientation and the colour profile. Locked fields may stay.", + "metadata.changes_one": "1 change", + "metadata.changes": "{0} changes", + "metadata.replace": "Replace the original", + "metadata.replace_hint": "The copy takes the original's place, and the original goes to the Recycle Bin / Trash", + "metadata.save": "Save", + "metadata.save_copy": "Save copy", + "metadata.save_next": "Save & next", + "metadata.add_field": "Add field", + "metadata.one_per_line": "One per line", + "metadata.remove_field": "Remove field", + "metadata.remove_named": "Remove {0}", + "metadata.undo": "Undo", + "metadata.undo_named": "Put {0} back", + "metadata.spoken_removed": "{0}, removed", + "metadata.read_only": "Can't be changed here", + "metadata.pdf_note": "Earlier values stay recoverable inside the PDF. Remove metadata drops them for good.", + "metadata.keys": "Enter edits a field or saves · Shift+Enter starts a new line · Esc cancels or closes · Ctrl+F searches", + "metadata.empty": "No metadata found in this file.", + "metadata.unreadable": "Its metadata couldn't be read", + "metadata.group.location": "Location", + "metadata.group.camera": "Camera", + "metadata.group.exposure": "Exposure", + "metadata.group.description": "Description", + "metadata.group.keywords": "Keywords", + "metadata.group.document": "Document", + "metadata.group.file": "File", "presets.editor_title": "Edit preset", "presets.delete_confirm": "Delete “{0}”? It will also be removed from every toolbar and group.", "presets.name_required": "Enter a name", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS, PS and older Illustrator files → PDF and images, PDF or SVG → EPS/PS.\nSVG and PDF-compatible AI files convert without it.", "engines.package.tesseract.name": "Tesseract (text recognition, OCR)", "engines.package.tesseract.description": "Reads the text of scanned PDFs and pictures: searchable PDF, DOCX, TXT.\nEach language downloads the first time it is used.", + "engines.package.exiftool.name": "ExifTool (metadata)", + "engines.package.exiftool.description": "Reads every metadata field of pictures, PDFs, video and sound, and edits them in most kinds of file without touching the file's content.\nUsed by the Metadata tool; without it the fields are shown read-only.", "engines.package.ffmpeg.name": "FFmpeg (video and audio)", "engines.package.ffmpeg.description": "Adds video and audio conversion, video → GIF or still image, sound from videos and GIF → MP4.\nOnly needed for video and audio.", "engines.package.whisper.name": "Whisper (AI Captions)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Install it with Homebrew (brew install ffmpeg), then choose “Check again”.\nOr choose “Use my copy…” and select its bin folder.", "engines.guidance.macos.ghostscript": "Install it with Homebrew (brew install ghostscript), then choose “Check again”.\nOr choose “Use my copy…” and select its bin folder.", "engines.guidance.macos.tesseract": "Install it with Homebrew (brew install tesseract), then choose “Check again”.\nOr choose “Use my copy…” and select its bin folder.", + "engines.guidance.macos.exiftool": "Install it with Homebrew (brew install exiftool), then choose “Check again”.\nOr choose “Use my copy…” and select its folder.", "engines.guidance.macos.calibre": "The calibre Filee downloads needs macOS 14 or later. Install calibre 7.26, the last version for this macOS, from calibre-ebook.com, then choose “Use my copy…” and select its .app folder.", "engines.guidance.linux": "Install {0} with your distribution’s package manager, then choose “Use my copy…” and select its program folder.", "engines.guidance.linux.libreoffice": "Install it with your package manager (Ubuntu/Debian: sudo apt install libreoffice), then choose “Check again”.\nOpening HWP also needs Java and the H2Orestart extension.", "engines.guidance.linux.ghostscript": "Install it with your package manager (Ubuntu/Debian: sudo apt install ghostscript), then choose “Check again”.", "engines.guidance.linux.tesseract": "Install it with your package manager (Ubuntu/Debian: sudo apt install tesseract-ocr), then choose “Check again”.", + "engines.guidance.linux.exiftool": "Install it with your package manager (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), then choose “Check again”.", "engines.guidance.windows": "Filee has no download of {0} for this PC.\nChoose “Use my copy…” and select an installed copy.", "engines.use_own_app_folder": "Choose the folder containing {0}.app", "engines.progress_detail": "{0} of {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/es.json b/src/Filee.App/Assets/i18n/es.json index c5f7b1cbc..a780a8ec0 100644 --- a/src/Filee.App/Assets/i18n/es.json +++ b/src/Filee.App/Assets/i18n/es.json @@ -243,6 +243,7 @@ "engine.calibre.description": "Formatos raros de libros electrónicos (LIT, LRF, CHM, PDB, RB…) y salida Kindle (MOBI, AZW3).\nPor encima del escritor HWPX, también crea los PDF de libros electrónicos (mucho más rápido para libros largos).", "engine.libreoffice.description": "Lee formatos antiguos y poco habituales: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nTambién guarda como DOC, XLS, PPT, ODT, ODP, plantillas OpenDocument y EMF.", "engine.translator.description": "Traduce documentos y libros electrónicos localmente usando modelos de Ollama fuera de línea.\nTambién traduce subtítulos SRT y WebVTT: solo se envía al modelo el texto de cada entrada; los números de entrada y las marcas de tiempo se conservan.", + "engine.exiftool.description": "Lee todos los campos de metadatos de imágenes, PDF, vídeo y audio, y los edita en la mayoría de tipos de archivo sin tocar su contenido.\nLo usa la herramienta Metadatos; sin él, los campos se muestran de solo lectura.", "profile.images": "Imágenes", "profile.pdf": "PDF", "profile.office": "Documentos", @@ -299,6 +300,7 @@ "error.cannot_hide": "Solo se pueden pixelar o difuminar imágenes", "error.background_picture": "No se puede leer la imagen para el fondo", "error.background_picture_changed": "La imagen para el fondo cambió después de mostrarse. Vuelve a añadir el fondo.", + "error.metadata_refused": "Algunos cambios de metadatos no se pudieron escribir como se pidió", "hide.save": "Ocultar y guardar", "hide.save_next": "Ocultar y seguir", "hide.pixelate": "Pixelar", @@ -415,6 +417,7 @@ "preset.group_flip": "Voltear", "preset.annotate": "Anotar", "preset.background": "Añadir fondo", + "preset.metadata": "Metadatos", "preset.flip_horizontal": "Volteo horizontal", "preset.flip_horizontal.short": "Horizontal", "preset.flip_vertical": "Volteo vertical", @@ -1057,6 +1060,43 @@ "background.color_sage": "Salvia", "background.picture_unreadable": "No se puede leer esta imagen. Elige otra, o un degradado o un color.", "background.keys": "Intro guarda · Esc cierra · Tab recorre el aspecto · las flechas eligen o mueven un control deslizante", + "metadata.search": "Buscar campos y valores", + "metadata.fields": "{0} campos", + "metadata.location_warning": "Cualquiera que tenga este archivo puede ver dónde se hizo.", + "metadata.read_only_banner": "Se muestran todos los campos. Para editarlos, instala ExifTool en Ajustes › Motores.", + "metadata.unwritable": "Se muestran todos los campos. Filee no puede cambiar los metadatos de este tipo de archivo.", + "metadata.needs_exiftool": "Los campos de este archivo no se pueden leer sin ExifTool. Instálalo en Ajustes › Motores.", + "metadata.embedded": "Este archivo también contiene un vídeo o una segunda imagen, con metadatos propios que aquí no se muestran ni se pueden borrar. «{0}» crea una copia limpia, sin ellos.", + "metadata.install": "Instalar ExifTool", + "metadata.remove_location": "Quitar ubicación", + "metadata.remove_all": "Quitar todo", + "metadata.remove_all_hint": "Conserva la orientación y el perfil de color. Los campos bloqueados pueden quedarse.", + "metadata.changes_one": "1 cambio", + "metadata.changes": "{0} cambios", + "metadata.replace": "Reemplazar el original", + "metadata.replace_hint": "La copia ocupa el lugar del original, y el original va a la Papelera de reciclaje", + "metadata.save": "Guardar", + "metadata.save_copy": "Guardar copia", + "metadata.save_next": "Guardar y siguiente", + "metadata.add_field": "Añadir campo", + "metadata.one_per_line": "Uno por línea", + "metadata.remove_field": "Quitar campo", + "metadata.remove_named": "Quitar {0}", + "metadata.undo": "Deshacer", + "metadata.undo_named": "Restaurar {0}", + "metadata.spoken_removed": "{0}, quitado", + "metadata.read_only": "No se puede cambiar aquí", + "metadata.pdf_note": "Los valores anteriores siguen siendo recuperables dentro del PDF. «Quitar metadatos» los elimina del todo.", + "metadata.keys": "Intro edita un campo o guarda · Mayús+Intro empieza una línea nueva · Esc cancela o cierra · Ctrl+F busca", + "metadata.empty": "Este archivo no tiene metadatos.", + "metadata.unreadable": "No se pudieron leer sus metadatos", + "metadata.group.location": "Ubicación", + "metadata.group.camera": "Cámara", + "metadata.group.exposure": "Exposición", + "metadata.group.description": "Descripción", + "metadata.group.keywords": "Palabras clave", + "metadata.group.document": "Documento", + "metadata.group.file": "Archivo", "presets.editor_title": "Editar ajuste preestablecido", "presets.delete_confirm": "¿Eliminar «{0}»? También se quitará de todos los menús circulares y grupos.", "presets.name_required": "Escribe un nombre", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "EPS, PS y archivos antiguos de Illustrator → PDF e imágenes, PDF o SVG → EPS/PS.\nSVG y AI compatibles con PDF se convierten sin él.", "engines.package.tesseract.name": "Tesseract (reconocimiento de texto, OCR)", "engines.package.tesseract.description": "Lee el texto de PDF escaneados e imágenes: PDF con búsqueda, DOCX, TXT.\nCada idioma se descarga la primera vez que se usa.", + "engines.package.exiftool.name": "ExifTool (metadatos)", + "engines.package.exiftool.description": "Lee todos los campos de metadatos de imágenes, PDF, vídeo y audio, y los edita en la mayoría de tipos de archivo sin tocar su contenido.\nLo usa la herramienta Metadatos; sin él, los campos se muestran de solo lectura.", "engines.package.ffmpeg.name": "FFmpeg (vídeo y audio)", "engines.package.ffmpeg.description": "Añade conversión de vídeo y audio, vídeo → GIF o imagen fija, sonido de vídeos y GIF → MP4.\nSolo es necesario para vídeo y audio.", "engines.package.whisper.name": "Whisper (subtítulos IA)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "Instálalo con Homebrew (brew install ffmpeg) y luego elige «Comprobar otra vez».\nO bien elige «Usar mi copia…» y selecciona su carpeta bin.", "engines.guidance.macos.ghostscript": "Instálalo con Homebrew (brew install ghostscript) y luego elige «Comprobar otra vez».\nO bien elige «Usar mi copia…» y selecciona su carpeta bin.", "engines.guidance.macos.tesseract": "Instálalo con Homebrew (brew install tesseract) y luego elige «Comprobar otra vez».\nO bien elige «Usar mi copia…» y selecciona su carpeta bin.", + "engines.guidance.macos.exiftool": "Instálalo con Homebrew (brew install exiftool) y elige «Comprobar de nuevo».\nO elige «Usar mi copia…» y selecciona su carpeta.", "engines.guidance.macos.calibre": "El calibre que descarga Filee necesita macOS 14 o posterior. Instala calibre 7.26, la última versión para este macOS, desde calibre-ebook.com y luego elige «Usar mi copia…» y selecciona su carpeta .app.", "engines.guidance.linux": "Instala {0} con el gestor de paquetes de tu distribución y luego elige «Usar mi copia…» y selecciona su carpeta de programa.", "engines.guidance.linux.libreoffice": "Instálalo con tu gestor de paquetes (Ubuntu/Debian: sudo apt install libreoffice) y luego elige «Comprobar otra vez».\nAbrir HWP necesita además Java y la extensión H2Orestart.", "engines.guidance.linux.ghostscript": "Instálalo con tu gestor de paquetes (Ubuntu/Debian: sudo apt install ghostscript) y luego elige «Comprobar otra vez».", "engines.guidance.linux.tesseract": "Instálalo con tu gestor de paquetes (Ubuntu/Debian: sudo apt install tesseract-ocr) y luego elige «Comprobar otra vez».", + "engines.guidance.linux.exiftool": "Instálalo con tu gestor de paquetes (Ubuntu/Debian: sudo apt install libimage-exiftool-perl) y elige «Comprobar de nuevo».", "engines.guidance.windows": "Filee no tiene una descarga de {0} para este PC.\nElige «Usar mi copia…» y selecciona una copia instalada.", "engines.use_own_app_folder": "Elige la carpeta que contiene {0}.app", "engines.progress_detail": "{0} de {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/fr.json b/src/Filee.App/Assets/i18n/fr.json index daaf48328..5c40f752d 100644 --- a/src/Filee.App/Assets/i18n/fr.json +++ b/src/Filee.App/Assets/i18n/fr.json @@ -250,6 +250,7 @@ "engine.calibre.description": "Formats de livres numériques rares (LIT, LRF, CHM, PDB, RB…) et sortie Kindle (MOBI, AZW3).\nPlacé au-dessus du rédacteur HWPX, il produit aussi les PDF des livres numériques (bien plus vite pour les longs livres).", "engine.libreoffice.description": "Lit les formats anciens et moins courants : DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nEnregistre aussi en DOC, XLS, PPT, ODT, ODP, modèles OpenDocument et EMF.", "engine.translator.description": "Traduit les documents et livres électroniques localement à l'aide de modèles Ollama hors ligne.\nTraduit aussi les sous-titres SRT et WebVTT : seul le texte des répliques est envoyé au modèle, les numéros et les horodatages sont conservés.", + "engine.exiftool.description": "Lit tous les champs de métadonnées des images, PDF, vidéos et sons, et les modifie dans la plupart des types de fichiers sans toucher au contenu.\nUtilisé par l'outil Métadonnées ; sans lui, les champs sont en lecture seule.", "profile.images": "Images", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "Seules les images peuvent être pixelisées ou floutées", "error.background_picture": "Impossible de lire l'image à mettre en arrière-plan", "error.background_picture_changed": "L'image à mettre en arrière-plan a changé après avoir été affichée. Ajoutez de nouveau l'arrière-plan.", + "error.metadata_refused": "Certaines modifications des métadonnées n'ont pas pu être écrites comme demandé", "hide.save": "Masquer et enregistrer", "hide.save_next": "Masquer et suivant", "hide.pixelate": "Pixeliser", @@ -425,6 +427,7 @@ "preset.group_flip": "Retourner", "preset.annotate": "Annoter", "preset.background": "Ajouter un fond", + "preset.metadata": "Métadonnées", "preset.flip_horizontal": "Retourner horizontalement", "preset.flip_horizontal.short": "Horizontal", "preset.flip_vertical": "Retourner verticalement", @@ -1072,6 +1075,43 @@ "background.color_sage": "Sauge", "background.picture_unreadable": "Impossible de lire cette image. Choisissez-en une autre, ou un dégradé ou une couleur.", "background.keys": "Entrée enregistre · Échap ferme · Tab parcourt l'apparence · les flèches choisissent ou déplacent un curseur", + "metadata.search": "Rechercher des champs et des valeurs", + "metadata.fields": "{0} champs", + "metadata.location_warning": "Toute personne qui a ce fichier peut voir où il a été pris.", + "metadata.read_only_banner": "Tous les champs sont affichés. Pour les modifier, installez ExifTool dans Réglages › Moteurs.", + "metadata.unwritable": "Tous les champs sont affichés. Filee ne peut pas modifier les métadonnées de ce type de fichier.", + "metadata.needs_exiftool": "Les champs de ce fichier ne peuvent pas être lus sans ExifTool. Installez-le dans Réglages › Moteurs.", + "metadata.embedded": "Ce fichier contient aussi une vidéo ou une seconde image, avec ses propres métadonnées, qui ne sont ni affichées ni effaçables ici. « {0} » crée une copie propre, sans elles.", + "metadata.install": "Installer ExifTool", + "metadata.remove_location": "Retirer le lieu", + "metadata.remove_all": "Tout retirer", + "metadata.remove_all_hint": "Garde l'orientation et le profil de couleur. Les champs verrouillés peuvent rester.", + "metadata.changes_one": "1 modification", + "metadata.changes": "{0} modifications", + "metadata.replace": "Remplacer l'original", + "metadata.replace_hint": "La copie prend la place de l'original, et l'original va dans la Corbeille", + "metadata.save": "Enregistrer", + "metadata.save_copy": "Enregistrer une copie", + "metadata.save_next": "Enregistrer et suivant", + "metadata.add_field": "Ajouter un champ", + "metadata.one_per_line": "Un par ligne", + "metadata.remove_field": "Retirer le champ", + "metadata.remove_named": "Supprimer {0}", + "metadata.undo": "Annuler", + "metadata.undo_named": "Rétablir {0}", + "metadata.spoken_removed": "{0}, supprimé", + "metadata.read_only": "Ne peut pas être modifié ici", + "metadata.pdf_note": "Les anciennes valeurs restent récupérables dans le PDF. « Supprimer les métadonnées » les efface pour de bon.", + "metadata.keys": "Entrée modifie un champ ou enregistre · Maj+Entrée commence une nouvelle ligne · Échap annule ou ferme · Ctrl+F recherche", + "metadata.empty": "Aucune métadonnée dans ce fichier.", + "metadata.unreadable": "Ses métadonnées n'ont pas pu être lues", + "metadata.group.location": "Lieu", + "metadata.group.camera": "Appareil", + "metadata.group.exposure": "Exposition", + "metadata.group.description": "Description", + "metadata.group.keywords": "Mots-clés", + "metadata.group.document": "Document", + "metadata.group.file": "Fichier", "presets.editor_title": "Modifier le préréglage", "presets.delete_confirm": "Supprimer « {0} » ? Il sera aussi retiré de tous les menus circulaires et groupes.", "presets.name_required": "Saisissez un nom", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "Fichiers EPS, PS et anciens Illustrator → PDF et images, PDF ou SVG → EPS/PS.\nLes fichiers SVG et AI compatibles PDF se convertissent sans lui.", "engines.package.tesseract.name": "Tesseract (reconnaissance de texte, OCR)", "engines.package.tesseract.description": "Lit le texte des PDF numérisés et des images : PDF consultable, DOCX, TXT.\nChaque langue est téléchargée à sa première utilisation.", + "engines.package.exiftool.name": "ExifTool (métadonnées)", + "engines.package.exiftool.description": "Lit tous les champs de métadonnées des images, PDF, vidéos et sons, et les modifie dans la plupart des types de fichiers sans toucher au contenu.\nUtilisé par l'outil Métadonnées ; sans lui, les champs sont en lecture seule.", "engines.package.ffmpeg.name": "FFmpeg (vidéo et audio)", "engines.package.ffmpeg.description": "Ajoute la conversion vidéo et audio, vidéo → GIF ou image fixe, le son des vidéos et GIF → MP4.\nNécessaire uniquement pour la vidéo et l'audio.", "engines.package.whisper.name": "Whisper (sous-titres IA)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Installez-le avec Homebrew (brew install ffmpeg), puis choisissez « Revérifier ».\nOu choisissez « Utiliser ma copie… » et sélectionnez son dossier bin.", "engines.guidance.macos.ghostscript": "Installez-le avec Homebrew (brew install ghostscript), puis choisissez « Revérifier ».\nOu choisissez « Utiliser ma copie… » et sélectionnez son dossier bin.", "engines.guidance.macos.tesseract": "Installez-le avec Homebrew (brew install tesseract), puis choisissez « Revérifier ».\nOu choisissez « Utiliser ma copie… » et sélectionnez son dossier bin.", + "engines.guidance.macos.exiftool": "Installez-le avec Homebrew (brew install exiftool), puis choisissez « Vérifier à nouveau ».\nOu choisissez « Utiliser ma copie… » et sélectionnez son dossier.", "engines.guidance.macos.calibre": "Le calibre téléchargé par Filee nécessite macOS 14 ou ultérieur. Installez calibre 7.26, dernière version pour ce macOS, depuis calibre-ebook.com, puis choisissez « Utiliser ma copie… » et sélectionnez son dossier .app.", "engines.guidance.linux": "Installez {0} avec le gestionnaire de paquets de votre distribution, puis choisissez « Utiliser ma copie… » et sélectionnez le dossier du programme.", "engines.guidance.linux.libreoffice": "Installez-le avec votre gestionnaire de paquets (Ubuntu/Debian : sudo apt install libreoffice), puis choisissez « Revérifier ».\nL'ouverture des HWP nécessite aussi Java et l'extension H2Orestart.", "engines.guidance.linux.ghostscript": "Installez-le avec votre gestionnaire de paquets (Ubuntu/Debian : sudo apt install ghostscript), puis choisissez « Revérifier ».", "engines.guidance.linux.tesseract": "Installez-le avec votre gestionnaire de paquets (Ubuntu/Debian : sudo apt install tesseract-ocr), puis choisissez « Revérifier ».", + "engines.guidance.linux.exiftool": "Installez-le avec votre gestionnaire de paquets (Ubuntu/Debian : sudo apt install libimage-exiftool-perl), puis choisissez « Vérifier à nouveau ».", "engines.guidance.windows": "Filee ne propose pas de téléchargement de {0} pour ce PC.\nChoisissez « Utiliser ma copie… » et sélectionnez une copie installée.", "engines.use_own_app_folder": "Choisissez le dossier contenant {0}.app", "engines.progress_detail": "{0} sur {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/hi.json b/src/Filee.App/Assets/i18n/hi.json index 89dcbfcc4..857afa7a0 100644 --- a/src/Filee.App/Assets/i18n/hi.json +++ b/src/Filee.App/Assets/i18n/hi.json @@ -243,6 +243,7 @@ "engine.calibre.description": "दुर्लभ ई-बुक फ़ॉर्मैट (LIT, LRF, CHM, PDB, RB…) और Kindle आउटपुट (MOBI, AZW3)।\nHWPX writer के ऊपर होने पर यह ई-बुक के PDF भी बनाता है (लंबी किताबों के लिए बहुत तेज़)।", "engine.libreoffice.description": "पुराने और कम प्रचलित फ़ॉर्मैट पढ़ता है: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nDOC, XLS, PPT, ODT, ODP, OpenDocument टेम्पलेट और EMF के रूप में सहेजता भी है।", "engine.translator.description": "ऑफ़लाइन ओलामा मॉडल का उपयोग करके दस्तावेज़ों और ई-पुस्तकों का स्थानीय अनुवाद करता है।\nSRT और WebVTT सबटाइटल का भी अनुवाद करता है: मॉडल को केवल क्यू का टेक्स्ट भेजा जाता है, क्यू नंबर और टाइमस्टैम्प वैसे ही रहते हैं।", + "engine.exiftool.description": "चित्रों, PDF, वीडियो और ऑडियो के सभी मेटाडेटा फ़ील्ड पढ़ता है, और ज़्यादातर तरह की फ़ाइलों में फ़ाइल की सामग्री छुए बिना उन्हें बदलता है।\nमेटाडेटा टूल इसका उपयोग करता है; इसके बिना फ़ील्ड केवल पढ़ने के लिए दिखते हैं।", "profile.images": "इमेज", "profile.pdf": "PDF", "profile.office": "दस्तावेज़", @@ -299,6 +300,7 @@ "error.cannot_hide": "पिक्सलेट और धुंधला केवल इमेज पर काम करता है", "error.background_picture": "पीछे लगाने वाली तस्वीर पढ़ी नहीं जा सकती", "error.background_picture_changed": "पीछे लगाने वाली तस्वीर दिखाए जाने के बाद बदल गई। पृष्ठभूमि फिर से जोड़ें।", + "error.metadata_refused": "मेटाडेटा के कुछ बदलाव माँगे गए तरीके से लिखे नहीं जा सके", "hide.save": "छिपाकर सहेजें", "hide.save_next": "छिपाकर अगला", "hide.pixelate": "पिक्सलेट", @@ -415,6 +417,7 @@ "preset.group_flip": "पलटें", "preset.annotate": "टिप्पणी", "preset.background": "पृष्ठभूमि जोड़ें", + "preset.metadata": "मेटाडेटा", "preset.flip_horizontal": "आड़ा पलटें", "preset.flip_horizontal.short": "आड़ा", "preset.flip_vertical": "खड़ा पलटें", @@ -1057,6 +1060,43 @@ "background.color_sage": "सेज", "background.picture_unreadable": "यह तस्वीर पढ़ी नहीं जा सकती। कोई दूसरी चुनें, या ग्रेडिएंट या रंग।", "background.keys": "Enter सहेजता है · Esc बंद करता है · Tab रूप के विकल्पों में चलता है · तीर चुनते हैं या स्लाइडर खिसकाते हैं", + "metadata.search": "फ़ील्ड और मान खोजें", + "metadata.fields": "{0} फ़ील्ड", + "metadata.location_warning": "इस फ़ाइल वाला कोई भी देख सकता है कि यह कहाँ बनी।", + "metadata.read_only_banner": "सभी फ़ील्ड दिख रहे हैं। बदलने के लिए सेटिंग › इंजन में ExifTool इंस्टॉल करें।", + "metadata.unwritable": "सभी फ़ील्ड दिख रहे हैं। Filee इस तरह की फ़ाइल का मेटाडेटा नहीं बदल सकता।", + "metadata.needs_exiftool": "इस फ़ाइल के फ़ील्ड ExifTool के बिना पढ़े नहीं जा सकते। इसे सेटिंग › इंजन में इंस्टॉल करें।", + "metadata.embedded": "इस फ़ाइल में एक वीडियो या दूसरा चित्र भी है, जिसका अपना मेटाडेटा यहाँ न दिखता है न हटाया जा सकता है। “{0}” उनके बिना एक साफ़ कॉपी बनाता है।", + "metadata.install": "ExifTool इंस्टॉल करें", + "metadata.remove_location": "स्थान हटाएँ", + "metadata.remove_all": "सब हटाएँ", + "metadata.remove_all_hint": "दिशा और रंग प्रोफ़ाइल रखता है। लॉक किए गए फ़ील्ड रह सकते हैं।", + "metadata.changes_one": "1 बदलाव", + "metadata.changes": "{0} बदलाव", + "metadata.replace": "मूल फ़ाइल बदलें", + "metadata.replace_hint": "कॉपी मूल फ़ाइल की जगह ले लेती है, और मूल फ़ाइल रीसायकल बिन / ट्रैश में चली जाती है", + "metadata.save": "सहेजें", + "metadata.save_copy": "कॉपी सहेजें", + "metadata.save_next": "सहेजें और अगला", + "metadata.add_field": "फ़ील्ड जोड़ें", + "metadata.one_per_line": "हर पंक्ति में एक", + "metadata.remove_field": "फ़ील्ड हटाएँ", + "metadata.remove_named": "{0} हटाएँ", + "metadata.undo": "पूर्ववत करें", + "metadata.undo_named": "{0} वापस लाएँ", + "metadata.spoken_removed": "{0}, हटाया गया", + "metadata.read_only": "यहाँ बदला नहीं जा सकता", + "metadata.pdf_note": "पुराने मान PDF के अंदर वापस पाए जा सकते हैं। «मेटाडेटा हटाएँ» उन्हें पूरी तरह हटा देता है।", + "metadata.keys": "Enter फ़ील्ड बदलता है या सहेजता है · Shift+Enter नई पंक्ति शुरू करता है · Esc रद्द करता है या बंद करता है · Ctrl+F खोजता है", + "metadata.empty": "इस फ़ाइल में कोई मेटाडेटा नहीं है।", + "metadata.unreadable": "इसका मेटाडेटा पढ़ा नहीं जा सका", + "metadata.group.location": "स्थान", + "metadata.group.camera": "कैमरा", + "metadata.group.exposure": "एक्सपोज़र", + "metadata.group.description": "विवरण", + "metadata.group.keywords": "कीवर्ड", + "metadata.group.document": "दस्तावेज़", + "metadata.group.file": "फ़ाइल", "presets.editor_title": "प्रीसेट संपादित करें", "presets.delete_confirm": "“{0}” हटाएँ? यह हर टूलबार और समूह से भी हट जाएगा।", "presets.name_required": "नाम दर्ज करें", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "EPS, PS और पुरानी Illustrator फ़ाइलें → PDF और इमेज, PDF या SVG → EPS/PS।\nSVG और PDF-संगत AI फ़ाइलें इसके बिना भी कन्वर्ट होती हैं।", "engines.package.tesseract.name": "Tesseract (टेक्स्ट पहचान, OCR)", "engines.package.tesseract.description": "स्कैन किए गए PDF और तस्वीरों का टेक्स्ट पढ़ता है: खोजने योग्य PDF, DOCX, TXT।\nहर भाषा पहली बार इस्तेमाल पर डाउनलोड होती है।", + "engines.package.exiftool.name": "ExifTool (मेटाडेटा)", + "engines.package.exiftool.description": "चित्रों, PDF, वीडियो और ऑडियो के सभी मेटाडेटा फ़ील्ड पढ़ता है, और ज़्यादातर तरह की फ़ाइलों में फ़ाइल की सामग्री छुए बिना उन्हें बदलता है।\nमेटाडेटा टूल इसका उपयोग करता है; इसके बिना फ़ील्ड केवल पढ़ने के लिए दिखते हैं।", "engines.package.ffmpeg.name": "FFmpeg (वीडियो और ऑडियो)", "engines.package.ffmpeg.description": "वीडियो और ऑडियो कन्वर्ज़न, वीडियो → GIF या स्टिल इमेज, वीडियो से आवाज़ और GIF → MP4 जोड़ता है।\nकेवल वीडियो और ऑडियो के लिए ज़रूरी।", "engines.package.whisper.name": "Whisper (AI उपशीर्षक)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "इसे Homebrew से इंस्टॉल करें (brew install ffmpeg), फिर “फिर से जाँचें” चुनें।\nया “मेरी कॉपी इस्तेमाल करें…” चुनें और उसका bin फ़ोल्डर चुनें।", "engines.guidance.macos.ghostscript": "इसे Homebrew से इंस्टॉल करें (brew install ghostscript), फिर “फिर से जाँचें” चुनें।\nया “मेरी कॉपी इस्तेमाल करें…” चुनें और उसका bin फ़ोल्डर चुनें।", "engines.guidance.macos.tesseract": "इसे Homebrew से इंस्टॉल करें (brew install tesseract), फिर “फिर से जाँचें” चुनें।\nया “मेरी कॉपी इस्तेमाल करें…” चुनें और उसका bin फ़ोल्डर चुनें।", + "engines.guidance.macos.exiftool": "Homebrew से इंस्टॉल करें (brew install exiftool), फिर «फिर से जाँचें» चुनें।\nया «मेरी कॉपी इस्तेमाल करें…» चुनकर उसका फ़ोल्डर चुनें।", "engines.guidance.macos.calibre": "Filee जो calibre डाउनलोड करता है, उसके लिए macOS 14 या नया चाहिए। calibre-ebook.com से calibre 7.26 इंस्टॉल करें (इस macOS के लिए आख़िरी वर्ज़न), फिर “मेरी कॉपी इस्तेमाल करें…” चुनें और उसका .app फ़ोल्डर चुनें।", "engines.guidance.linux": "अपने डिस्ट्रीब्यूशन के पैकेज मैनेजर से {0} इंस्टॉल करें, फिर “मेरी कॉपी इस्तेमाल करें…” चुनें और उसका प्रोग्राम फ़ोल्डर चुनें।", "engines.guidance.linux.libreoffice": "इसे अपने पैकेज मैनेजर से इंस्टॉल करें (Ubuntu/Debian: sudo apt install libreoffice), फिर “फिर से जाँचें” चुनें।\nHWP खोलने के लिए Java और H2Orestart एक्सटेंशन भी चाहिए।", "engines.guidance.linux.ghostscript": "इसे अपने पैकेज मैनेजर से इंस्टॉल करें (Ubuntu/Debian: sudo apt install ghostscript), फिर “फिर से जाँचें” चुनें।", "engines.guidance.linux.tesseract": "इसे अपने पैकेज मैनेजर से इंस्टॉल करें (Ubuntu/Debian: sudo apt install tesseract-ocr), फिर “फिर से जाँचें” चुनें।", + "engines.guidance.linux.exiftool": "अपने पैकेज मैनेजर से इंस्टॉल करें (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), फिर «फिर से जाँचें» चुनें।", "engines.guidance.windows": "इस PC के लिए Filee के पास {0} का डाउनलोड नहीं है।\n“मेरी कॉपी इस्तेमाल करें…” चुनें और इंस्टॉल की गई कॉपी चुनें।", "engines.use_own_app_folder": "वह फ़ोल्डर चुनें जिसमें {0}.app है", "engines.progress_detail": "{1} में से {0} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/id.json b/src/Filee.App/Assets/i18n/id.json index 549b1f2a7..b6e20844b 100644 --- a/src/Filee.App/Assets/i18n/id.json +++ b/src/Filee.App/Assets/i18n/id.json @@ -243,6 +243,7 @@ "engine.calibre.description": "Format e-book langka (LIT, LRF, CHM, PDB, RB…) dan output Kindle (MOBI, AZW3).\nBila diposisikan di atas pembuat HWPX, Calibre juga membuat PDF dari e-book (jauh lebih cepat untuk buku tebal).", "engine.libreoffice.description": "Membaca format lama dan jarang digunakan: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nJuga menyimpan sebagai DOC, XLS, PPT, ODT, ODP, templat OpenDocument, dan EMF.", "engine.translator.description": "Menerjemahkan dokumen dan e-book secara lokal menggunakan model Ollama offline.\nJuga menerjemahkan subtitle SRT dan WebVTT: hanya teks cue yang dikirim ke model, nomor cue dan stempel waktu tetap dipertahankan.", + "engine.exiftool.description": "Membaca semua bidang metadata gambar, PDF, video, dan audio, serta mengeditnya di sebagian besar jenis file tanpa menyentuh isi file.\nDigunakan oleh alat Metadata; tanpanya, bidang hanya dapat dibaca.", "profile.images": "Gambar", "profile.pdf": "PDF", "profile.office": "Dokumen", @@ -299,6 +300,7 @@ "error.cannot_hide": "Hanya gambar yang dapat dipikselasi atau diburamkan", "error.background_picture": "Gambar untuk latar tidak bisa dibaca", "error.background_picture_changed": "Gambar untuk latar berubah setelah ditampilkan. Tambahkan latar lagi.", + "error.metadata_refused": "Beberapa perubahan metadata tidak dapat ditulis seperti yang diminta", "hide.save": "Sembunyikan & Simpan", "hide.save_next": "Sembunyikan & Berikutnya", "hide.pixelate": "Pikselasi", @@ -415,6 +417,7 @@ "preset.group_flip": "Balik", "preset.annotate": "Anotasi", "preset.background": "Tambah latar", + "preset.metadata": "Metadata", "preset.flip_horizontal": "Balik horizontal", "preset.flip_horizontal.short": "Horizontal", "preset.flip_vertical": "Balik vertikal", @@ -1057,6 +1060,43 @@ "background.color_sage": "Hijau sage", "background.picture_unreadable": "Gambar ini tidak bisa dibaca. Pilih gambar lain, atau gradasi atau warna.", "background.keys": "Enter menyimpan · Esc menutup · Tab menelusuri tampilan · panah memilih atau menggeser penggeser", + "metadata.search": "Cari bidang dan nilai", + "metadata.fields": "{0} bidang", + "metadata.location_warning": "Siapa pun yang memiliki file ini dapat melihat di mana file ini dibuat.", + "metadata.read_only_banner": "Menampilkan semua bidang. Untuk mengeditnya, pasang ExifTool di Pengaturan › Engine.", + "metadata.unwritable": "Menampilkan semua bidang. Filee tidak dapat mengubah metadata jenis file ini.", + "metadata.needs_exiftool": "Bidang file ini tidak dapat dibaca tanpa ExifTool. Pasang di Pengaturan › Engine.", + "metadata.embedded": "File ini juga berisi video atau gambar kedua, dengan metadata sendiri yang tidak ditampilkan dan tidak dapat dihapus di sini. “{0}” membuat salinan bersih tanpanya.", + "metadata.install": "Pasang ExifTool", + "metadata.remove_location": "Hapus lokasi", + "metadata.remove_all": "Hapus semuanya", + "metadata.remove_all_hint": "Orientasi dan profil warna tetap disimpan. Bidang yang terkunci mungkin tetap ada.", + "metadata.changes_one": "1 perubahan", + "metadata.changes": "{0} perubahan", + "metadata.replace": "Ganti file asli", + "metadata.replace_hint": "Salinan menggantikan file asli, dan file asli dipindahkan ke Tong Sampah", + "metadata.save": "Simpan", + "metadata.save_copy": "Simpan salinan", + "metadata.save_next": "Simpan & Berikutnya", + "metadata.add_field": "Tambah bidang", + "metadata.one_per_line": "Satu per baris", + "metadata.remove_field": "Hapus bidang", + "metadata.remove_named": "Hapus {0}", + "metadata.undo": "Urungkan", + "metadata.undo_named": "Kembalikan {0}", + "metadata.spoken_removed": "{0}, dihapus", + "metadata.read_only": "Tidak dapat diubah di sini", + "metadata.pdf_note": "Nilai sebelumnya masih dapat dipulihkan di dalam PDF. Hapus metadata menghapusnya untuk selamanya.", + "metadata.keys": "Enter mengedit bidang atau menyimpan · Shift+Enter memulai baris baru · Esc membatalkan atau menutup · Ctrl+F mencari", + "metadata.empty": "Tidak ada metadata di file ini.", + "metadata.unreadable": "Metadatanya tidak dapat dibaca", + "metadata.group.location": "Lokasi", + "metadata.group.camera": "Kamera", + "metadata.group.exposure": "Eksposur", + "metadata.group.description": "Deskripsi", + "metadata.group.keywords": "Kata kunci", + "metadata.group.document": "Dokumen", + "metadata.group.file": "File", "presets.editor_title": "Edit preset", "presets.delete_confirm": "Hapus “{0}”? Preset ini juga akan dihapus dari semua toolbar dan grup.", "presets.name_required": "Masukkan nama", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "File EPS, PS, dan Illustrator lama → PDF dan gambar, PDF atau SVG → EPS/PS.\nFile SVG dan AI yang kompatibel dengan PDF dapat dikonversi tanpanya.", "engines.package.tesseract.name": "Tesseract (pengenalan teks, OCR)", "engines.package.tesseract.description": "Membaca teks dari PDF hasil pindai dan gambar: PDF yang dapat dicari, DOCX, TXT.\nSetiap bahasa diunduh saat pertama kali digunakan.", + "engines.package.exiftool.name": "ExifTool (metadata)", + "engines.package.exiftool.description": "Membaca semua bidang metadata gambar, PDF, video, dan audio, serta mengeditnya di sebagian besar jenis file tanpa menyentuh isi file.\nDigunakan oleh alat Metadata; tanpanya, bidang hanya dapat dibaca.", "engines.package.ffmpeg.name": "FFmpeg (video dan audio)", "engines.package.ffmpeg.description": "Menambahkan konversi video dan audio, video → GIF atau gambar diam, ekstraksi suara dari video, dan GIF → MP4.\nHanya diperlukan untuk video dan audio.", "engines.package.whisper.name": "Whisper (Subtitle AI)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "Pasang menggunakan Homebrew (brew install ffmpeg), lalu pilih “Periksa lagi”.\nAtau pilih “Gunakan salinan saya…” dan tentukan folder bin miliknya.", "engines.guidance.macos.ghostscript": "Pasang menggunakan Homebrew (brew install ghostscript), lalu pilih “Periksa lagi”.\nAtau pilih “Gunakan salinan saya…” dan tentukan folder bin miliknya.", "engines.guidance.macos.tesseract": "Pasang menggunakan Homebrew (brew install tesseract), lalu pilih “Periksa lagi”.\nAtau pilih “Gunakan salinan saya…” dan tentukan folder bin miliknya.", + "engines.guidance.macos.exiftool": "Pasang menggunakan Homebrew (brew install exiftool), lalu pilih “Periksa lagi”.\nAtau pilih “Gunakan salinan saya…” dan tentukan foldernya.", "engines.guidance.macos.calibre": "Calibre yang diunduh Filee memerlukan macOS 14 atau lebih baru. Pasang Calibre 7.26, versi terakhir untuk versi macOS ini, dari calibre-ebook.com, lalu pilih “Gunakan salinan saya…” dan tentukan folder .app miliknya.", "engines.guidance.linux": "Pasang {0} melalui pengelola paket distribusi Linux Anda, lalu pilih “Gunakan salinan saya…” dan pilih folder programnya.", "engines.guidance.linux.libreoffice": "Pasang melalui pengelola paket Anda (Ubuntu/Debian: sudo apt install libreoffice), lalu pilih “Periksa lagi”.\nUntuk membuka HWP juga memerlukan Java dan ekstensi H2Orestart.", "engines.guidance.linux.ghostscript": "Pasang melalui pengelola paket Anda (Ubuntu/Debian: sudo apt install ghostscript), lalu pilih “Periksa lagi”.", "engines.guidance.linux.tesseract": "Pasang melalui pengelola paket Anda (Ubuntu/Debian: sudo apt install tesseract-ocr), lalu pilih “Periksa lagi”.", + "engines.guidance.linux.exiftool": "Pasang melalui pengelola paket Anda (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), lalu pilih “Periksa lagi”.", "engines.guidance.windows": "Filee tidak memiliki paket unduhan {0} untuk PC ini.\nPilih “Gunakan salinan saya…” dan tentukan salinan yang sudah terpasang.", "engines.use_own_app_folder": "Pilih folder yang memuat {0}.app", "engines.progress_detail": "{0} dari {1} · {2}/d · {3}", diff --git a/src/Filee.App/Assets/i18n/it.json b/src/Filee.App/Assets/i18n/it.json index 72934839b..14d829c67 100644 --- a/src/Filee.App/Assets/i18n/it.json +++ b/src/Filee.App/Assets/i18n/it.json @@ -242,6 +242,7 @@ "engine.calibre.description": "Formati di e-book rari (LIT, LRF, CHM, PDB, RB…) e output per Kindle (MOBI, AZW3).\nPosto sopra il writer HWPX, crea anche i PDF degli e-book (molto più veloce per i libri lunghi).", "engine.libreoffice.description": "Legge formati vecchi e meno comuni: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nSalva anche come DOC, XLS, PPT, ODT, ODP, modelli OpenDocument ed EMF.", "engine.translator.description": "Traduce documenti ed e-book localmente utilizzando modelli Ollama offline.\nTraduce anche i sottotitoli SRT e WebVTT: al modello viene inviato solo il testo delle battute, numerazione e tempi restano invariati.", +"engine.exiftool.description": "Legge tutti i campi dei metadati di immagini, PDF, video e audio, e li modifica nella maggior parte dei tipi di file senza toccarne il contenuto.\nUsato dallo strumento Metadati; senza, i campi sono di sola lettura.", "profile.images": "Immagini", "profile.pdf": "PDF", "profile.office": "Documenti", @@ -298,6 +299,7 @@ "error.cannot_hide": "Solo le immagini possono essere pixellate o sfocate", "error.background_picture": "Impossibile leggere l'immagine da mettere sullo sfondo", "error.background_picture_changed": "L'immagine da mettere sullo sfondo è cambiata dopo essere stata mostrata. Aggiungi di nuovo lo sfondo.", +"error.metadata_refused": "Alcune modifiche ai metadati non si sono potute scrivere come richiesto", "hide.save": "Nascondi e salva", "hide.save_next": "Nascondi e avanti", "hide.pixelate": "Pixella", @@ -414,6 +416,7 @@ "preset.group_flip": "Capovolgi", "preset.annotate": "Annota", "preset.background": "Aggiungi sfondo", +"preset.metadata": "Metadati", "preset.flip_horizontal": "Capovolgi orizzontalmente", "preset.flip_horizontal.short": "Orizzontale", "preset.flip_vertical": "Capovolgi verticalmente", @@ -1056,6 +1059,43 @@ "background.color_sage": "Salvia", "background.picture_unreadable": "Impossibile leggere questa immagine. Scegline un'altra, oppure una sfumatura o un colore.", "background.keys": "Invio salva · Esc chiude · Tab scorre l'aspetto · le frecce scelgono o spostano un cursore", +"metadata.search": "Cerca campi e valori", +"metadata.fields": "{0} campi", +"metadata.location_warning": "Chiunque abbia questo file può vedere dove è stato creato.", +"metadata.read_only_banner": "Sono mostrati tutti i campi. Per modificarli, installa ExifTool in Impostazioni › Motori.", +"metadata.unwritable": "Sono mostrati tutti i campi. Filee non può modificare i metadati di questo tipo di file.", +"metadata.needs_exiftool": "I campi di questo file non si possono leggere senza ExifTool. Installalo in Impostazioni › Motori.", +"metadata.embedded": "Questo file contiene anche un video o una seconda immagine, con metadati propri che qui non si vedono e non si possono eliminare. “{0}” crea una copia pulita, senza di essi.", +"metadata.install": "Installa ExifTool", +"metadata.remove_location": "Rimuovi posizione", +"metadata.remove_all": "Rimuovi tutto", +"metadata.remove_all_hint": "Mantiene l'orientamento e il profilo colore. I campi bloccati possono restare.", +"metadata.changes_one": "1 modifica", +"metadata.changes": "{0} modifiche", +"metadata.replace": "Sostituisci l'originale", +"metadata.replace_hint": "La copia prende il posto dell'originale, e l'originale va nel Cestino", +"metadata.save": "Salva", +"metadata.save_copy": "Salva copia", +"metadata.save_next": "Salva e avanti", +"metadata.add_field": "Aggiungi campo", +"metadata.one_per_line": "Uno per riga", +"metadata.remove_field": "Rimuovi campo", +"metadata.remove_named": "Rimuovi {0}", +"metadata.undo": "Annulla", +"metadata.undo_named": "Ripristina {0}", +"metadata.spoken_removed": "{0}, rimosso", +"metadata.read_only": "Non si può modificare qui", +"metadata.pdf_note": "I valori precedenti restano recuperabili all'interno del PDF. Rimuovi metadati li elimina definitivamente.", +"metadata.keys": "Invio modifica un campo o salva · Maiusc+Invio inizia una nuova riga · Esc annulla o chiude · Ctrl+F cerca", +"metadata.empty": "Nessun metadato in questo file.", +"metadata.unreadable": "Non è stato possibile leggere i suoi metadati", +"metadata.group.location": "Posizione", +"metadata.group.camera": "Fotocamera", +"metadata.group.exposure": "Esposizione", +"metadata.group.description": "Descrizione", +"metadata.group.keywords": "Parole chiave", +"metadata.group.document": "Documento", +"metadata.group.file": "File", "presets.editor_title": "Modifica preset", "presets.delete_confirm": "Eliminare “{0}”? Verrà rimosso anche da ogni barra e gruppo.", "presets.name_required": "Inserisci un nome", @@ -1130,6 +1170,8 @@ "engines.package.ghostscript.description": "EPS, PS e vecchi file Illustrator → PDF e immagini, PDF o SVG → EPS/PS.\nSVG e file AI compatibili con PDF si convertono senza.", "engines.package.tesseract.name": "Tesseract (riconoscimento del testo, OCR)", "engines.package.tesseract.description": "Legge il testo di PDF acquisiti da scansione e di immagini: PDF ricercabile, DOCX, TXT.\nOgni lingua viene scaricata la prima volta che la usi.", +"engines.package.exiftool.name": "ExifTool (metadati)", +"engines.package.exiftool.description": "Legge tutti i campi dei metadati di immagini, PDF, video e audio, e li modifica nella maggior parte dei tipi di file senza toccarne il contenuto.\nUsato dallo strumento Metadati; senza, i campi sono di sola lettura.", "engines.package.ffmpeg.name": "FFmpeg (video e audio)", "engines.package.ffmpeg.description": "Aggiunge la conversione di video e audio, video → GIF o immagine fissa, audio dai video e GIF → MP4.\nServe solo per video e audio.", "engines.package.whisper.name": "Whisper (sottotitoli IA)", @@ -1142,11 +1184,13 @@ "engines.guidance.macos.ffmpeg": "Installalo con Homebrew (brew install ffmpeg), poi scegli “Controlla di nuovo”.\nOppure scegli “Usa la mia copia…” e seleziona la sua cartella bin.", "engines.guidance.macos.ghostscript": "Installalo con Homebrew (brew install ghostscript), poi scegli “Controlla di nuovo”.\nOppure scegli “Usa la mia copia…” e seleziona la sua cartella bin.", "engines.guidance.macos.tesseract": "Installalo con Homebrew (brew install tesseract), poi scegli “Controlla di nuovo”.\nOppure scegli “Usa la mia copia…” e seleziona la sua cartella bin.", +"engines.guidance.macos.exiftool": "Installalo con Homebrew (brew install exiftool), poi scegli “Controlla di nuovo”.\nOppure scegli “Usa la mia copia…” e seleziona la sua cartella.", "engines.guidance.macos.calibre": "Il calibre che scarica Filee richiede macOS 14 o successivo. Installa calibre 7.26, l'ultima versione per questo macOS, da calibre-ebook.com, poi scegli “Usa la mia copia…” e seleziona la sua cartella .app.", "engines.guidance.linux": "Installa {0} con il gestore di pacchetti della tua distribuzione, poi scegli “Usa la mia copia…” e seleziona la cartella del suo programma.", "engines.guidance.linux.libreoffice": "Installalo con il tuo gestore di pacchetti (Ubuntu/Debian: sudo apt install libreoffice), poi scegli “Controlla di nuovo”.\nPer aprire HWP servono anche Java e l'estensione H2Orestart.", "engines.guidance.linux.ghostscript": "Installalo con il tuo gestore di pacchetti (Ubuntu/Debian: sudo apt install ghostscript), poi scegli “Controlla di nuovo”.", "engines.guidance.linux.tesseract": "Installalo con il tuo gestore di pacchetti (Ubuntu/Debian: sudo apt install tesseract-ocr), poi scegli “Controlla di nuovo”.", +"engines.guidance.linux.exiftool": "Installalo con il tuo gestore di pacchetti (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), poi scegli “Controlla di nuovo”.", "engines.guidance.windows": "Filee non ha un download di {0} per questo PC.\nScegli “Usa la mia copia…” e seleziona una copia installata.", "engines.use_own_app_folder": "Scegli la cartella che contiene {0}.app", "engines.progress_detail": "{0} di {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/ja.json b/src/Filee.App/Assets/i18n/ja.json index e8604dd61..92bd59bd0 100644 --- a/src/Filee.App/Assets/i18n/ja.json +++ b/src/Filee.App/Assets/i18n/ja.json @@ -243,6 +243,7 @@ "engine.calibre.description": "一般的でない電子書籍形式(LIT、LRF、CHM、PDB、RBなど)を変換し、Kindle用のMOBI、AZW3形式で保存できます。\n「HWPX writer」より上に置くと、電子書籍のPDF作成もCalibreが担当します(長い本では大幅に速くなります)。", "engine.libreoffice.description": "古い形式や一般的でない形式(DOC、PPT、Pages、Keynote、Numbers、WordPerfect、ClarisWorks、Publisher、Visioなど)を読み込みます。\nDOC、XLS、PPT、ODT、ODP、OpenDocumentテンプレート、EMF形式での保存にも対応します。", "engine.translator.description": "オフラインのOllamaモデルを使用して、ローカルでドキュメントや電子書籍を翻訳します。\nSRT・WebVTT字幕も翻訳します。モデルに送るのは字幕のテキストだけで、字幕番号とタイムスタンプはそのまま保持されます。", + "engine.exiftool.description": "画像、PDF、動画、音声のすべてのメタデータ項目を読み取り、ほとんどの種類のファイルでは内容に触れずに編集します。\nメタデータツールが使います。ない場合、項目は読み取り専用で表示されます。", "profile.images": "画像", "profile.pdf": "PDF", "profile.office": "文書", @@ -299,6 +300,7 @@ "error.cannot_hide": "モザイクとぼかしは画像でのみ使えます", "error.background_picture": "背景に置く画像を読み込めません", "error.background_picture_changed": "背景に置く画像が、表示された後に変更されました。もう一度背景を追加してください。", + "error.metadata_refused": "メタデータの一部の変更を指定どおりに書き込めませんでした", "hide.save": "隠して保存", "hide.save_next": "隠して次へ", "hide.pixelate": "モザイク", @@ -415,6 +417,7 @@ "preset.group_flip": "反転", "preset.annotate": "注釈", "preset.background": "背景を追加", + "preset.metadata": "メタデータ", "preset.flip_horizontal": "水平方向に反転", "preset.flip_horizontal.short": "水平", "preset.flip_vertical": "垂直方向に反転", @@ -1057,6 +1060,43 @@ "background.color_sage": "セージ", "background.picture_unreadable": "この画像は読み込めません。別の画像か、グラデーションか色を選んでください。", "background.keys": "Enter で保存 · Esc で閉じる · Tab で見た目の項目を移動 · 矢印キーで選択やスライダーの調整", + "metadata.search": "項目と値を検索", + "metadata.fields": "{0} 項目", + "metadata.location_warning": "このファイルを持つ人は誰でも撮影場所がわかります。", + "metadata.read_only_banner": "すべての項目を表示しています。編集するには、設定 › エンジンで ExifTool をインストールしてください。", + "metadata.unwritable": "すべての項目を表示しています。Filee はこの種類のファイルのメタデータを変更できません。", + "metadata.needs_exiftool": "このファイルの項目は ExifTool がないと読み取れません。設定 › エンジンでインストールしてください。", + "metadata.embedded": "このファイルには動画または 2 枚目の画像も含まれており、その独自のメタデータはここでは表示も削除もできません。「{0}」を使うと、それらを含まないクリーンなコピーを作成できます。", + "metadata.install": "ExifTool をインストール", + "metadata.remove_location": "位置情報を削除", + "metadata.remove_all": "すべて削除", + "metadata.remove_all_hint": "向きとカラープロファイルは残します。ロックされた項目は残る場合があります。", + "metadata.changes_one": "1 件の変更", + "metadata.changes": "{0} 件の変更", + "metadata.replace": "元のファイルを置き換える", + "metadata.replace_hint": "コピーが元のファイルと置き換わり、元のファイルはごみ箱に移動します", + "metadata.save": "保存", + "metadata.save_copy": "コピーを保存", + "metadata.save_next": "保存して次へ", + "metadata.add_field": "項目を追加", + "metadata.one_per_line": "1 行に 1 つ", + "metadata.remove_field": "項目を削除", + "metadata.remove_named": "{0} を削除", + "metadata.undo": "元に戻す", + "metadata.undo_named": "{0} を元に戻す", + "metadata.spoken_removed": "{0}、削除済み", + "metadata.read_only": "ここでは変更できません", + "metadata.pdf_note": "以前の値は PDF 内に復元可能な形で残ります。「メタデータを削除」で完全に消せます。", + "metadata.keys": "Enter で項目を編集または保存 · Shift+Enter で改行 · Esc で取り消しまたは閉じる · Ctrl+F で検索", + "metadata.empty": "このファイルにはメタデータがありません。", + "metadata.unreadable": "メタデータを読み取れませんでした", + "metadata.group.location": "位置情報", + "metadata.group.camera": "カメラ", + "metadata.group.exposure": "露出", + "metadata.group.description": "説明", + "metadata.group.keywords": "キーワード", + "metadata.group.document": "文書", + "metadata.group.file": "ファイル", "presets.editor_title": "プリセットを編集", "presets.delete_confirm": "「{0}」を削除しますか?すべてのドーナツメニューとグループからも削除されます。", "presets.name_required": "名前を入力してください", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "EPS、PS、古いIllustratorファイル → PDF・画像、PDF・SVG → EPS・PSの変換に使います。\nSVGやPDF互換のAIファイルは、インストールしなくても変換できます。", "engines.package.tesseract.name": "Tesseract(文字認識、OCR)", "engines.package.tesseract.description": "スキャンした PDF や画像の文字を読み取ります:検索可能な PDF、DOCX、TXT。\n言語は初めて使うときにダウンロードされます。", + "engines.package.exiftool.name": "ExifTool(メタデータ)", + "engines.package.exiftool.description": "画像、PDF、動画、音声のすべてのメタデータ項目を読み取り、ほとんどの種類のファイルでは内容に触れずに編集します。\nメタデータツールが使います。ない場合、項目は読み取り専用で表示されます。", "engines.package.ffmpeg.name": "FFmpeg(動画・音声)", "engines.package.ffmpeg.description": "動画・音声の変換、動画 → GIF・静止画、動画からの音声抽出、GIF → MP4に対応します。\n動画と音声の変換にのみ必要です。", "engines.package.whisper.name": "Whisper (AI字幕)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "Homebrewでffmpegをインストール(brew install ffmpeg)してから、「再確認」を選んでください。\nまたは「手元のプログラムを使う…」を選び、binフォルダーを指定してください。", "engines.guidance.macos.ghostscript": "Homebrewでghostscriptをインストール(brew install ghostscript)してから、「再確認」を選んでください。\nまたは「手元のプログラムを使う…」を選び、binフォルダーを指定してください。", "engines.guidance.macos.tesseract": "HomebrewでTesseractをインストール(brew install tesseract)してから、「再確認」を選んでください。\nまたは「手元のプログラムを使う…」を選び、binフォルダーを指定してください。", + "engines.guidance.macos.exiftool": "Homebrew でインストール(brew install exiftool)してから「再確認」を選んでください。\nまたは「自分のコピーを使う…」でそのフォルダーを選んでください。", "engines.guidance.macos.calibre": "FileeがダウンロードするcalibreにはmacOS 14以降が必要です。calibre-ebook.comからこのmacOS用の最終版calibre 7.26をインストールし、「手元のプログラムを使う…」を選んで.appフォルダーを指定してください。", "engines.guidance.linux": "ディストリビューションのパッケージマネージャーで{0}をインストールしてから、「手元のプログラムを使う…」を選び、プログラムのフォルダーを指定してください。", "engines.guidance.linux.libreoffice": "パッケージマネージャーでインストール(Ubuntu/Debian:sudo apt install libreoffice)してから、「再確認」を選んでください。\nHWPを開くには、JavaとH2Orestart拡張機能も必要です。", "engines.guidance.linux.ghostscript": "パッケージマネージャーでインストール(Ubuntu/Debian:sudo apt install ghostscript)してから、「再確認」を選んでください。", "engines.guidance.linux.tesseract": "パッケージマネージャーでインストール(Ubuntu/Debian:sudo apt install tesseract-ocr)してから、「再確認」を選んでください。", + "engines.guidance.linux.exiftool": "パッケージマネージャーでインストール(Ubuntu/Debian: sudo apt install libimage-exiftool-perl)してから「再確認」を選んでください。", "engines.guidance.windows": "このPC向けの{0}はFileeからダウンロードできません。\n「手元のプログラムを使う…」を選び、インストール済みのものを指定してください。", "engines.use_own_app_folder": "{0}.appを含むフォルダーを選択してください", "engines.progress_detail": "{0}/{1} · {2}/秒 · {3}", diff --git a/src/Filee.App/Assets/i18n/ko.json b/src/Filee.App/Assets/i18n/ko.json index 1bc34d2ba..ca761c5e2 100644 --- a/src/Filee.App/Assets/i18n/ko.json +++ b/src/Filee.App/Assets/i18n/ko.json @@ -250,6 +250,7 @@ "engine.calibre.description": "드문 전자책 형식(LIT, LRF, CHM, PDB, RB…)과 킨들용 출력(MOBI, AZW3).\nHWPX writer보다 위에 두면 전자책 PDF도 만들어요(긴 책은 훨씬 빨라요).", "engine.libreoffice.description": "DOC, PPT, Pages, Keynote, Numbers, 워드퍼펙트, ClarisWorks, Publisher, Visio 같은 오래되었거나 드문 형식을 읽어요.\nDOC·XLS·PPT·ODT·ODP, 오픈도큐먼트 서식 파일, EMF로 저장도 해요.", "engine.translator.description": "오프라인 Ollama 모델을 사용해 문서와 전자책을 컴퓨터에서 번역해요.\nSRT·WebVTT 자막도 번역해요. 모델에는 대사 텍스트만 보내고, 자막 번호와 시간은 그대로 둬요.", + "engine.exiftool.description": "사진, PDF, 동영상, 소리의 모든 메타데이터 항목을 읽고, 대부분의 파일 종류에서 파일 내용은 건드리지 않고 편집해요.\n메타데이터 도구가 사용해요. 없으면 항목을 읽기 전용으로 보여 줘요.", "profile.images": "이미지", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "모자이크와 흐림은 이미지에만 할 수 있어요", "error.background_picture": "배경에 넣을 사진을 읽을 수 없습니다", "error.background_picture_changed": "배경에 넣을 사진이 표시된 뒤에 바뀌었습니다. 배경을 다시 추가하세요.", + "error.metadata_refused": "메타데이터 변경 중 일부를 요청한 대로 쓸 수 없었어요", "hide.save": "가리고 저장", "hide.save_next": "가리고 다음으로", "hide.pixelate": "모자이크", @@ -425,6 +427,7 @@ "preset.group_flip": "뒤집기", "preset.annotate": "주석", "preset.background": "배경 추가", + "preset.metadata": "메타데이터", "preset.flip_horizontal": "좌우 반전", "preset.flip_horizontal.short": "좌우", "preset.flip_vertical": "상하 반전", @@ -1072,6 +1075,43 @@ "background.color_sage": "세이지", "background.picture_unreadable": "이 사진을 읽을 수 없습니다. 다른 사진이나 그라데이션, 색을 고르세요.", "background.keys": "Enter 저장 · Esc 닫기 · Tab으로 모양 항목 이동 · 화살표로 선택하거나 슬라이더 이동", + "metadata.search": "항목과 값 검색", + "metadata.fields": "항목 {0}개", + "metadata.location_warning": "이 파일을 가진 사람은 누구나 만든 장소를 알 수 있어요.", + "metadata.read_only_banner": "모든 항목을 보여 줘요. 편집하려면 설정 › 엔진에서 ExifTool을 설치하세요.", + "metadata.unwritable": "모든 항목을 보여 줘요. Filee는 이 종류의 파일의 메타데이터를 바꿀 수 없어요.", + "metadata.needs_exiftool": "이 파일의 항목은 ExifTool 없이는 읽을 수 없어요. 설정 › 엔진에서 설치하세요.", + "metadata.embedded": "이 파일에는 동영상이나 두 번째 사진도 들어 있고, 그 자체의 메타데이터는 여기서 보이지 않으며 지울 수도 없어요. “{0}”(으)로 이를 뺀 깨끗한 사본을 만들 수 있어요.", + "metadata.install": "ExifTool 설치", + "metadata.remove_location": "위치 지우기", + "metadata.remove_all": "모두 지우기", + "metadata.remove_all_hint": "방향과 색 프로필은 남겨요. 잠긴 항목은 남을 수 있어요.", + "metadata.changes_one": "변경 1개", + "metadata.changes": "변경 {0}개", + "metadata.replace": "원본 바꾸기", + "metadata.replace_hint": "사본이 원본을 대신하고, 원본은 휴지통으로 옮겨져요", + "metadata.save": "저장", + "metadata.save_copy": "사본 저장", + "metadata.save_next": "저장하고 다음", + "metadata.add_field": "항목 추가", + "metadata.one_per_line": "한 줄에 하나씩", + "metadata.remove_field": "항목 지우기", + "metadata.remove_named": "{0} 제거", + "metadata.undo": "되돌리기", + "metadata.undo_named": "{0} 되돌리기", + "metadata.spoken_removed": "{0}, 제거됨", + "metadata.read_only": "여기서는 바꿀 수 없어요", + "metadata.pdf_note": "예전 값은 PDF 안에 복구할 수 있게 남아요. 메타데이터 제거를 쓰면 완전히 지워져요.", + "metadata.keys": "Enter: 항목 편집 또는 저장 · Shift+Enter: 줄 바꿈 · Esc: 취소 또는 닫기 · Ctrl+F: 검색", + "metadata.empty": "이 파일에는 메타데이터가 없어요.", + "metadata.unreadable": "메타데이터를 읽을 수 없어요", + "metadata.group.location": "위치", + "metadata.group.camera": "카메라", + "metadata.group.exposure": "노출", + "metadata.group.description": "설명", + "metadata.group.keywords": "키워드", + "metadata.group.document": "문서", + "metadata.group.file": "파일", "presets.editor_title": "프리셋 편집", "presets.delete_confirm": "“{0}”을(를) 삭제할까요? 모든 툴바와 묶음에서도 빠져요.", "presets.name_required": "이름을 입력하세요", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS·PS와 예전 일러스트레이터 파일을 PDF·이미지로, PDF·SVG를 EPS·PS로 바꿔요.\nSVG와 PDF 호환 AI 파일은 없어도 돼요.", "engines.package.tesseract.name": "Tesseract (텍스트 인식, OCR)", "engines.package.tesseract.description": "스캔한 PDF와 사진의 글자를 읽어요: 검색 가능한 PDF, DOCX, TXT.\n언어는 처음 쓸 때 내려받아요.", + "engines.package.exiftool.name": "ExifTool (메타데이터)", + "engines.package.exiftool.description": "사진, PDF, 동영상, 소리의 모든 메타데이터 항목을 읽고, 대부분의 파일 종류에서 파일 내용은 건드리지 않고 편집해요.\n메타데이터 도구가 사용해요. 없으면 항목을 읽기 전용으로 보여 줘요.", "engines.package.ffmpeg.name": "FFmpeg (동영상·오디오)", "engines.package.ffmpeg.description": "동영상·오디오 변환, 동영상 → GIF·정지 이미지, 소리 추출, GIF → MP4를 추가해요.\n동영상과 오디오에만 필요해요.", "engines.package.whisper.name": "Whisper (AI 자막)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Homebrew로 설치(brew install ffmpeg)한 뒤 “다시 확인”을 누르세요.\n또는 “내 프로그램 쓰기…”로 bin 폴더를 고르세요.", "engines.guidance.macos.ghostscript": "Homebrew로 설치(brew install ghostscript)한 뒤 “다시 확인”을 누르세요.\n또는 “내 프로그램 쓰기…”로 bin 폴더를 고르세요.", "engines.guidance.macos.tesseract": "Homebrew로 설치(brew install tesseract)한 뒤 “다시 확인”을 누르세요.\n또는 “내 프로그램 쓰기…”로 bin 폴더를 고르세요.", + "engines.guidance.macos.exiftool": "Homebrew로 설치(brew install exiftool)한 뒤 “다시 확인”을 고르세요.\n또는 “내 사본 사용…”을 골라 그 폴더를 선택하세요.", "engines.guidance.macos.calibre": "Filee가 받는 calibre는 macOS 14 이상이 필요해요. calibre-ebook.com에서 이 macOS용 마지막 버전인 calibre 7.26을 설치한 뒤 “내 프로그램 쓰기…”로 .app 폴더를 선택해 주세요.", "engines.guidance.linux": "배포판의 패키지 관리자로 {0}을(를) 설치한 뒤 “내 프로그램 쓰기…”로 프로그램 폴더를 선택해 주세요.", "engines.guidance.linux.libreoffice": "패키지 관리자로 설치(Ubuntu/Debian: sudo apt install libreoffice)한 뒤 “다시 확인”을 누르세요.\nHWP를 열려면 Java와 H2Orestart 확장도 필요해요.", "engines.guidance.linux.ghostscript": "패키지 관리자로 설치(Ubuntu/Debian: sudo apt install ghostscript)한 뒤 “다시 확인”을 누르세요.", "engines.guidance.linux.tesseract": "패키지 관리자로 설치(Ubuntu/Debian: sudo apt install tesseract-ocr)한 뒤 “다시 확인”을 누르세요.", + "engines.guidance.linux.exiftool": "패키지 관리자로 설치(Ubuntu/Debian: sudo apt install libimage-exiftool-perl)한 뒤 “다시 확인”을 고르세요.", "engines.guidance.windows": "이 PC용 {0} 다운로드는 없어요.\n“내 프로그램 쓰기…”로 설치된 프로그램을 선택해 주세요.", "engines.use_own_app_folder": "{0}.app이 들어 있는 폴더 선택", "engines.progress_detail": "{0} / {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/ne.json b/src/Filee.App/Assets/i18n/ne.json index 2d3b48c38..c3878f260 100644 --- a/src/Filee.App/Assets/i18n/ne.json +++ b/src/Filee.App/Assets/i18n/ne.json @@ -243,6 +243,7 @@ "engine.calibre.description": "दुर्लभ इ-पुस्तक ढाँचाहरू (LIT, LRF, CHM, PDB, RB…) र Kindle आउटपुट (MOBI, AZW3)।\nHWPX writer भन्दा माथि राख्दा, यसले इ-पुस्तकका PDF पनि बनाउँछ (लामो पुस्तकका लागि धेरै छिटो)।", "engine.libreoffice.description": "पुराना र कम प्रचलित ढाँचाहरू पढ्छ: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nसाथै DOC, XLS, PPT, ODT, ODP, OpenDocument टेम्प्लेट र EMF को रूपमा सुरक्षित गर्दछ।", "engine.translator.description": "अफलाइन ओलामा मोडेलहरू प्रयोग गरेर कागजात र ई-पुस्तकहरू स्थानीय रूपमा अनुवाद गर्दछ।\nSRT र WebVTT उपशीर्षकहरू पनि अनुवाद गर्दछ: मोडेललाई क्यूको पाठ मात्र पठाइन्छ, क्यू नम्बर र टाइमस्ट्याम्प जस्ताको तस्तै राखिन्छ।", + "engine.exiftool.description": "चित्र, PDF, भिडियो र अडियोका सबै मेटाडाटा फिल्ड पढ्छ, र धेरैजसो प्रकारका फाइलमा फाइलको सामग्री नछोई तिनलाई सम्पादन गर्छ।\nमेटाडाटा उपकरणले यसलाई प्रयोग गर्छ; यसबिना फिल्डहरू पढ्न मात्र मिल्ने गरी देखिन्छन्।", "profile.images": "छविहरू", "profile.pdf": "PDF", "profile.office": "कार्यालय कागजातहरू", @@ -299,6 +300,7 @@ "error.cannot_hide": "पिक्सलेट र धमिलो पार्ने कार्य केवल छविहरूमा मात्र काम गर्छ", "error.background_picture": "पछाडि राख्ने तस्बिर पढ्न सकिँदैन", "error.background_picture_changed": "पछाडि राख्ने तस्बिर देखाइएपछि बदलियो। पृष्ठभूमि फेरि थप्नुहोस्।", + "error.metadata_refused": "मेटाडाटाका केही परिवर्तनहरू भनिएअनुसार लेख्न सकिएन", "hide.save": "लुकाएर सुरक्षित गर्नुहोस्", "hide.save_next": "लुकाएर अर्कोमा जानुहोस्", "hide.pixelate": "पिक्सलेट", @@ -415,6 +417,7 @@ "preset.group_flip": "उल्ट्याउनुहोस्", "preset.annotate": "टिप्पणी", "preset.background": "पृष्ठभूमि थप्नुहोस्", + "preset.metadata": "मेटाडेटा", "preset.flip_horizontal": "तेर्सो उल्ट्याउनुहोस्", "preset.flip_horizontal.short": "तेर्सो", "preset.flip_vertical": "ठाडो उल्ट्याउनुहोस्", @@ -1057,6 +1060,43 @@ "background.color_sage": "सेज", "background.picture_unreadable": "यो तस्बिर पढ्न सकिँदैन। अर्को तस्बिर, वा ग्रेडियन्ट वा रङ छान्नुहोस्।", "background.keys": "Enter ले सेभ गर्छ · Esc ले बन्द गर्छ · Tab ले रूपका विकल्पहरूमा जान्छ · तीरले छान्छ वा स्लाइडर सार्छ", + "metadata.search": "फिल्ड र मान खोज्नुहोस्", + "metadata.fields": "{0} फिल्ड", + "metadata.location_warning": "यो फाइल भएका जो कोहीले यो कहाँ बनेको हो देख्न सक्छन्।", + "metadata.read_only_banner": "सबै फिल्ड देखाइँदैछ। सम्पादन गर्न सेटिङ › इन्जिनमा ExifTool स्थापना गर्नुहोस्।", + "metadata.unwritable": "सबै फिल्ड देखाइँदैछ। Filee ले यस प्रकारको फाइलको मेटाडाटा बदल्न सक्दैन।", + "metadata.needs_exiftool": "यो फाइलका फिल्डहरू ExifTool बिना पढ्न सकिँदैन। सेटिङ › इन्जिनमा यसलाई स्थापना गर्नुहोस्।", + "metadata.embedded": "यो फाइलमा भिडियो वा दोस्रो चित्र पनि छ, जसको आफ्नै मेटाडाटा यहाँ देखिँदैन र हटाउन सकिँदैन। “{0}” ले ती बिना सफा प्रतिलिपि बनाउँछ।", + "metadata.install": "ExifTool स्थापना गर्नुहोस्", + "metadata.remove_location": "स्थान हटाउनुहोस्", + "metadata.remove_all": "सबै हटाउनुहोस्", + "metadata.remove_all_hint": "दिशा र रङ प्रोफाइल राख्छ। लक गरिएका फिल्डहरू रहन सक्छन्।", + "metadata.changes_one": "1 परिवर्तन", + "metadata.changes": "{0} परिवर्तन", + "metadata.replace": "मूल फाइल बदल्नुहोस्", + "metadata.replace_hint": "प्रतिलिपिले मूल फाइलको ठाउँ लिन्छ, र मूल फाइल रिसाइकल बिन / ट्र्यासमा जान्छ", + "metadata.save": "सुरक्षित गर्नुहोस्", + "metadata.save_copy": "प्रतिलिपि सुरक्षित गर्नुहोस्", + "metadata.save_next": "सुरक्षित गरेर अर्को", + "metadata.add_field": "फिल्ड थप्नुहोस्", + "metadata.one_per_line": "प्रत्येक लाइनमा एउटा", + "metadata.remove_field": "फिल्ड हटाउनुहोस्", + "metadata.remove_named": "{0} हटाउनुहोस्", + "metadata.undo": "पूर्ववत गर्नुहोस्", + "metadata.undo_named": "{0} फर्काउनुहोस्", + "metadata.spoken_removed": "{0}, हटाइयो", + "metadata.read_only": "यहाँ बदल्न सकिँदैन", + "metadata.pdf_note": "पहिलेका मान PDF भित्र फिर्ता ल्याउन सकिने गरी रहन्छन्। «मेटाडेटा हटाउनुहोस्» ले तिनलाई पूर्ण रूपमा हटाउँछ।", + "metadata.keys": "Enter ले फिल्ड सम्पादन गर्छ वा सुरक्षित गर्छ · Shift+Enter ले नयाँ लाइन सुरु गर्छ · Esc ले रद्द गर्छ वा बन्द गर्छ · Ctrl+F ले खोज्छ", + "metadata.empty": "यो फाइलमा मेटाडेटा छैन।", + "metadata.unreadable": "यसको मेटाडाटा पढ्न सकिएन", + "metadata.group.location": "स्थान", + "metadata.group.camera": "क्यामेरा", + "metadata.group.exposure": "एक्सपोजर", + "metadata.group.description": "विवरण", + "metadata.group.keywords": "कुञ्जी शब्द", + "metadata.group.document": "कागजात", + "metadata.group.file": "फाइल", "presets.editor_title": "प्रिसेट सम्पादन गर्नुहोस्", "presets.delete_confirm": "“{0}” हटाउने? यो प्रत्येक टुलबार र समूहबाट पनि हटाइनेछ।", "presets.name_required": "नाम प्रविष्ट गर्नुहोस्", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "EPS, PS र पुराना Illustrator फाइलहरू → PDF र छविहरू, PDF वा SVG → EPS/PS।\nSVG र PDF-मिल्दो AI फाइलहरू यस बिना पनि रूपान्तरण हुन्छन्।", "engines.package.tesseract.name": "Tesseract (पाठ पहिचान, OCR)", "engines.package.tesseract.description": "स्क्यान गरिएका PDF र तस्बिरको पाठ पढ्छ: खोज्न मिल्ने PDF, DOCX, TXT।\nहरेक भाषा पहिलो पटक प्रयोग गर्दा डाउनलोड हुन्छ।", + "engines.package.exiftool.name": "ExifTool (मेटाडेटा)", + "engines.package.exiftool.description": "चित्र, PDF, भिडियो र अडियोका सबै मेटाडाटा फिल्ड पढ्छ, र धेरैजसो प्रकारका फाइलमा फाइलको सामग्री नछोई तिनलाई सम्पादन गर्छ।\nमेटाडाटा उपकरणले यसलाई प्रयोग गर्छ; यसबिना फिल्डहरू पढ्न मात्र मिल्ने गरी देखिन्छन्।", "engines.package.ffmpeg.name": "FFmpeg (भिडियो र अडियो)", "engines.package.ffmpeg.description": "भिडियो र अडियो रूपान्तरण, भिडियो → GIF वा स्थिर छवि, भिडियोबाट ध्वनि र GIF → MP4 थप्छ।\nकेवल भिडियो र अडियोका लागि मात्र आवश्यक छ।", "engines.package.whisper.name": "Whisper (AI उपशीर्षकहरू)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "यसलाई Homebrew बाट स्थापना गर्नुहोस् (brew install ffmpeg), त्यसपछि \"फेरि जाँच गर्नुहोस्\" रोज्नुहोस्।\nवा “मेरो प्रतिलिपि प्रयोग गर्नुहोस्…” रोजेर यसको bin फोल्डर चयन गर्नुहोस्।", "engines.guidance.macos.ghostscript": "यसलाई Homebrew बाट स्थापना गर्नुहोस् (brew install ghostscript), त्यसपछि \"फेरि जाँच गर्नुहोस्\" रोज्नुहोस्।\nवा “मेरो प्रतिलिपि प्रयोग गर्नुहोस्…” रोजेर यसको bin फोल्डर चयन गर्नुहोस्।", "engines.guidance.macos.tesseract": "यसलाई Homebrew बाट स्थापना गर्नुहोस् (brew install tesseract), त्यसपछि \"फेरि जाँच गर्नुहोस्\" रोज्नुहोस्।\nवा “मेरो प्रतिलिपि प्रयोग गर्नुहोस्…” रोजेर यसको bin फोल्डर चयन गर्नुहोस्।", + "engines.guidance.macos.exiftool": "Homebrew बाट स्थापना गर्नुहोस् (brew install exiftool), त्यसपछि «फेरि जाँच्नुहोस्» छान्नुहोस्।\nवा «मेरो प्रतिलिपि प्रयोग गर्नुहोस्…» छानेर यसको फोल्डर छान्नुहोस्।", "engines.guidance.macos.calibre": "यसलाई Homebrew बाट स्थापना गर्नुहोस् (brew install --cask calibre), वा Calibre.app समावेश भएको फोल्डर रोज्नुहोस्।", "engines.guidance.linux": "आफ्नो वितरणको प्याकेज प्रबन्धकबाट {0} स्थापना गर्नुहोस्, त्यसपछि “मेरो प्रतिलिपि प्रयोग गर्नुहोस्…” रोजेर यसको प्रोग्राम फोल्डर चयन गर्नुहोस्।", "engines.guidance.linux.libreoffice": "यसलाई प्याकेज प्रबन्धकबाट स्थापना गर्नुहोस् (sudo apt install libreoffice), त्यसपछि \"फेरि जाँच गर्नुहोस्\" रोज्नुहोस्।", "engines.guidance.linux.ghostscript": "यसलाई प्याकेज प्रबन्धकबाट स्थापना गर्नुहोस् (sudo apt install ghostscript), त्यसपछि \"फेरि जाँच गर्नुहोस्\" रोज्नुहोस्।", "engines.guidance.linux.tesseract": "यसलाई प्याकेज प्रबन्धकबाट स्थापना गर्नुहोस् (sudo apt install tesseract-ocr), त्यसपछि \"फेरि जाँच गर्नुहोस्\" रोज्नुहोस्।", + "engines.guidance.linux.exiftool": "प्याकेज म्यानेजरबाट स्थापना गर्नुहोस् (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), त्यसपछि «फेरि जाँच्नुहोस्» छान्नुहोस्।", "engines.guidance.windows": "यस कम्प्युटरका लागि Filee सँग {0} को डाउनलोड छैन।\n“मेरो प्रतिलिपि प्रयोग गर्नुहोस्…” रोज्नुहोस् र स्थापना गरिएको प्रतिलिपि चयन गर्नुहोस्।", "engines.use_own_app_folder": "{0}.app समावेश भएको फोल्डर रोज्नुहोस्", "engines.progress_detail": "{1} मध्ये {0} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/pl.json b/src/Filee.App/Assets/i18n/pl.json index ba717bcbd..667c17dd1 100644 --- a/src/Filee.App/Assets/i18n/pl.json +++ b/src/Filee.App/Assets/i18n/pl.json @@ -250,6 +250,7 @@ "engine.calibre.description": "Rzadkie formaty e-booków (LIT, LRF, CHM, PDB, RB…) i zapis dla Kindle (MOBI, AZW3).\nUmieszczony nad HWPX writer tworzy też PDF-y e-booków (znacznie szybciej przy długich książkach).", "engine.libreoffice.description": "Odczytuje starsze i rzadsze formaty: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nZapisuje też jako DOC, XLS, PPT, ODT, ODP, szablony OpenDocument i EMF.", "engine.translator.description": "Tłumaczy dokumenty i e-booki lokalnie za pomocą modeli offline Ollama.\nTłumaczy też napisy SRT i WebVTT: do modelu trafia tylko tekst napisów, a numery i znaczniki czasu pozostają bez zmian.", + "engine.exiftool.description": "Odczytuje wszystkie pola metadanych obrazów, PDF-ów, wideo i dźwięku oraz edytuje je w większości rodzajów plików, nie zmieniając ich zawartości.\nUżywa go narzędzie Metadane; bez niego pola są tylko do odczytu.", "profile.images": "Obrazy", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "Pikselizować i rozmywać można tylko obrazy", "error.background_picture": "Nie można odczytać obrazu do umieszczenia w tle", "error.background_picture_changed": "Obraz do umieszczenia w tle zmienił się po wyświetleniu. Dodaj tło ponownie.", + "error.metadata_refused": "Niektórych zmian metadanych nie udało się zapisać tak, jak chciano", "hide.save": "Ukryj i zapisz", "hide.save_next": "Ukryj i dalej", "hide.pixelate": "Pikselizacja", @@ -425,6 +427,7 @@ "preset.group_flip": "Odbij", "preset.annotate": "Adnotacje", "preset.background": "Dodaj tło", + "preset.metadata": "Metadane", "preset.flip_horizontal": "Odbij w poziomie", "preset.flip_horizontal.short": "Poziomo", "preset.flip_vertical": "Odbij w pionie", @@ -1072,6 +1075,43 @@ "background.color_sage": "Szałwia", "background.picture_unreadable": "Nie można odczytać tego obrazu. Wybierz inny albo gradient lub kolor.", "background.keys": "Enter zapisuje · Esc zamyka · Tab przechodzi przez wygląd · strzałki wybierają lub przesuwają suwak", + "metadata.search": "Szukaj pól i wartości", + "metadata.fields": "Pola: {0}", + "metadata.location_warning": "Każdy, kto ma ten plik, zobaczy, gdzie powstał.", + "metadata.read_only_banner": "Pokazane są wszystkie pola. Aby je edytować, zainstaluj ExifTool w Ustawienia › Silniki.", + "metadata.unwritable": "Pokazane są wszystkie pola. Filee nie może zmienić metadanych tego rodzaju pliku.", + "metadata.needs_exiftool": "Pól tego pliku nie można odczytać bez ExifTool. Zainstaluj go w Ustawienia › Silniki.", + "metadata.embedded": "Ten plik zawiera też wideo lub drugi obraz z własnymi metadanymi, których nie widać tutaj i nie można tu usunąć. „{0}” tworzy czystą kopię bez nich.", + "metadata.install": "Zainstaluj ExifTool", + "metadata.remove_location": "Usuń lokalizację", + "metadata.remove_all": "Usuń wszystko", + "metadata.remove_all_hint": "Zachowuje orientację i profil kolorów. Zablokowane pola mogą pozostać.", + "metadata.changes_one": "1 zmiana", + "metadata.changes": "Zmiany: {0}", + "metadata.replace": "Zastąp oryginał", + "metadata.replace_hint": "Kopia zajmuje miejsce oryginału, a oryginał trafia do Kosza", + "metadata.save": "Zapisz", + "metadata.save_copy": "Zapisz kopię", + "metadata.save_next": "Zapisz i dalej", + "metadata.add_field": "Dodaj pole", + "metadata.one_per_line": "Jeden w wierszu", + "metadata.remove_field": "Usuń pole", + "metadata.remove_named": "Usuń {0}", + "metadata.undo": "Cofnij", + "metadata.undo_named": "Przywróć {0}", + "metadata.spoken_removed": "{0}, usunięto", + "metadata.read_only": "Nie można tego tu zmienić", + "metadata.pdf_note": "Wcześniejsze wartości da się odzyskać z PDF. „Usuń metadane” usuwa je na dobre.", + "metadata.keys": "Enter edytuje pole lub zapisuje · Shift+Enter zaczyna nowy wiersz · Esc anuluje lub zamyka · Ctrl+F wyszukuje", + "metadata.empty": "Ten plik nie ma metadanych.", + "metadata.unreadable": "Nie udało się odczytać metadanych", + "metadata.group.location": "Lokalizacja", + "metadata.group.camera": "Aparat", + "metadata.group.exposure": "Ekspozycja", + "metadata.group.description": "Opis", + "metadata.group.keywords": "Słowa kluczowe", + "metadata.group.document": "Dokument", + "metadata.group.file": "Plik", "presets.editor_title": "Edytuj ustawienie", "presets.delete_confirm": "Usunąć „{0}”? Zostanie też usunięte z każdego paska i grupy.", "presets.name_required": "Podaj nazwę", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS, PS i starsze pliki Illustratora → PDF i obrazy, PDF lub SVG → EPS/PS.\nSVG i pliki AI zgodne z PDF konwertują się bez niego.", "engines.package.tesseract.name": "Tesseract (rozpoznawanie tekstu, OCR)", "engines.package.tesseract.description": "Odczytuje tekst zeskanowanych PDF i obrazów: przeszukiwalny PDF, DOCX, TXT.\nKażdy język jest pobierany przy pierwszym użyciu.", + "engines.package.exiftool.name": "ExifTool (metadane)", + "engines.package.exiftool.description": "Odczytuje wszystkie pola metadanych obrazów, PDF-ów, wideo i dźwięku oraz edytuje je w większości rodzajów plików, nie zmieniając ich zawartości.\nUżywa go narzędzie Metadane; bez niego pola są tylko do odczytu.", "engines.package.ffmpeg.name": "FFmpeg (wideo i audio)", "engines.package.ffmpeg.description": "Dodaje konwersję wideo i audio, wideo → GIF lub klatka, dźwięk z wideo i GIF → MP4.\nPotrzebny tylko do wideo i audio.", "engines.package.whisper.name": "Whisper (napisy SI)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Zainstaluj przez Homebrew (brew install ffmpeg), potem wybierz „Sprawdź ponownie”.\nAlbo wybierz „Użyj mojej kopii…” i wskaż folder bin.", "engines.guidance.macos.ghostscript": "Zainstaluj przez Homebrew (brew install ghostscript), potem wybierz „Sprawdź ponownie”.\nAlbo wybierz „Użyj mojej kopii…” i wskaż folder bin.", "engines.guidance.macos.tesseract": "Zainstaluj przez Homebrew (brew install tesseract), potem wybierz „Sprawdź ponownie”.\nAlbo wybierz „Użyj mojej kopii…” i wskaż folder bin.", + "engines.guidance.macos.exiftool": "Zainstaluj przez Homebrew (brew install exiftool), a potem wybierz „Sprawdź ponownie”.\nAlbo wybierz „Użyj mojej kopii…” i wskaż jego folder.", "engines.guidance.macos.calibre": "calibre pobierane przez Filee wymaga macOS 14 lub nowszego. Zainstaluj calibre 7.26, ostatnią wersję dla tego macOS, ze strony calibre-ebook.com, potem wybierz „Użyj mojej kopii…” i wskaż jego folder .app.", "engines.guidance.linux": "Zainstaluj {0} menedżerem pakietów swojej dystrybucji, potem wybierz „Użyj mojej kopii…” i wskaż folder programu.", "engines.guidance.linux.libreoffice": "Zainstaluj menedżerem pakietów (Ubuntu/Debian: sudo apt install libreoffice), potem wybierz „Sprawdź ponownie”.\nOtwieranie HWP wymaga też Javy i rozszerzenia H2Orestart.", "engines.guidance.linux.ghostscript": "Zainstaluj menedżerem pakietów (Ubuntu/Debian: sudo apt install ghostscript), potem wybierz „Sprawdź ponownie”.", "engines.guidance.linux.tesseract": "Zainstaluj menedżerem pakietów (Ubuntu/Debian: sudo apt install tesseract-ocr), potem wybierz „Sprawdź ponownie”.", + "engines.guidance.linux.exiftool": "Zainstaluj menedżerem pakietów (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), a potem wybierz „Sprawdź ponownie”.", "engines.guidance.windows": "Filee nie ma do pobrania {0} dla tego komputera.\nWybierz „Użyj mojej kopii…” i wskaż zainstalowaną kopię.", "engines.use_own_app_folder": "Wybierz folder zawierający {0}.app", "engines.progress_detail": "{0} z {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/pt-BR.json b/src/Filee.App/Assets/i18n/pt-BR.json index 144a491c5..45cbbfa4c 100644 --- a/src/Filee.App/Assets/i18n/pt-BR.json +++ b/src/Filee.App/Assets/i18n/pt-BR.json @@ -250,6 +250,7 @@ "engine.calibre.description": "Formatos raros de e-book (LIT, LRF, CHM, PDB, RB…) e saída Kindle (MOBI, AZW3).\nAcima do gravador de HWPX, também gera os PDFs de e-books (bem mais rápido em livros longos).", "engine.libreoffice.description": "Lê formatos antigos e menos comuns: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nTambém salva como DOC, XLS, PPT, ODT, ODP, modelos OpenDocument e EMF.", "engine.translator.description": "Traduz documentos e e-books localmente usando modelos offline do Ollama.\nTambém traduz legendas SRT e WebVTT: só o texto de cada legenda é enviado ao modelo; os números e os tempos das legendas são mantidos.", + "engine.exiftool.description": "Lê todos os campos de metadados de imagens, PDFs, vídeo e áudio, e os edita na maioria dos tipos de arquivo sem mexer no conteúdo.\nUsado pela ferramenta Metadados; sem ele, os campos aparecem somente para leitura.", "profile.images": "Imagens", "profile.pdf": "PDF", @@ -322,6 +323,7 @@ "error.cannot_hide": "Só é possível pixelizar ou desfocar imagens", "error.background_picture": "Não é possível ler a imagem para o fundo", "error.background_picture_changed": "A imagem para o fundo mudou depois de ser mostrada. Adicione o fundo de novo.", + "error.metadata_refused": "Algumas alterações de metadados não puderam ser gravadas como pedido", "hide.save": "Ocultar e salvar", "hide.save_next": "Ocultar e avançar", "hide.pixelate": "Pixelizar", @@ -425,6 +427,7 @@ "preset.group_flip": "Espelhar", "preset.annotate": "Anotar", "preset.background": "Adicionar fundo", + "preset.metadata": "Metadados", "preset.flip_horizontal": "Espelhar na horizontal", "preset.flip_horizontal.short": "Horizontal", "preset.flip_vertical": "Espelhar na vertical", @@ -1072,6 +1075,43 @@ "background.color_sage": "Sálvia", "background.picture_unreadable": "Não é possível ler esta imagem. Escolha outra, ou um degradê ou uma cor.", "background.keys": "Enter salva · Esc fecha · Tab percorre o visual · as setas escolhem ou movem um controle deslizante", + "metadata.search": "Pesquisar campos e valores", + "metadata.fields": "{0} campos", + "metadata.location_warning": "Qualquer pessoa com este arquivo pode ver onde ele foi feito.", + "metadata.read_only_banner": "Mostrando todos os campos. Para editá-los, instale o ExifTool em Ajustes › Motores.", + "metadata.unwritable": "Mostrando todos os campos. O Filee não consegue alterar os metadados deste tipo de arquivo.", + "metadata.needs_exiftool": "Os campos deste arquivo não podem ser lidos sem o ExifTool. Instale-o em Ajustes › Motores.", + "metadata.embedded": "Este arquivo também contém um vídeo ou uma segunda imagem, com metadados próprios que não aparecem nem podem ser apagados aqui. “{0}” cria uma cópia limpa, sem eles.", + "metadata.install": "Instalar ExifTool", + "metadata.remove_location": "Remover local", + "metadata.remove_all": "Remover tudo", + "metadata.remove_all_hint": "Mantém a orientação e o perfil de cor. Campos bloqueados podem permanecer.", + "metadata.changes_one": "1 alteração", + "metadata.changes": "{0} alterações", + "metadata.replace": "Substituir o original", + "metadata.replace_hint": "A cópia fica no lugar do original, e o original vai para a Lixeira", + "metadata.save": "Salvar", + "metadata.save_copy": "Salvar cópia", + "metadata.save_next": "Salvar e próximo", + "metadata.add_field": "Adicionar campo", + "metadata.one_per_line": "Um por linha", + "metadata.remove_field": "Remover campo", + "metadata.remove_named": "Remover {0}", + "metadata.undo": "Desfazer", + "metadata.undo_named": "Restaurar {0}", + "metadata.spoken_removed": "{0}, removido", + "metadata.read_only": "Não pode ser alterado aqui", + "metadata.pdf_note": "Os valores anteriores continuam recuperáveis dentro do PDF. «Remover metadados» os apaga de vez.", + "metadata.keys": "Enter edita um campo ou salva · Shift+Enter começa uma nova linha · Esc cancela ou fecha · Ctrl+F pesquisa", + "metadata.empty": "Este arquivo não tem metadados.", + "metadata.unreadable": "Não foi possível ler os metadados", + "metadata.group.location": "Local", + "metadata.group.camera": "Câmera", + "metadata.group.exposure": "Exposição", + "metadata.group.description": "Descrição", + "metadata.group.keywords": "Palavras-chave", + "metadata.group.document": "Documento", + "metadata.group.file": "Arquivo", "presets.editor_title": "Editar predefinição", "presets.delete_confirm": "Excluir “{0}”? Também será removida de todo menu e grupo.", "presets.name_required": "Digite um nome", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS, PS e arquivos antigos do Illustrator → PDF e imagens, PDF ou SVG → EPS/PS.\nSVG e arquivos AI compatíveis com PDF convertem sem ele.", "engines.package.tesseract.name": "Tesseract (reconhecimento de texto, OCR)", "engines.package.tesseract.description": "Lê o texto de PDFs digitalizados e imagens: PDF pesquisável, DOCX, TXT.\nCada idioma é baixado na primeira vez que é usado.", + "engines.package.exiftool.name": "ExifTool (metadados)", + "engines.package.exiftool.description": "Lê todos os campos de metadados de imagens, PDFs, vídeo e áudio, e os edita na maioria dos tipos de arquivo sem mexer no conteúdo.\nUsado pela ferramenta Metadados; sem ele, os campos aparecem somente para leitura.", "engines.package.ffmpeg.name": "FFmpeg (vídeo e áudio)", "engines.package.ffmpeg.description": "Adiciona conversão de vídeo e áudio, vídeo → GIF ou imagem parada, som de vídeos e GIF → MP4.\nSó é necessário para vídeo e áudio.", "engines.package.whisper.name": "Whisper (legendas com IA)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Instale com o Homebrew (brew install ffmpeg) e escolha “Verificar de novo”.\nOu escolha “Usar minha cópia…” e selecione a pasta bin.", "engines.guidance.macos.ghostscript": "Instale com o Homebrew (brew install ghostscript) e escolha “Verificar de novo”.\nOu escolha “Usar minha cópia…” e selecione a pasta bin.", "engines.guidance.macos.tesseract": "Instale com o Homebrew (brew install tesseract) e escolha “Verificar de novo”.\nOu escolha “Usar minha cópia…” e selecione a pasta bin.", + "engines.guidance.macos.exiftool": "Instale com o Homebrew (brew install exiftool) e escolha «Verificar novamente».\nOu escolha «Usar minha cópia…» e selecione a pasta.", "engines.guidance.macos.calibre": "O calibre que o Filee baixa precisa do macOS 14 ou posterior. Instale o calibre 7.26, a última versão para este macOS, em calibre-ebook.com, depois escolha “Usar minha cópia…” e selecione a pasta .app.", "engines.guidance.linux": "Instale {0} com o gerenciador de pacotes da sua distribuição, depois escolha “Usar minha cópia…” e selecione a pasta do programa.", "engines.guidance.linux.libreoffice": "Instale com o gerenciador de pacotes (Ubuntu/Debian: sudo apt install libreoffice) e escolha “Verificar de novo”.\nAbrir HWP também precisa de Java e da extensão H2Orestart.", "engines.guidance.linux.ghostscript": "Instale com o gerenciador de pacotes (Ubuntu/Debian: sudo apt install ghostscript) e escolha “Verificar de novo”.", "engines.guidance.linux.tesseract": "Instale com o gerenciador de pacotes (Ubuntu/Debian: sudo apt install tesseract-ocr) e escolha “Verificar de novo”.", + "engines.guidance.linux.exiftool": "Instale com o gerenciador de pacotes (Ubuntu/Debian: sudo apt install libimage-exiftool-perl) e escolha «Verificar novamente».", "engines.guidance.windows": "O Filee não tem download de {0} para este PC.\nEscolha “Usar minha cópia…” e selecione uma cópia instalada.", "engines.use_own_app_folder": "Escolha a pasta que contém {0}.app", "engines.progress_detail": "{0} de {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/pt-PT.json b/src/Filee.App/Assets/i18n/pt-PT.json index dbacc8508..da7ebbdfd 100644 --- a/src/Filee.App/Assets/i18n/pt-PT.json +++ b/src/Filee.App/Assets/i18n/pt-PT.json @@ -250,6 +250,7 @@ "engine.calibre.description": "Formatos raros de livros digitais (LIT, LRF, CHM, PDB, RB…) e saída para Kindle (MOBI, AZW3).\nAcima do gerador HWPX, também cria PDF de livros digitais (muito mais depressa para livros longos).", "engine.libreoffice.description": "Lê formatos antigos e menos comuns: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nTambém guarda em DOC, XLS, PPT, ODT, ODP, modelos OpenDocument e EMF.", "engine.translator.description": "Traduz documentos e e-books localmente através de modelos offline do Ollama.\nTambém traduz legendas SRT e WebVTT: apenas o texto de cada legenda é enviado ao modelo; os números e os tempos das legendas mantêm-se.", + "engine.exiftool.description": "Lê todos os campos de metadados de imagens, PDF, vídeo e áudio, e edita-os na maioria dos tipos de ficheiro sem mexer no conteúdo.\nUsado pela ferramenta Metadados; sem ele, os campos aparecem só para leitura.", "profile.images": "Imagens", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "Só é possível pixelizar ou desfocar imagens", "error.background_picture": "Não é possível ler a imagem para o fundo", "error.background_picture_changed": "A imagem para o fundo mudou depois de ser mostrada. Adicione o fundo novamente.", + "error.metadata_refused": "Algumas alterações aos metadados não puderam ser gravadas como pedido", "hide.save": "Ocultar e guardar", "hide.save_next": "Ocultar e avançar", "hide.pixelate": "Pixelizar", @@ -425,6 +427,7 @@ "preset.group_flip": "Inverter", "preset.annotate": "Anotar", "preset.background": "Adicionar fundo", + "preset.metadata": "Metadados", "preset.flip_horizontal": "Inverter na horizontal", "preset.flip_horizontal.short": "Horizontal", "preset.flip_vertical": "Inverter na vertical", @@ -1072,6 +1075,43 @@ "background.color_sage": "Salva", "background.picture_unreadable": "Não é possível ler esta imagem. Escolha outra, ou um gradiente ou uma cor.", "background.keys": "Enter guarda · Esc fecha · Tab percorre o aspeto · as setas escolhem ou movem um cursor", + "metadata.search": "Pesquisar campos e valores", + "metadata.fields": "{0} campos", + "metadata.location_warning": "Qualquer pessoa com este ficheiro pode ver onde foi feito.", + "metadata.read_only_banner": "A mostrar todos os campos. Para os editar, instale o ExifTool em Definições › Motores.", + "metadata.unwritable": "A mostrar todos os campos. O Filee não consegue alterar os metadados deste tipo de ficheiro.", + "metadata.needs_exiftool": "Os campos deste ficheiro não podem ser lidos sem o ExifTool. Instale-o em Definições › Motores.", + "metadata.embedded": "Este ficheiro também contém um vídeo ou uma segunda imagem, com metadados próprios que não aparecem nem podem ser apagados aqui. “{0}” cria uma cópia limpa, sem eles.", + "metadata.install": "Instalar ExifTool", + "metadata.remove_location": "Remover localização", + "metadata.remove_all": "Remover tudo", + "metadata.remove_all_hint": "Mantém a orientação e o perfil de cor. Os campos bloqueados podem permanecer.", + "metadata.changes_one": "1 alteração", + "metadata.changes": "{0} alterações", + "metadata.replace": "Substituir o original", + "metadata.replace_hint": "A cópia fica no lugar do original, e o original vai para a Reciclagem ou para o Lixo", + "metadata.save": "Guardar", + "metadata.save_copy": "Guardar cópia", + "metadata.save_next": "Guardar e seguinte", + "metadata.add_field": "Adicionar campo", + "metadata.one_per_line": "Um por linha", + "metadata.remove_field": "Remover campo", + "metadata.remove_named": "Remover {0}", + "metadata.undo": "Anular", + "metadata.undo_named": "Repor {0}", + "metadata.spoken_removed": "{0}, removido", + "metadata.read_only": "Não pode ser alterado aqui", + "metadata.pdf_note": "Os valores anteriores continuam recuperáveis dentro do PDF. «Remover metadados» apaga-os de vez.", + "metadata.keys": "Enter edita um campo ou guarda · Shift+Enter começa uma nova linha · Esc cancela ou fecha · Ctrl+F pesquisa", + "metadata.empty": "Este ficheiro não tem metadados.", + "metadata.unreadable": "Não foi possível ler os metadados", + "metadata.group.location": "Localização", + "metadata.group.camera": "Câmara", + "metadata.group.exposure": "Exposição", + "metadata.group.description": "Descrição", + "metadata.group.keywords": "Palavras-chave", + "metadata.group.document": "Documento", + "metadata.group.file": "Ficheiro", "presets.editor_title": "Editar predefinição", "presets.delete_confirm": "Eliminar «{0}»? Também será removida de todas as barras e grupos.", "presets.name_required": "Introduz um nome", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS, PS e ficheiros Illustrator antigos → PDF e imagens, PDF ou SVG → EPS/PS.\nSVG e ficheiros AI compatíveis com PDF são convertidos sem ele.", "engines.package.tesseract.name": "Tesseract (reconhecimento de texto, OCR)", "engines.package.tesseract.description": "Lê o texto de PDF digitalizados e de imagens: PDF pesquisável, DOCX e TXT.\nOs dados de cada idioma são descarregados na primeira utilização.", + "engines.package.exiftool.name": "ExifTool (metadados)", + "engines.package.exiftool.description": "Lê todos os campos de metadados de imagens, PDF, vídeo e áudio, e edita-os na maioria dos tipos de ficheiro sem mexer no conteúdo.\nUsado pela ferramenta Metadados; sem ele, os campos aparecem só para leitura.", "engines.package.ffmpeg.name": "FFmpeg (vídeo e áudio)", "engines.package.ffmpeg.description": "Adiciona conversão de vídeo e áudio, vídeo → GIF ou imagem fixa, extração de som de vídeos e GIF → MP4.\nSó é necessário para vídeo e áudio.", "engines.package.whisper.name": "Whisper (legendas com IA)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Instala-o com o Homebrew (brew install ffmpeg) e escolhe «Verificar novamente».\nOu escolhe «Utilizar a minha cópia…» e seleciona a pasta bin.", "engines.guidance.macos.ghostscript": "Instala-o com o Homebrew (brew install ghostscript) e escolhe «Verificar novamente».\nOu escolhe «Utilizar a minha cópia…» e seleciona a pasta bin.", "engines.guidance.macos.tesseract": "Instala-o com o Homebrew (brew install tesseract) e escolhe «Verificar novamente».\nOu escolhe «Utilizar a minha cópia…» e seleciona a pasta bin.", + "engines.guidance.macos.exiftool": "Instale com o Homebrew (brew install exiftool) e escolha «Verificar de novo».\nOu escolha «Usar a minha cópia…» e selecione a pasta.", "engines.guidance.macos.calibre": "A versão do calibre que o Filee descarrega requer o macOS 14 ou posterior. Instala o calibre 7.26, a última versão compatível com este macOS, a partir de calibre-ebook.com. Depois, escolhe «Utilizar a minha cópia…» e seleciona a pasta .app.", "engines.guidance.linux": "Instala o {0} com o gestor de pacotes da tua distribuição, escolhe «Utilizar a minha cópia…» e seleciona a pasta do programa.", "engines.guidance.linux.libreoffice": "Instala-o com o gestor de pacotes (Ubuntu/Debian: sudo apt install libreoffice) e escolhe «Verificar novamente».\nAbrir HWP também requer Java e a extensão H2Orestart.", "engines.guidance.linux.ghostscript": "Instala-o com o gestor de pacotes (Ubuntu/Debian: sudo apt install ghostscript) e escolhe «Verificar novamente».", "engines.guidance.linux.tesseract": "Instala-o com o gestor de pacotes (Ubuntu/Debian: sudo apt install tesseract-ocr) e escolhe «Verificar novamente».", + "engines.guidance.linux.exiftool": "Instale com o gestor de pacotes (Ubuntu/Debian: sudo apt install libimage-exiftool-perl) e escolha «Verificar de novo».", "engines.guidance.windows": "O Filee não disponibiliza {0} para descarregar neste computador.\nEscolhe «Utilizar a minha cópia…» e seleciona uma cópia instalada.", "engines.use_own_app_folder": "Escolhe a pasta que contém {0}.app", "engines.progress_detail": "{0} de {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/ru.json b/src/Filee.App/Assets/i18n/ru.json index ea78ff03e..140ec2fec 100644 --- a/src/Filee.App/Assets/i18n/ru.json +++ b/src/Filee.App/Assets/i18n/ru.json @@ -250,6 +250,7 @@ "engine.calibre.description": "Редкие форматы электронных книг (LIT, LRF, CHM, PDB, RB…) и вывод для Kindle (MOBI, AZW3).\nЕсли стоит выше HWPX writer, он же делает PDF из электронных книг (для длинных книг намного быстрее).", "engine.libreoffice.description": "Читает старые и редкие форматы: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nА ещё сохраняет в DOC, XLS, PPT, ODT, ODP, шаблоны OpenDocument и EMF.", "engine.translator.description": "Переводит документы и электронные книги локально с помощью офлайн-моделей Ollama.\nТакже переводит субтитры SRT и WebVTT: в модель отправляется только текст реплик, номера и тайм-коды сохраняются.", + "engine.exiftool.description": "Читает все поля метаданных изображений, PDF, видео и звука и изменяет их в большинстве типов файлов, не трогая содержимое.\nИспользуется инструментом «Метаданные»; без него поля доступны только для чтения.", "profile.images": "Изображения", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "Пикселизировать и размывать можно только изображения", "error.background_picture": "Не удаётся прочитать изображение для фона", "error.background_picture_changed": "Изображение для фона изменилось после показа. Добавьте фон ещё раз.", + "error.metadata_refused": "Некоторые изменения метаданных не удалось записать так, как было задано", "hide.save": "Скрыть и сохранить", "hide.save_next": "Скрыть и дальше", "hide.pixelate": "Пикселизация", @@ -425,6 +427,7 @@ "preset.group_flip": "Отразить", "preset.annotate": "Пометки", "preset.background": "Добавить фон", + "preset.metadata": "Метаданные", "preset.flip_horizontal": "Отразить по горизонтали", "preset.flip_horizontal.short": "По гориз.", "preset.flip_vertical": "Отразить по вертикали", @@ -1072,6 +1075,43 @@ "background.color_sage": "Шалфей", "background.picture_unreadable": "Не удаётся прочитать это изображение. Выберите другое, градиент или цвет.", "background.keys": "Enter сохраняет · Esc закрывает · Tab переходит по настройкам вида · стрелки выбирают или двигают ползунок", + "metadata.search": "Искать поля и значения", + "metadata.fields": "Полей: {0}", + "metadata.location_warning": "Любой, у кого есть этот файл, увидит, где он сделан.", + "metadata.read_only_banner": "Показаны все поля. Чтобы изменять их, установите ExifTool в Настройки › Движки.", + "metadata.unwritable": "Показаны все поля. Filee не может изменять метаданные файлов этого типа.", + "metadata.needs_exiftool": "Поля этого файла нельзя прочитать без ExifTool. Установите его в Настройки › Движки.", + "metadata.embedded": "В этом файле есть также видео или второе изображение со своими метаданными, которые здесь не показаны и не могут быть удалены. «{0}» создаёт чистую копию без них.", + "metadata.install": "Установить ExifTool", + "metadata.remove_location": "Убрать место", + "metadata.remove_all": "Убрать всё", + "metadata.remove_all_hint": "Сохраняет ориентацию и цветовой профиль. Заблокированные поля могут остаться.", + "metadata.changes_one": "1 изменение", + "metadata.changes": "Изменений: {0}", + "metadata.replace": "Заменить оригинал", + "metadata.replace_hint": "Копия занимает место оригинала, а оригинал отправляется в Корзину", + "metadata.save": "Сохранить", + "metadata.save_copy": "Сохранить копию", + "metadata.save_next": "Сохранить и далее", + "metadata.add_field": "Добавить поле", + "metadata.one_per_line": "По одному в строке", + "metadata.remove_field": "Убрать поле", + "metadata.remove_named": "Удалить {0}", + "metadata.undo": "Отменить", + "metadata.undo_named": "Вернуть {0}", + "metadata.spoken_removed": "{0}, удалено", + "metadata.read_only": "Здесь нельзя изменить", + "metadata.pdf_note": "Прежние значения остаются восстановимыми внутри PDF. «Удалить метаданные» убирает их насовсем.", + "metadata.keys": "Enter изменяет поле или сохраняет · Shift+Enter начинает новую строку · Esc отменяет или закрывает · Ctrl+F ищет", + "metadata.empty": "В этом файле нет метаданных.", + "metadata.unreadable": "Не удалось прочитать метаданные", + "metadata.group.location": "Место", + "metadata.group.camera": "Камера", + "metadata.group.exposure": "Экспозиция", + "metadata.group.description": "Описание", + "metadata.group.keywords": "Ключевые слова", + "metadata.group.document": "Документ", + "metadata.group.file": "Файл", "presets.editor_title": "Изменить пресет", "presets.delete_confirm": "Удалить «{0}»? Он также пропадёт из всех круговых меню и групп.", "presets.name_required": "Введите название", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS, PS и старые файлы Illustrator → PDF и картинки, PDF или SVG → EPS/PS.\nSVG и файлы AI, совместимые с PDF, конвертируются и без него.", "engines.package.tesseract.name": "Tesseract (распознавание текста, OCR)", "engines.package.tesseract.description": "Читает текст отсканированных PDF и изображений: PDF с поиском, DOCX, TXT.\nКаждый язык скачивается при первом использовании.", + "engines.package.exiftool.name": "ExifTool (метаданные)", + "engines.package.exiftool.description": "Читает все поля метаданных изображений, PDF, видео и звука и изменяет их в большинстве типов файлов, не трогая содержимое.\nИспользуется инструментом «Метаданные»; без него поля доступны только для чтения.", "engines.package.ffmpeg.name": "FFmpeg (видео и аудио)", "engines.package.ffmpeg.description": "Добавляет конвертацию видео и аудио, видео → GIF или стоп-кадр, звук из видео и GIF → MP4.\nНужен только для видео и аудио.", "engines.package.whisper.name": "Whisper (ИИ-субтитры)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Установите через Homebrew (brew install ffmpeg), затем выберите «Проверить снова».\nИли выберите «Использовать свою копию…» и укажите папку bin.", "engines.guidance.macos.ghostscript": "Установите через Homebrew (brew install ghostscript), затем выберите «Проверить снова».\nИли выберите «Использовать свою копию…» и укажите папку bin.", "engines.guidance.macos.tesseract": "Установите через Homebrew (brew install tesseract), затем выберите «Проверить снова».\nИли выберите «Использовать свою копию…» и укажите папку bin.", + "engines.guidance.macos.exiftool": "Установите через Homebrew (brew install exiftool) и выберите «Проверить снова».\nИли выберите «Использовать мою копию…» и укажите её папку.", "engines.guidance.macos.calibre": "Для calibre, которую скачивает Filee, нужна macOS 14 или новее. Установите calibre 7.26, последнюю версию для этой macOS, с calibre-ebook.com, затем выберите «Использовать свою копию…» и укажите её папку .app.", "engines.guidance.linux": "Установите {0} менеджером пакетов вашего дистрибутива, затем выберите «Использовать свою копию…» и укажите папку программы.", "engines.guidance.linux.libreoffice": "Установите менеджером пакетов (Ubuntu/Debian: sudo apt install libreoffice), затем выберите «Проверить снова».\nДля открытия HWP также нужны Java и расширение H2Orestart.", "engines.guidance.linux.ghostscript": "Установите менеджером пакетов (Ubuntu/Debian: sudo apt install ghostscript), затем выберите «Проверить снова».", "engines.guidance.linux.tesseract": "Установите менеджером пакетов (Ubuntu/Debian: sudo apt install tesseract-ocr), затем выберите «Проверить снова».", + "engines.guidance.linux.exiftool": "Установите через менеджер пакетов (Ubuntu/Debian: sudo apt install libimage-exiftool-perl) и выберите «Проверить снова».", "engines.guidance.windows": "Filee не может скачать {0} для этого компьютера.\nВыберите «Использовать свою копию…» и укажите установленную копию.", "engines.use_own_app_folder": "Выберите папку, в которой лежит {0}.app", "engines.progress_detail": "{0} из {1} · {2}/с · {3}", diff --git a/src/Filee.App/Assets/i18n/th.json b/src/Filee.App/Assets/i18n/th.json index fbae21637..e2a4b880d 100644 --- a/src/Filee.App/Assets/i18n/th.json +++ b/src/Filee.App/Assets/i18n/th.json @@ -243,6 +243,7 @@ "engine.calibre.description": "ฟอร์แมตอีบุ๊กที่หาได้ยาก (LIT, LRF, CHM, PDB, RB…) และไฟล์สำหรับ Kindle (MOBI, AZW3)\nเมื่อวางไว้เหนือตัวเขียน HWPX จะทำหน้าที่สร้าง PDF ของอีบุ๊กด้วย (เร็วกว่ามากสำหรับหนังสือเล่มหนา)", "engine.libreoffice.description": "อ่านฟอร์แมตรุ่นเก่าและฟอร์แมตที่พบน้อย: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nและบันทึกเป็น DOC, XLS, PPT, ODT, ODP, แม่แบบ OpenDocument และ EMF", "engine.translator.description": "แปลเอกสารและอีบุ๊กแบบออฟไลน์บนเครื่องโดยใช้โมเดล Ollama\nแปลคำบรรยาย SRT และ WebVTT ได้ด้วย โดยส่งเฉพาะข้อความคำบรรยายไปยังโมเดล ส่วนหมายเลขและเวลาของแต่ละช่วงจะคงเดิม", + "engine.exiftool.description": "อ่านทุกฟิลด์เมทาดาตาของรูปภาพ PDF วิดีโอ และเสียง และแก้ไขได้ในไฟล์ส่วนใหญ่โดยไม่แตะต้องเนื้อหาของไฟล์\nเครื่องมือเมทาดาตาใช้สิ่งนี้ หากไม่มี ฟิลด์จะแสดงแบบอ่านอย่างเดียว", "profile.images": "รูปภาพ", "profile.pdf": "PDF", "profile.office": "เอกสาร", @@ -299,6 +300,7 @@ "error.cannot_hide": "ใส่โมเสกหรือเบลอได้เฉพาะไฟล์รูปภาพเท่านั้น", "error.background_picture": "อ่านรูปที่จะใช้เป็นพื้นหลังไม่ได้", "error.background_picture_changed": "รูปที่จะใช้เป็นพื้นหลังเปลี่ยนไปหลังจากแสดงแล้ว เพิ่มพื้นหลังอีกครั้ง", + "error.metadata_refused": "เขียนการเปลี่ยนแปลงเมทาดาตาบางรายการตามที่ขอไม่ได้", "hide.save": "ซ่อนและบันทึก", "hide.save_next": "ซ่อนและต่อไป", "hide.pixelate": "โมเสก", @@ -415,6 +417,7 @@ "preset.group_flip": "พลิกภาพ", "preset.annotate": "ใส่คำอธิบาย", "preset.background": "เพิ่มพื้นหลัง", + "preset.metadata": "เมทาดาทา", "preset.flip_horizontal": "พลิกแนวนอน", "preset.flip_horizontal.short": "แนวนอน", "preset.flip_vertical": "พลิกแนวตั้ง", @@ -1057,6 +1060,43 @@ "background.color_sage": "เสจ", "background.picture_unreadable": "อ่านรูปนี้ไม่ได้ เลือกรูปอื่น หรือไล่สีหรือสีแทน", "background.keys": "Enter บันทึก · Esc ปิด · Tab ไล่ไปตามตัวเลือกรูปแบบ · ลูกศรเลือกหรือเลื่อนแถบเลื่อน", + "metadata.search": "ค้นหาฟิลด์และค่า", + "metadata.fields": "{0} ฟิลด์", + "metadata.location_warning": "ใครก็ตามที่มีไฟล์นี้จะเห็นว่าสร้างที่ไหน", + "metadata.read_only_banner": "กำลังแสดงทุกฟิลด์ หากต้องการแก้ไข ให้ติดตั้ง ExifTool ในการตั้งค่า › เอนจิน", + "metadata.unwritable": "กำลังแสดงทุกฟิลด์ Filee ไม่สามารถเปลี่ยนเมทาดาตาของไฟล์ประเภทนี้ได้", + "metadata.needs_exiftool": "อ่านฟิลด์ของไฟล์นี้ไม่ได้หากไม่มี ExifTool ติดตั้งได้ในการตั้งค่า › เอนจิน", + "metadata.embedded": "ไฟล์นี้ยังมีวิดีโอหรือรูปที่สอง ซึ่งมีเมทาดาตาของตัวเองที่ไม่แสดงและลบที่นี่ไม่ได้ “{0}” จะสร้างสำเนาที่สะอาดโดยไม่มีสิ่งเหล่านี้", + "metadata.install": "ติดตั้ง ExifTool", + "metadata.remove_location": "ลบตำแหน่ง", + "metadata.remove_all": "ลบทั้งหมด", + "metadata.remove_all_hint": "เก็บการหมุนและโปรไฟล์สีไว้ ฟิลด์ที่ล็อกอาจยังคงอยู่", + "metadata.changes_one": "1 การเปลี่ยนแปลง", + "metadata.changes": "{0} การเปลี่ยนแปลง", + "metadata.replace": "แทนที่ไฟล์ต้นฉบับ", + "metadata.replace_hint": "สำเนาจะแทนที่ต้นฉบับ และต้นฉบับจะถูกย้ายไปที่ถังรีไซเคิล / ถังขยะ", + "metadata.save": "บันทึก", + "metadata.save_copy": "บันทึกสำเนา", + "metadata.save_next": "บันทึกและถัดไป", + "metadata.add_field": "เพิ่มฟิลด์", + "metadata.one_per_line": "บรรทัดละหนึ่งรายการ", + "metadata.remove_field": "ลบฟิลด์", + "metadata.remove_named": "ลบ {0}", + "metadata.undo": "เลิกทำ", + "metadata.undo_named": "นำ {0} กลับมา", + "metadata.spoken_removed": "{0}, ลบแล้ว", + "metadata.read_only": "เปลี่ยนที่นี่ไม่ได้", + "metadata.pdf_note": "ค่าเดิมยังกู้คืนได้ภายใน PDF ใช้ «ลบเมทาดาทา» เพื่อลบออกถาวร", + "metadata.keys": "Enter แก้ไขฟิลด์หรือบันทึก · Shift+Enter ขึ้นบรรทัดใหม่ · Esc ยกเลิกหรือปิด · Ctrl+F ค้นหา", + "metadata.empty": "ไฟล์นี้ไม่มีเมทาดาทา", + "metadata.unreadable": "อ่านเมทาดาตาของไฟล์นี้ไม่ได้", + "metadata.group.location": "ตำแหน่ง", + "metadata.group.camera": "กล้อง", + "metadata.group.exposure": "การรับแสง", + "metadata.group.description": "คำอธิบาย", + "metadata.group.keywords": "คำสำคัญ", + "metadata.group.document": "เอกสาร", + "metadata.group.file": "ไฟล์", "presets.editor_title": "แก้ไขพรีเซ็ต", "presets.delete_confirm": "ลบ “{0}” หรือไม่? พรีเซ็ตนี้จะถูกลบออกจากทุกแถบเครื่องมือและกลุ่มด้วย", "presets.name_required": "โปรดระบุชื่อ", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "แปลง EPS, PS และไฟล์ Illustrator รุ่นเก่า → PDF และรูปภาพ, PDF หรือ SVG → EPS/PS\n(ไฟล์ SVG และไฟล์ AI ที่บันทึกแบบรองรับ PDF สามารถแปลงได้โดยไม่ต้องติดตั้ง)", "engines.package.tesseract.name": "Tesseract (รู้จำข้อความ, OCR)", "engines.package.tesseract.description": "อ่านข้อความจาก PDF ที่สแกนและรูปภาพ: PDF ที่ค้นหาได้, DOCX, TXT\nแต่ละภาษาจะดาวน์โหลดเมื่อใช้ครั้งแรก", + "engines.package.exiftool.name": "ExifTool (เมทาดาทา)", + "engines.package.exiftool.description": "อ่านทุกฟิลด์เมทาดาตาของรูปภาพ PDF วิดีโอ และเสียง และแก้ไขได้ในไฟล์ส่วนใหญ่โดยไม่แตะต้องเนื้อหาของไฟล์\nเครื่องมือเมทาดาตาใช้สิ่งนี้ หากไม่มี ฟิลด์จะแสดงแบบอ่านอย่างเดียว", "engines.package.ffmpeg.name": "FFmpeg (วิดีโอและเสียง)", "engines.package.ffmpeg.description": "เพิ่มการแปลงวิดีโอและเสียง วิดีโอ → GIF หรือภาพนิ่ง แยกเสียงจากวิดีโอ และ GIF → MP4\n(จำเป็นเฉพาะสำหรับงานวิดีโอและเสียงเท่านั้น)", "engines.package.whisper.name": "Whisper (คำบรรยาย AI)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "ติดตั้งผ่าน Homebrew (brew install ffmpeg) แล้วเลือก “ตรวจสอบอีกครั้ง”\nหรือเลือก “ใช้โปรแกรมในเครื่อง…” แล้วเลือกโฟลเดอร์ bin", "engines.guidance.macos.ghostscript": "ติดตั้งผ่าน Homebrew (brew install ghostscript) แล้วเลือก “ตรวจสอบอีกครั้ง”\nหรือเลือก “ใช้โปรแกรมในเครื่อง…” แล้วเลือกโฟลเดอร์ bin", "engines.guidance.macos.tesseract": "ติดตั้งผ่าน Homebrew (brew install tesseract) แล้วเลือก “ตรวจสอบอีกครั้ง”\nหรือเลือก “ใช้โปรแกรมในเครื่อง…” แล้วเลือกโฟลเดอร์ bin", + "engines.guidance.macos.exiftool": "ติดตั้งด้วย Homebrew (brew install exiftool) แล้วเลือก «ตรวจสอบอีกครั้ง»\nหรือเลือก «ใช้สำเนาของฉัน…» แล้วเลือกโฟลเดอร์ของมัน", "engines.guidance.macos.calibre": "calibre ที่ Filee ดาวน์โหลดต้องใช้ macOS 14 ขึ้นไป ให้ติดตั้ง calibre 7.26 (เวอร์ชันล่าสุดสำหรับ macOS รุ่นนี้) จาก calibre-ebook.com แล้วเลือก “ใช้โปรแกรมในเครื่อง…” และเลือกโฟลเดอร์ .app", "engines.guidance.linux": "ติดตั้ง {0} ด้วยตัวจัดการแพ็กเกจของการกระจายลินุกซ์ของคุณ แล้วเลือก “ใช้โปรแกรมในเครื่อง…” และเลือกโฟลเดอร์ของโปรแกรม", "engines.guidance.linux.libreoffice": "ติดตั้งด้วยตัวจัดการแพ็กเกจของคุณ (Ubuntu/Debian: sudo apt install libreoffice) แล้วเลือก “ตรวจสอบอีกครั้ง”\nการเปิด HWP จำเป็นต้องใช้ Java และส่วนขยาย H2Orestart ด้วย", "engines.guidance.linux.ghostscript": "ติดตั้งด้วยตัวจัดการแพ็กเกจของคุณ (Ubuntu/Debian: sudo apt install ghostscript) แล้วเลือก “ตรวจสอบอีกครั้ง”", "engines.guidance.linux.tesseract": "ติดตั้งด้วยตัวจัดการแพ็กเกจของคุณ (Ubuntu/Debian: sudo apt install tesseract-ocr) แล้วเลือก “ตรวจสอบอีกครั้ง”", + "engines.guidance.linux.exiftool": "ติดตั้งด้วยตัวจัดการแพ็กเกจ (Ubuntu/Debian: sudo apt install libimage-exiftool-perl) แล้วเลือก «ตรวจสอบอีกครั้ง»", "engines.guidance.windows": "Filee ไม่มีตัวดาวน์โหลด {0} สำหรับพีซีเครื่องนี้\nโปรดเลือก “ใช้โปรแกรมในเครื่อง…” และเลือกโปรแกรมที่ติดตั้งไว้", "engines.use_own_app_folder": "เลือกโฟลเดอร์ที่บรรจุ {0}.app", "engines.progress_detail": "{0} จาก {1} · {2}/วินาที · {3}", diff --git a/src/Filee.App/Assets/i18n/tr.json b/src/Filee.App/Assets/i18n/tr.json index a78466ece..f6dd69720 100644 --- a/src/Filee.App/Assets/i18n/tr.json +++ b/src/Filee.App/Assets/i18n/tr.json @@ -243,6 +243,7 @@ "engine.calibre.description": "Nadir e-kitap biçimleri (LIT, LRF, CHM, PDB, RB…) ve Kindle çıktısı (MOBI, AZW3).\nHWPX yazıcısının üstüne alındığında e-kitapları PDF'ye de dönüştürür (uzun kitaplarda çok daha hızlıdır).", "engine.libreoffice.description": "Eski ve az kullanılan biçimleri okur: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nDOC, XLS, PPT, ODT, ODP, OpenDocument şablonları ve EMF olarak da kaydeder.", "engine.translator.description": "Çevrimdışı Ollama modellerini kullanarak belgeleri ve e-kitapları yerel olarak çevirir.\nSRT ve WebVTT altyazılarını da çevirir: modele yalnızca altyazı metni gönderilir, altyazı numaraları ve zaman damgaları korunur.", + "engine.exiftool.description": "Resimlerin, PDF'lerin, videonun ve sesin tüm üst veri alanlarını okur ve çoğu dosya türünde dosyanın içeriğine dokunmadan düzenler.\nÜst Veri aracı kullanır; o olmadan alanlar salt okunur gösterilir.", "profile.images": "Görseller", "profile.pdf": "PDF", "profile.office": "Belgeler", @@ -299,6 +300,7 @@ "error.cannot_hide": "Yalnızca görseller pikselleştirilebilir veya bulanıklaştırılabilir", "error.background_picture": "Arka plana konacak resim okunamıyor", "error.background_picture_changed": "Arka plana konacak resim gösterildikten sonra değişti. Arka planı yeniden ekleyin.", + "error.metadata_refused": "Bazı üst veri değişiklikleri istendiği gibi yazılamadı", "hide.save": "Gizle ve kaydet", "hide.save_next": "Gizle ve ilerle", "hide.pixelate": "Pikselleştir", @@ -415,6 +417,7 @@ "preset.group_flip": "Çevir", "preset.annotate": "Not ekle", "preset.background": "Arka plan ekle", + "preset.metadata": "Meta veriler", "preset.flip_horizontal": "Yatay çevir", "preset.flip_horizontal.short": "Yatay", "preset.flip_vertical": "Dikey çevir", @@ -1057,6 +1060,43 @@ "background.color_sage": "Adaçayı", "background.picture_unreadable": "Bu resim okunamıyor. Başka bir resim ya da bir degrade veya renk seçin.", "background.keys": "Enter kaydeder · Esc kapatır · Tab görünüm seçenekleri arasında gezer · oklar seçer veya kaydırıcıyı kaydırır", + "metadata.search": "Alan ve değer ara", + "metadata.fields": "{0} alan", + "metadata.location_warning": "Bu dosyaya sahip olan herkes nerede çekildiğini görebilir.", + "metadata.read_only_banner": "Tüm alanlar gösteriliyor. Düzenlemek için Ayarlar › Motorlar'dan ExifTool'u yükleyin.", + "metadata.unwritable": "Tüm alanlar gösteriliyor. Filee bu tür dosyaların üst verisini değiştiremez.", + "metadata.needs_exiftool": "Bu dosyanın alanları ExifTool olmadan okunamaz. Ayarlar › Motorlar'dan yükleyin.", + "metadata.embedded": "Bu dosyada ayrıca bir video veya ikinci bir resim var; bunların burada gösterilmeyen ve temizlenemeyen kendi üst verileri var. “{0}” onlar olmadan temiz bir kopya oluşturur.", + "metadata.install": "ExifTool'u yükle", + "metadata.remove_location": "Konumu kaldır", + "metadata.remove_all": "Hepsini kaldır", + "metadata.remove_all_hint": "Yönü ve renk profilini korur. Kilitli alanlar kalabilir.", + "metadata.changes_one": "1 değişiklik", + "metadata.changes": "{0} değişiklik", + "metadata.replace": "Orijinalin yerine yaz", + "metadata.replace_hint": "Kopya orijinalin yerini alır ve orijinal Geri Dönüşüm Kutusu'na / Çöp Sepeti'ne taşınır", + "metadata.save": "Kaydet", + "metadata.save_copy": "Kopyayı kaydet", + "metadata.save_next": "Kaydet ve sonraki", + "metadata.add_field": "Alan ekle", + "metadata.one_per_line": "Her satıra bir tane", + "metadata.remove_field": "Alanı kaldır", + "metadata.remove_named": "{0} kaldır", + "metadata.undo": "Geri al", + "metadata.undo_named": "{0} geri getir", + "metadata.spoken_removed": "{0}, kaldırıldı", + "metadata.read_only": "Burada değiştirilemez", + "metadata.pdf_note": "Önceki değerler PDF içinde kurtarılabilir kalır. \"Meta verileri kaldır\" onları tamamen siler.", + "metadata.keys": "Enter bir alanı düzenler veya kaydeder · Shift+Enter yeni satır başlatır · Esc iptal eder veya kapatır · Ctrl+F arar", + "metadata.empty": "Bu dosyada meta veri yok.", + "metadata.unreadable": "Üst verisi okunamadı", + "metadata.group.location": "Konum", + "metadata.group.camera": "Kamera", + "metadata.group.exposure": "Pozlama", + "metadata.group.description": "Açıklama", + "metadata.group.keywords": "Anahtar sözcükler", + "metadata.group.document": "Belge", + "metadata.group.file": "Dosya", "presets.editor_title": "Ön ayarı düzenle", "presets.delete_confirm": "“{0}” silinsin mi? Tüm araç çubuklarından ve gruplardan da kaldırılır.", "presets.name_required": "Bir ad girin", @@ -1131,6 +1171,8 @@ "engines.package.ghostscript.description": "EPS, PS ve eski Illustrator dosyaları → PDF ve görseller; PDF veya SVG → EPS/PS.\nSVG ve PDF uyumlu AI dosyaları bu motor olmadan dönüştürülür.", "engines.package.tesseract.name": "Tesseract (metin tanıma, OCR)", "engines.package.tesseract.description": "Taranmış PDF ve görsellerdeki metni okur: aranabilir PDF, DOCX, TXT.\nHer dil ilk kullanıldığında indirilir.", + "engines.package.exiftool.name": "ExifTool (meta veriler)", + "engines.package.exiftool.description": "Resimlerin, PDF'lerin, videonun ve sesin tüm üst veri alanlarını okur ve çoğu dosya türünde dosyanın içeriğine dokunmadan düzenler.\nÜst Veri aracı kullanır; o olmadan alanlar salt okunur gösterilir.", "engines.package.ffmpeg.name": "FFmpeg (video ve ses)", "engines.package.ffmpeg.description": "Video ve ses dönüştürme, video → GIF veya durağan görsel, videodan ses çıkarma ve GIF → MP4 ekler.\nYalnızca video ve ses için gereklidir.", "engines.package.whisper.name": "Whisper (YZ Altyazı)", @@ -1143,11 +1185,13 @@ "engines.guidance.macos.ffmpeg": "Homebrew ile kurup (brew install ffmpeg) “Yeniden denetle” seçeneğini kullanın.\nYa da “Kendi kopyamı kullan…” ile bin klasörünü seçin.", "engines.guidance.macos.ghostscript": "Homebrew ile kurup (brew install ghostscript) “Yeniden denetle” seçeneğini kullanın.\nYa da “Kendi kopyamı kullan…” ile bin klasörünü seçin.", "engines.guidance.macos.tesseract": "Homebrew ile kurup (brew install tesseract) “Yeniden denetle” seçeneğini kullanın.\nYa da “Kendi kopyamı kullan…” ile bin klasörünü seçin.", + "engines.guidance.macos.exiftool": "Homebrew ile yükleyin (brew install exiftool), sonra \"Yeniden denetle\"yi seçin.\nYa da \"Kendi kopyamı kullan…\"ı seçip klasörünü seçin.", "engines.guidance.macos.calibre": "Filee'nin indirdiği calibre, macOS 14 veya üstünü gerektirir. Bu macOS için son sürüm olan calibre 7.26'yı calibre-ebook.com adresinden kurun; ardından “Kendi kopyamı kullan…” ile .app klasörünü seçin.", "engines.guidance.linux": "Dağıtımınızın paket yöneticisiyle {0} kurun; ardından “Kendi kopyamı kullan…” ile program klasörünü seçin.", "engines.guidance.linux.libreoffice": "Paket yöneticinizle kurup (Ubuntu/Debian: sudo apt install libreoffice) “Yeniden denetle” seçeneğini kullanın.\nHWP açmak için Java ve H2Orestart eklentisi de gereklidir.", "engines.guidance.linux.ghostscript": "Paket yöneticinizle kurup (Ubuntu/Debian: sudo apt install ghostscript) “Yeniden denetle” seçeneğini kullanın.", "engines.guidance.linux.tesseract": "Paket yöneticinizle kurup (Ubuntu/Debian: sudo apt install tesseract-ocr) “Yeniden denetle” seçeneğini kullanın.", + "engines.guidance.linux.exiftool": "Paket yöneticinizle yükleyin (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), sonra \"Yeniden denetle\"yi seçin.", "engines.guidance.windows": "Filee bu bilgisayar için {0} indirmesi sunmuyor.\n“Kendi kopyamı kullan…” ile kurulu bir kopyayı seçin.", "engines.use_own_app_folder": "{0}.app içeren klasörü seçin", "engines.progress_detail": "{0} / {1} · {2}/sn · {3}", diff --git a/src/Filee.App/Assets/i18n/vi.json b/src/Filee.App/Assets/i18n/vi.json index 9c122d2bf..cbfe4cff2 100644 --- a/src/Filee.App/Assets/i18n/vi.json +++ b/src/Filee.App/Assets/i18n/vi.json @@ -250,6 +250,7 @@ "engine.calibre.description": "Định dạng sách điện tử hiếm (LIT, LRF, CHM, PDB, RB…) và đầu ra Kindle (MOBI, AZW3).\nKhi đặt trên trình ghi HWPX, Calibre cũng tạo PDF cho sách điện tử (nhanh hơn nhiều với sách dài).", "engine.libreoffice.description": "Đọc các định dạng cũ hơn và ít phổ biến: DOC, PPT, Pages, Keynote, Numbers, WordPerfect, ClarisWorks, Publisher, Visio…\nCũng lưu dưới dạng DOC, XLS, PPT, ODT, ODP, mẫu OpenDocument và EMF.", "engine.translator.description": "Dịch tài liệu và sách điện tử ngay trên máy bằng các mô hình Ollama ngoại tuyến.\nCũng dịch phụ đề SRT và WebVTT: chỉ văn bản của từng câu phụ đề được gửi tới mô hình, số thứ tự và mốc thời gian được giữ nguyên.", + "engine.exiftool.description": "Đọc mọi trường siêu dữ liệu của ảnh, PDF, video và âm thanh, và sửa chúng ở hầu hết các loại tệp mà không đụng đến nội dung tệp.\nĐược công cụ Siêu dữ liệu dùng; không có nó, các trường chỉ đọc.", "profile.images": "Ảnh", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "Chỉ ảnh mới có thể pixel hóa hoặc làm mờ", "error.background_picture": "Không thể đọc ảnh đặt phía sau", "error.background_picture_changed": "Ảnh đặt phía sau đã thay đổi sau khi được hiển thị. Hãy thêm nền lại.", + "error.metadata_refused": "Không thể ghi một số thay đổi siêu dữ liệu như yêu cầu", "hide.save": "Che & lưu", "hide.save_next": "Che & tiếp", "hide.pixelate": "Pixel hóa", @@ -425,6 +427,7 @@ "preset.group_flip": "Lật", "preset.annotate": "Chú thích", "preset.background": "Thêm nền", + "preset.metadata": "Siêu dữ liệu", "preset.flip_horizontal": "Lật ngang", "preset.flip_horizontal.short": "Ngang", "preset.flip_vertical": "Lật dọc", @@ -1072,6 +1075,43 @@ "background.color_sage": "Xanh xô thơm", "background.picture_unreadable": "Không thể đọc ảnh này. Hãy chọn ảnh khác, hoặc một chuyển sắc hay một màu.", "background.keys": "Enter để lưu · Esc để đóng · Tab đi qua các tùy chọn kiểu · phím mũi tên để chọn hoặc di chuyển thanh trượt", + "metadata.search": "Tìm trường và giá trị", + "metadata.fields": "{0} trường", + "metadata.location_warning": "Bất kỳ ai có tệp này đều có thể thấy nó được tạo ở đâu.", + "metadata.read_only_banner": "Đang hiển thị mọi trường. Để sửa, hãy cài ExifTool trong Cài đặt › Bộ xử lý.", + "metadata.unwritable": "Đang hiển thị mọi trường. Filee không thể thay đổi siêu dữ liệu của loại tệp này.", + "metadata.needs_exiftool": "Không thể đọc các trường của tệp này nếu không có ExifTool. Hãy cài trong Cài đặt › Bộ xử lý.", + "metadata.embedded": "Tệp này còn chứa một video hoặc ảnh thứ hai, với siêu dữ liệu riêng không hiển thị và không xóa được ở đây. “{0}” tạo một bản sao sạch, không có chúng.", + "metadata.install": "Cài ExifTool", + "metadata.remove_location": "Xóa vị trí", + "metadata.remove_all": "Xóa tất cả", + "metadata.remove_all_hint": "Giữ hướng ảnh và hồ sơ màu. Các trường bị khóa có thể vẫn còn.", + "metadata.changes_one": "1 thay đổi", + "metadata.changes": "{0} thay đổi", + "metadata.replace": "Thay thế bản gốc", + "metadata.replace_hint": "Bản sao thay thế bản gốc, và bản gốc được chuyển vào Thùng rác", + "metadata.save": "Lưu", + "metadata.save_copy": "Lưu bản sao", + "metadata.save_next": "Lưu & tiếp", + "metadata.add_field": "Thêm trường", + "metadata.one_per_line": "Mỗi dòng một mục", + "metadata.remove_field": "Xóa trường", + "metadata.remove_named": "Xóa {0}", + "metadata.undo": "Hoàn tác", + "metadata.undo_named": "Khôi phục {0}", + "metadata.spoken_removed": "{0}, đã xóa", + "metadata.read_only": "Không thể thay đổi ở đây", + "metadata.pdf_note": "Các giá trị trước đó vẫn có thể khôi phục bên trong PDF. Xóa siêu dữ liệu sẽ loại bỏ chúng vĩnh viễn.", + "metadata.keys": "Enter để sửa trường hoặc lưu · Shift+Enter để xuống dòng · Esc để hủy hoặc đóng · Ctrl+F để tìm", + "metadata.empty": "Tệp này không có siêu dữ liệu.", + "metadata.unreadable": "Không thể đọc siêu dữ liệu của tệp", + "metadata.group.location": "Vị trí", + "metadata.group.camera": "Máy ảnh", + "metadata.group.exposure": "Phơi sáng", + "metadata.group.description": "Mô tả", + "metadata.group.keywords": "Từ khóa", + "metadata.group.document": "Tài liệu", + "metadata.group.file": "Tệp", "presets.editor_title": "Sửa cấu hình sẵn", "presets.delete_confirm": "Xóa “{0}”? Cấu hình sẵn này cũng sẽ bị gỡ khỏi mọi thanh công cụ và nhóm.", "presets.name_required": "Nhập tên", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS, PS và tệp Illustrator cũ hơn → PDF và ảnh, PDF hoặc SVG → EPS/PS.\nSVG và tệp AI tương thích PDF chuyển đổi được mà không cần Ghostscript.", "engines.package.tesseract.name": "Tesseract (nhận dạng văn bản, OCR)", "engines.package.tesseract.description": "Đọc văn bản trong PDF quét và ảnh: PDF có thể tìm kiếm, DOCX, TXT.\nMỗi ngôn ngữ được tải xuống ở lần đầu sử dụng.", + "engines.package.exiftool.name": "ExifTool (siêu dữ liệu)", + "engines.package.exiftool.description": "Đọc mọi trường siêu dữ liệu của ảnh, PDF, video và âm thanh, và sửa chúng ở hầu hết các loại tệp mà không đụng đến nội dung tệp.\nĐược công cụ Siêu dữ liệu dùng; không có nó, các trường chỉ đọc.", "engines.package.ffmpeg.name": "FFmpeg (video và âm thanh)", "engines.package.ffmpeg.description": "Thêm chuyển đổi video và âm thanh, video → GIF hoặc ảnh tĩnh, âm thanh từ video và GIF → MP4.\nChỉ cần cho video và âm thanh.", "engines.package.whisper.name": "Whisper (phụ đề AI)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "Cài bằng Homebrew (brew install ffmpeg), rồi chọn “Kiểm tra lại”.\nHoặc chọn “Dùng bản của tôi…” và chọn thư mục bin của nó.", "engines.guidance.macos.ghostscript": "Cài bằng Homebrew (brew install ghostscript), rồi chọn “Kiểm tra lại”.\nHoặc chọn “Dùng bản của tôi…” và chọn thư mục bin của nó.", "engines.guidance.macos.tesseract": "Cài bằng Homebrew (brew install tesseract), rồi chọn “Kiểm tra lại”.\nHoặc chọn “Dùng bản của tôi…” và chọn thư mục bin của nó.", + "engines.guidance.macos.exiftool": "Cài bằng Homebrew (brew install exiftool), rồi chọn “Kiểm tra lại”.\nHoặc chọn “Dùng bản của tôi…” và chọn thư mục của nó.", "engines.guidance.macos.calibre": "Bản calibre mà Filee tải xuống cần macOS 14 trở lên. Hãy cài calibre 7.26, phiên bản cuối cùng cho macOS này, từ calibre-ebook.com, rồi chọn “Dùng bản của tôi…” và chọn thư mục .app của nó.", "engines.guidance.linux": "Cài {0} bằng trình quản lý gói của bản phân phối, rồi chọn “Dùng bản của tôi…” và chọn thư mục chương trình của nó.", "engines.guidance.linux.libreoffice": "Cài bằng trình quản lý gói (Ubuntu/Debian: sudo apt install libreoffice), rồi chọn “Kiểm tra lại”.\nMở HWP cũng cần Java và tiện ích mở rộng H2Orestart.", "engines.guidance.linux.ghostscript": "Cài bằng trình quản lý gói (Ubuntu/Debian: sudo apt install ghostscript), rồi chọn “Kiểm tra lại”.", "engines.guidance.linux.tesseract": "Cài bằng trình quản lý gói (Ubuntu/Debian: sudo apt install tesseract-ocr), rồi chọn “Kiểm tra lại”.", + "engines.guidance.linux.exiftool": "Cài bằng trình quản lý gói (Ubuntu/Debian: sudo apt install libimage-exiftool-perl), rồi chọn “Kiểm tra lại”.", "engines.guidance.windows": "Filee không có bản tải xuống {0} cho máy tính này.\nHãy chọn “Dùng bản của tôi…” và chọn một bản đã cài.", "engines.use_own_app_folder": "Chọn thư mục chứa {0}.app", "engines.progress_detail": "{0}/{1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/zh-CN.json b/src/Filee.App/Assets/i18n/zh-CN.json index dd02313cb..9ed5dde2c 100644 --- a/src/Filee.App/Assets/i18n/zh-CN.json +++ b/src/Filee.App/Assets/i18n/zh-CN.json @@ -250,6 +250,7 @@ "engine.calibre.description": "少见的电子书格式(LIT、LRF、CHM、PDB、RB…)以及 Kindle 输出(MOBI、AZW3)。\n排在 HWPX writer 之上时,也由它把电子书转为 PDF(长书快得多)。", "engine.libreoffice.description": "读取较旧或少见的格式,如 DOC、PPT、Pages、Keynote、Numbers、WordPerfect、ClarisWorks、Publisher、Visio。\n也能保存为 DOC、XLS、PPT、ODT、ODP、OpenDocument 模板或 EMF。", "engine.translator.description": "使用离线 Ollama 模型在本地翻译文档和电子书。\n也能翻译 SRT 和 WebVTT 字幕:只把字幕文本发送给模型,字幕序号和时间轴保持不变。", + "engine.exiftool.description": "读取图片、PDF、视频和音频的所有元数据字段,并在大多数类型的文件中编辑它们,不改动文件内容。\n供“元数据”工具使用;没有它时,字段只能查看。", "profile.images": "图片", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "只能对图片打马赛克或模糊", "error.background_picture": "无法读取用作背景的图片", "error.background_picture_changed": "用作背景的图片在显示后已更改。请重新添加背景。", + "error.metadata_refused": "部分元数据更改无法按要求写入", "hide.save": "遮盖并保存", "hide.save_next": "遮盖并下一张", "hide.pixelate": "马赛克", @@ -425,6 +427,7 @@ "preset.group_flip": "翻转", "preset.annotate": "批注", "preset.background": "添加背景", + "preset.metadata": "元数据", "preset.flip_horizontal": "水平翻转", "preset.flip_horizontal.short": "水平", "preset.flip_vertical": "垂直翻转", @@ -1072,6 +1075,43 @@ "background.color_sage": "鼠尾草绿", "background.picture_unreadable": "无法读取这张图片。请选择另一张图片,或渐变、颜色。", "background.keys": "Enter 保存 · Esc 关闭 · Tab 在外观选项间移动 · 方向键选择或移动滑块", + "metadata.search": "搜索字段和值", + "metadata.fields": "{0} 个字段", + "metadata.location_warning": "任何拿到此文件的人都能看到它的拍摄地点。", + "metadata.read_only_banner": "正在显示所有字段。要编辑,请在“设置 › 引擎”中安装 ExifTool。", + "metadata.unwritable": "正在显示所有字段。Filee 无法更改此类文件的元数据。", + "metadata.needs_exiftool": "没有 ExifTool 无法读取此文件的字段。请在“设置 › 引擎”中安装。", + "metadata.embedded": "此文件还包含视频或第二张图片,其元数据在此处不显示,也无法清除。“{0}”可生成不含它们的干净副本。", + "metadata.install": "安装 ExifTool", + "metadata.remove_location": "移除位置", + "metadata.remove_all": "全部移除", + "metadata.remove_all_hint": "保留方向和颜色配置文件。锁定的字段可能会保留。", + "metadata.changes_one": "1 处更改", + "metadata.changes": "{0} 处更改", + "metadata.replace": "替换原文件", + "metadata.replace_hint": "副本将取代原文件,原文件会移到回收站/废纸篓", + "metadata.save": "保存", + "metadata.save_copy": "保存副本", + "metadata.save_next": "保存并下一个", + "metadata.add_field": "添加字段", + "metadata.one_per_line": "每行一项", + "metadata.remove_field": "移除字段", + "metadata.remove_named": "移除 {0}", + "metadata.undo": "撤销", + "metadata.undo_named": "恢复 {0}", + "metadata.spoken_removed": "{0},已移除", + "metadata.read_only": "无法在此更改", + "metadata.pdf_note": "旧值仍可在 PDF 中恢复。使用“移除元数据”可彻底删除。", + "metadata.keys": "Enter 编辑字段或保存 · Shift+Enter 换行 · Esc 取消或关闭 · Ctrl+F 搜索", + "metadata.empty": "此文件中没有元数据。", + "metadata.unreadable": "无法读取其元数据", + "metadata.group.location": "位置", + "metadata.group.camera": "相机", + "metadata.group.exposure": "曝光", + "metadata.group.description": "描述", + "metadata.group.keywords": "关键词", + "metadata.group.document": "文档", + "metadata.group.file": "文件", "presets.editor_title": "编辑预设", "presets.delete_confirm": "删除“{0}”?它也会从所有工具栏和组中移除。", "presets.name_required": "请输入名称", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS、PS 和旧版 Illustrator 文件 → PDF 和图片,PDF 或 SVG → EPS/PS。\nSVG 和兼容 PDF 的 AI 文件无需它即可转换。", "engines.package.tesseract.name": "Tesseract(文字识别,OCR)", "engines.package.tesseract.description": "读取扫描 PDF 和图片中的文字:可搜索的 PDF、DOCX、TXT。\n每种语言在首次使用时下载。", + "engines.package.exiftool.name": "ExifTool(元数据)", + "engines.package.exiftool.description": "读取图片、PDF、视频和音频的所有元数据字段,并在大多数类型的文件中编辑它们,不改动文件内容。\n供“元数据”工具使用;没有它时,字段只能查看。", "engines.package.ffmpeg.name": "FFmpeg(视频和音频)", "engines.package.ffmpeg.description": "增加视频和音频转换、视频 → GIF 或静态图片、提取声音以及 GIF → MP4。\n仅视频和音频转换需要它。", "engines.package.whisper.name": "Whisper (AI字幕)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "用 Homebrew 安装(brew install ffmpeg)后点“重新检查”。\n或点“使用我的程序…”选择它的 bin 文件夹。", "engines.guidance.macos.ghostscript": "用 Homebrew 安装(brew install ghostscript)后点“重新检查”。\n或点“使用我的程序…”选择它的 bin 文件夹。", "engines.guidance.macos.tesseract": "用 Homebrew 安装(brew install tesseract)后点“重新检查”。\n或点“使用我的程序…”选择它的 bin 文件夹。", + "engines.guidance.macos.exiftool": "用 Homebrew 安装(brew install exiftool),然后选择“重新检查”。\n或选择“使用我的副本…”并选中其文件夹。", "engines.guidance.macos.calibre": "Filee 下载的 calibre 需要 macOS 14 或更高版本。请从 calibre-ebook.com 安装适用于此 macOS 的最后一个版本 calibre 7.26,再点“使用我的程序…”选择它的 .app 文件夹。", "engines.guidance.linux": "用发行版的软件包管理器安装 {0},再点“使用我的程序…”选择它的程序文件夹。", "engines.guidance.linux.libreoffice": "用软件包管理器安装(Ubuntu/Debian 下运行 sudo apt install libreoffice)后点“重新检查”。\n打开 HWP 还需要 Java 和 H2Orestart 扩展。", "engines.guidance.linux.ghostscript": "用软件包管理器安装(Ubuntu/Debian 下运行 sudo apt install ghostscript)后点“重新检查”。", "engines.guidance.linux.tesseract": "用软件包管理器安装(Ubuntu/Debian 下运行 sudo apt install tesseract-ocr)后点“重新检查”。", + "engines.guidance.linux.exiftool": "用软件包管理器安装(Ubuntu/Debian:sudo apt install libimage-exiftool-perl),然后选择“重新检查”。", "engines.guidance.windows": "没有适用于这台电脑的 {0} 下载。\n点“使用我的程序…”选择已安装的程序。", "engines.use_own_app_folder": "选择包含 {0}.app 的文件夹", "engines.progress_detail": "{0} / {1} · {2}/s · {3}", diff --git a/src/Filee.App/Assets/i18n/zh-TW.json b/src/Filee.App/Assets/i18n/zh-TW.json index e2e0480e8..3be5dbd8d 100644 --- a/src/Filee.App/Assets/i18n/zh-TW.json +++ b/src/Filee.App/Assets/i18n/zh-TW.json @@ -250,6 +250,7 @@ "engine.calibre.description": "少見的電子書格式(LIT、LRF、CHM、PDB、RB…)以及 Kindle 輸出(MOBI、AZW3)。\n排在 HWPX writer 之上時,也由它把電子書轉為 PDF(長書快得多)。", "engine.libreoffice.description": "讀取較舊或少見的格式,如 DOC、PPT、Pages、Keynote、Numbers、WordPerfect、ClarisWorks、Publisher、Visio。\n也能儲存為 DOC、XLS、PPT、ODT、ODP、OpenDocument 範本或 EMF。", "engine.translator.description": "使用離線 Ollama 模型在本地翻譯文件與電子書。\n也能翻譯 SRT 和 WebVTT 字幕:只將字幕文字傳送給模型,字幕序號和時間軸保持不變。", + "engine.exiftool.description": "讀取圖片、PDF、影片和音訊的所有中繼資料欄位,並在大多數類型的檔案中編輯它們,不更動檔案內容。\n供「中繼資料」工具使用;沒有它時,欄位只能檢視。", "profile.images": "圖片", "profile.pdf": "PDF", @@ -308,6 +309,7 @@ "error.cannot_hide": "只能對圖片加上馬賽克或模糊", "error.background_picture": "無法讀取要放在背景的圖片", "error.background_picture_changed": "要放在背景的圖片在顯示後已變更。請重新加入背景。", + "error.metadata_refused": "部分中繼資料變更無法依要求寫入", "hide.save": "遮蓋並儲存", "hide.save_next": "遮蓋並下一張", "hide.pixelate": "馬賽克", @@ -425,6 +427,7 @@ "preset.group_flip": "翻轉", "preset.annotate": "註解", "preset.background": "加入背景", + "preset.metadata": "中繼資料", "preset.flip_horizontal": "水平翻轉", "preset.flip_horizontal.short": "水平", "preset.flip_vertical": "垂直翻轉", @@ -1072,6 +1075,43 @@ "background.color_sage": "鼠尾草綠", "background.picture_unreadable": "無法讀取這張圖片。請選擇另一張圖片,或漸層、顏色。", "background.keys": "Enter 儲存 · Esc 關閉 · Tab 在外觀選項間移動 · 方向鍵選擇或移動滑桿", + "metadata.search": "搜尋欄位和值", + "metadata.fields": "{0} 個欄位", + "metadata.location_warning": "任何拿到此檔案的人都能看到它的拍攝地點。", + "metadata.read_only_banner": "正在顯示所有欄位。若要編輯,請在「設定 › 引擎」中安裝 ExifTool。", + "metadata.unwritable": "正在顯示所有欄位。Filee 無法變更此類檔案的中繼資料。", + "metadata.needs_exiftool": "沒有 ExifTool 無法讀取此檔案的欄位。請在「設定 › 引擎」中安裝。", + "metadata.embedded": "此檔案還包含影片或第二張圖片,其中繼資料在此處不顯示,也無法清除。「{0}」可產生不含它們的乾淨副本。", + "metadata.install": "安裝 ExifTool", + "metadata.remove_location": "移除位置", + "metadata.remove_all": "全部移除", + "metadata.remove_all_hint": "保留方向和色彩描述檔。鎖定的欄位可能會保留。", + "metadata.changes_one": "1 處變更", + "metadata.changes": "{0} 處變更", + "metadata.replace": "取代原始檔", + "metadata.replace_hint": "副本會取代原始檔,原始檔會移到資源回收筒/垃圾桶", + "metadata.save": "儲存", + "metadata.save_copy": "儲存副本", + "metadata.save_next": "儲存並下一個", + "metadata.add_field": "新增欄位", + "metadata.one_per_line": "每行一項", + "metadata.remove_field": "移除欄位", + "metadata.remove_named": "移除 {0}", + "metadata.undo": "復原", + "metadata.undo_named": "還原 {0}", + "metadata.spoken_removed": "{0},已移除", + "metadata.read_only": "無法在此變更", + "metadata.pdf_note": "舊值仍可在 PDF 中復原。使用「移除中繼資料」可徹底刪除。", + "metadata.keys": "Enter 編輯欄位或儲存 · Shift+Enter 換行 · Esc 取消或關閉 · Ctrl+F 搜尋", + "metadata.empty": "此檔案中沒有中繼資料。", + "metadata.unreadable": "無法讀取其中繼資料", + "metadata.group.location": "位置", + "metadata.group.camera": "相機", + "metadata.group.exposure": "曝光", + "metadata.group.description": "描述", + "metadata.group.keywords": "關鍵字", + "metadata.group.document": "文件", + "metadata.group.file": "檔案", "presets.editor_title": "編輯預設", "presets.delete_confirm": "刪除「{0}」?它也會從所有工具列和群組中移除。", "presets.name_required": "請輸入名稱", @@ -1148,6 +1188,8 @@ "engines.package.ghostscript.description": "EPS、PS 和舊版 Illustrator 檔案 → PDF 和圖片,PDF 或 SVG → EPS/PS。\nSVG 和相容 PDF 的 AI 檔案無需它即可轉換。", "engines.package.tesseract.name": "Tesseract(文字辨識,OCR)", "engines.package.tesseract.description": "讀取掃描 PDF 和圖片中的文字:可搜尋的 PDF、DOCX、TXT。\n每種語言在第一次使用時下載。", + "engines.package.exiftool.name": "ExifTool(中繼資料)", + "engines.package.exiftool.description": "讀取圖片、PDF、影片和音訊的所有中繼資料欄位,並在大多數類型的檔案中編輯它們,不更動檔案內容。\n供「中繼資料」工具使用;沒有它時,欄位只能檢視。", "engines.package.ffmpeg.name": "FFmpeg(影片和音訊)", "engines.package.ffmpeg.description": "增加影片和音訊轉換、影片 → GIF 或靜態圖片、擷取聲音以及 GIF → MP4。\n僅影片和音訊轉換需要它。", "engines.package.whisper.name": "Whisper (AI字幕)", @@ -1160,11 +1202,13 @@ "engines.guidance.macos.ffmpeg": "用 Homebrew 安裝(brew install ffmpeg)後點「重新檢查」。\n或點「使用我的程式…」選擇它的 bin 資料夾。", "engines.guidance.macos.ghostscript": "用 Homebrew 安裝(brew install ghostscript)後點「重新檢查」。\n或點「使用我的程式…」選擇它的 bin 資料夾。", "engines.guidance.macos.tesseract": "用 Homebrew 安裝(brew install tesseract)後點「重新檢查」。\n或點「使用我的程式…」選擇它的 bin 資料夾。", + "engines.guidance.macos.exiftool": "用 Homebrew 安裝(brew install exiftool),然後選擇「重新檢查」。\n或選擇「使用我的副本…」並選取其資料夾。", "engines.guidance.macos.calibre": "Filee 下載的 calibre 需要 macOS 14 或更高版本。請從 calibre-ebook.com 安裝適用於此 macOS 的最後一個版本 calibre 7.26,再點「使用我的程式…」選擇它的 .app 資料夾。", "engines.guidance.linux": "用發行版的套件管理員安裝 {0},再點「使用我的程式…」選擇它的程式資料夾。", "engines.guidance.linux.libreoffice": "用套件管理員安裝(Ubuntu/Debian 下執行 sudo apt install libreoffice)後點「重新檢查」。\n開啟 HWP 還需要 Java 和 H2Orestart 擴充功能。", "engines.guidance.linux.ghostscript": "用套件管理員安裝(Ubuntu/Debian 下執行 sudo apt install ghostscript)後點「重新檢查」。", "engines.guidance.linux.tesseract": "用套件管理員安裝(Ubuntu/Debian 下執行 sudo apt install tesseract-ocr)後點「重新檢查」。", + "engines.guidance.linux.exiftool": "用套件管理員安裝(Ubuntu/Debian:sudo apt install libimage-exiftool-perl),然後選擇「重新檢查」。", "engines.guidance.windows": "沒有適用於這台電腦的 {0} 下載。\n點「使用我的程式…」選擇已安裝的程式。", "engines.use_own_app_folder": "選擇包含 {0}.app 的資料夾", "engines.progress_detail": "{0} / {1} · {2}/s · {3}", diff --git a/src/Filee.App/Services/PresetAvailability.cs b/src/Filee.App/Services/PresetAvailability.cs index 32d17b141..a7f32e538 100644 --- a/src/Filee.App/Services/PresetAvailability.cs +++ b/src/Filee.App/Services/PresetAvailability.cs @@ -141,6 +141,10 @@ private PresetJudgement Evaluate(Preset preset, IReadOnlyList format return formats.Any(f => HideService.CanOpen(f.Id)) ? new(PresetUse.Usable, null) : new(PresetUse.Unusable, _loc["error.images_only"]); + if (preset.IsMetadataTool) + return formats.Any(WindowService.MetadataReadable) + ? new(PresetUse.Usable, null) + : new(PresetUse.Unusable, _loc["error.unsupported_source"]); if (preset.IsHideTool) return formats.Any(f => HideService.CanOpen(f.Id)) ? new(PresetUse.Usable, null) diff --git a/src/Filee.App/Services/WindowService.cs b/src/Filee.App/Services/WindowService.cs index 970e9ff42..9fff05d0b 100644 --- a/src/Filee.App/Services/WindowService.cs +++ b/src/Filee.App/Services/WindowService.cs @@ -39,12 +39,14 @@ public sealed class WindowService(ILocalizer loc, IServiceProvider services, Mem private HideWindow? _hideWindow; private AnnotateWindow? _annotateWindow; private BackgroundWindow? _backgroundWindow; + private MetadataWindow? _metadataWindow; private ImageEditWindow? _imageEdit; internal ClipWindow? ClipWindow => _clipWindow; internal HideWindow? HideWindow => _hideWindow; internal AnnotateWindow? AnnotateWindow => _annotateWindow; internal BackgroundWindow? BackgroundWindow => _backgroundWindow; + internal MetadataWindow? MetadataWindow => _metadataWindow; internal ImageEditWindow? ImageEdit => _imageEdit; private readonly HashSet _spreadsheetStudios = []; @@ -206,6 +208,8 @@ public void ShowTool(IReadOnlyList files, Preset preset, PixelPoint? nea ShowAnnotateWindow(files, preset); else if (preset.IsBackgroundTool) ShowBackgroundWindow(files, preset); + else if (preset.IsMetadataTool) + ShowMetadataWindow(files, preset); else if (preset.IsImageEditTool) ShowImageEditWindow(files, preset); // Before the crop: the print studio and the PDF page tools have no Tool of their own, which reads as a crop @@ -370,6 +374,37 @@ public void ShowBackgroundWindow(IReadOnlyList files, Preset preset) win.Activate(); } + /// + /// Opens the "Metadata" window on the files it reads (pictures, PDFs, video, sound), one after another. Each file + /// is written by an ordinary conversion, shown in the toast. + /// + public void ShowMetadataWindow(IReadOnlyList files, Preset preset) + { + var readable = files.Where(f => MetadataReadable(FormatRegistry.Detect(f))).ToList(); + if (readable.Count == 0) + return; + _metadataWindow?.Close(); + var conversions = services.GetRequiredService(); + var vm = new MetadataViewModel(readable, preset, loc, services.GetRequiredService(), new MetadataEngines(), + (path, conversion) => conversions.Start([path], conversion), services.GetService>()); + var win = new MetadataWindow { DataContext = vm }; + vm.InstallRequested += () => ShowMain("engines"); + PrepareWindow(win); + win.Closed += (_, _) => + { + if (_metadataWindow == win) + _metadataWindow = null; + trimmer.Request(); + }; + _metadataWindow = win; + win.Show(); + win.Activate(); + } + + /// The files the Metadata tool opens: pictures, PDFs, video and sound. + internal static bool MetadataReadable(Filee.Core.Formats.FileFormat? format) => + format?.Category is FormatCategory.Image or FormatCategory.Pdf or FormatCategory.Video or FormatCategory.Audio; + /// /// The pixel size of a picture as it is seen, read from its headers: turned as its EXIF orientation says, and the /// largest of an icon's sizes (the one converted). Null when ImageMagick can't read it. diff --git a/src/Filee.App/ViewModels/MetadataViewModel.cs b/src/Filee.App/ViewModels/MetadataViewModel.cs new file mode 100644 index 000000000..a48c96dab --- /dev/null +++ b/src/Filee.App/ViewModels/MetadataViewModel.cs @@ -0,0 +1,783 @@ +// State of the "Metadata" window: one file at a time, every field it carries grouped by where it lives (GPS, IFD0, +// XMP-dc, a PDF's document info …), searchable; with ExifTool, for a kind of file it writes, each value can be edited, +// removed or added and Remove location / Remove everything clear many at once. Saving starts an ordinary conversion +// (MetadataTool) that writes only the metadata, into a copy or in the original's place, shown in the toast. Without +// ExifTool every field is shown read-only (MetadataExtractor). + +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using Avalonia.Media.Imaging; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Filee.App.Services; +using Filee.Core.Formats; +using Filee.Core.Localization; +using Filee.Core.Presets; +using Filee.Core.Settings; +using Filee.Engines.Metadata; +using Microsoft.Extensions.Logging; + +namespace Filee.App.ViewModels; + +/// How the window reads metadata; tests stand in for ExifTool. +public sealed class MetadataEngines +{ + /// + /// ExifTool's program, looked for again each time (it may have been installed since); null without it. + /// + public Func Locate { get; init; } = ExifTool.Locate; + + public Func>> Read { get; init; } = (exifTool, path) => ExifTool.ReadAsync(exifTool, path); + + /// The fields without ExifTool, read-only. + public Func> ReadWithout { get; init; } = MetadataReader.Read; +} + +public sealed partial class MetadataViewModel : ObservableObject, IDisposable +{ + /// Groups with a name of their own, in the order shown; any other comes after them, File last. + private static readonly (string Group, string Key)[] KnownGroups = + [ + ("GPS", "metadata.group.location"), ("IFD0", "metadata.group.camera"), ("ExifIFD", "metadata.group.exposure"), + ("XMP-dc", "metadata.group.description"), ("IPTC", "metadata.group.keywords"), ("PDF", "metadata.group.document"), + ]; + + /// Fields Add field offers, by the group they go in. + internal static readonly IReadOnlyDictionary AddableFields = new Dictionary + { + ["XMP-dc"] = ["Title", "Description", "Creator", "Subject", "Rights"], + ["IPTC"] = ["Keywords", "ObjectName", "Caption-Abstract", "By-line", "CopyrightNotice"], + ["PDF"] = ["Title", "Author", "Subject", "Keywords"], + }; + + /// + /// The kinds of file IPTC is written into; every other one gets XMP (or, a PDF, its document info). + /// + private static readonly HashSet IptcFormats = ["jpg", "tiff", "psd"]; + + private readonly IReadOnlyList _files; + private readonly Preset _tool; + private readonly ILocalizer _loc; + private readonly UserDataStore _store; + private readonly MetadataEngines _engines; + private readonly Action _export; + private readonly ILogger? _log; + private List _rows = []; + private string? _formatId; + private bool _closed; + + /// How each file went: saved, skipped or failed (none yet while it is open). + private readonly CropItemState?[] _outcomes; + + /// The file each of is about. + private readonly List _problemFiles = []; + + public MetadataViewModel(IReadOnlyList files, Preset tool, ILocalizer loc, UserDataStore store, MetadataEngines engines, + Action export, ILogger? log = null) + { + _files = files; + _tool = tool; + _loc = loc; + _store = store; + _engines = engines; + _export = export; + _log = log; + _outcomes = new CropItemState?[files.Count]; + _hasMultipleFiles = files.Count > 1; + _replaceOriginal = MetadataTool.ReplacesOriginal(tool); + loc.LanguageChanged += OnLanguageChanged; + _ = LoadFileAsync(0); + } + + [ObservableProperty] private int _currentIndex; + [ObservableProperty] private string _currentFileName = ""; + [ObservableProperty] private string _details = ""; + [ObservableProperty] private string _counterText = ""; + [ObservableProperty] private bool _hasMultipleFiles; + [ObservableProperty] private Bitmap? _thumbnail; + [ObservableProperty] private string _statusNotice = ""; + [ObservableProperty] private bool _isPdf; + + /// + /// What the file holds beyond the fields shown, when it does (): "" otherwise. + /// + [ObservableProperty] private string _embeddedNotice = ""; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsReadOnly), nameof(ReadOnlyNotice), nameof(CanInstall))] + [NotifyCanExecuteChangedFor(nameof(SaveCommand), nameof(RemoveLocationCommand), nameof(RemoveAllCommand))] + private bool _canEdit; + + /// + /// ExifTool is there but doesn't write this kind of file (MP3, MKV, BMP …): shown read-only all the same. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ReadOnlyNotice), nameof(CanInstall))] + private bool _isUnwritable; + + /// + /// No ExifTool, and MetadataExtractor doesn't read this kind of file (a PDF, MKV, FLAC): nothing to show until it + /// is installed. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ReadOnlyNotice), nameof(CanInstall), nameof(IsEmpty))] + private bool _needsExifTool; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(SaveCommand), nameof(RemoveLocationCommand), nameof(RemoveAllCommand))] + private bool _isBusy; + + /// + /// "Replace the original": the copy takes its place and the original goes to the Recycle Bin / Trash. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(SaveText))] + private bool _replaceOriginal; + + [ObservableProperty] private string _searchText = ""; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(HasChanges), nameof(ChangesText))] + [NotifyCanExecuteChangedFor(nameof(SaveCommand))] + private int _changeCount; + + /// + /// All files are done and some could not be opened or saved: the window stays open on and + /// until it is closed (as "Pixelate / blur" does). + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsEditing))] + private bool _isFinished; + + [ObservableProperty] private string _summary = ""; + [ObservableProperty] private int _doneCount; + [ObservableProperty] private int _skippedCount; + [ObservableProperty] private int _failedCount; + + /// "file: reason" for each file that could not be opened or saved. + public ObservableCollection Problems { get; } = []; + + public bool IsEditing => !IsFinished; + + /// The groups shown (those with a row matching the search). + public ObservableCollection Groups { get; } = []; + + public bool IsEmpty => Groups.Count == 0 && !NeedsExifTool; + public bool IsReadOnly => !CanEdit; + public bool HasChanges => ChangeCount > 0; + public string ChangesText => ChangeCount == 1 ? _loc["metadata.changes_one"] : _loc.Format("metadata.changes", ChangeCount); + + /// + /// Why the fields can't be changed, while they can't: no ExifTool, or a kind of file it doesn't write. + /// + public string ReadOnlyNotice => CanEdit ? "" : _loc[NeedsExifTool ? "metadata.needs_exiftool" : IsUnwritable ? "metadata.unwritable" : "metadata.read_only_banner"]; + + /// Install ExifTool is offered: it would make the fields editable. + public bool CanInstall => !CanEdit && (NeedsExifTool || !IsUnwritable); + + public string SaveText => _loc[ReplaceOriginal ? "metadata.save" : CurrentIndex < _files.Count - 1 ? "metadata.save_next" : "metadata.save_copy"]; + + /// Remove location applies: it was pressed, and every field it takes is still struck through. + public bool RemovesLocation => _removeLocationAsked && !_rows.Any(r => r.CanEdit && ExifTool.IsLocation(r.Field) && !r.IsRemoved); + + /// Remove everything applies: it was pressed, and every field it takes is still struck through. + public bool RemovesAll => _removeAllAsked && !_rows.Any(r => r.CanEdit && !r.IsAdded && !ExifTool.KeptByRemoveAll(r.Field) && !r.IsRemoved); + + private bool _holdsMore; + private bool _removeLocationAsked; + private bool _removeAllAsked; + + /// Raised when the last file is done, or on close. + public event Action? CloseRequested; + + /// Opens Settings → Engines (Install ExifTool). + public event Action? InstallRequested; + + partial void OnSearchTextChanged(string value) => Arrange(); + + partial void OnReplaceOriginalChanged(bool value) => Remember(); + + // ───────── Files ───────── + + /// + /// Opens file ; past the last one, closes, or shows the summary when a file could not be + /// opened or saved. + /// + public async Task LoadFileAsync(int index) + { + if (_closed) + return; + if (index >= _files.Count) + { + Finish(); + return; + } + IsBusy = true; + CurrentIndex = index; + var path = _files[index]; + CurrentFileName = Path.GetFileName(path); + CounterText = _loc.Format("crop.page_counter", index + 1, _files.Count); + _formatId = FormatRegistry.Detect(path)?.Id; + IsPdf = _formatId == "pdf"; + OnPropertyChanged(nameof(SaveText)); + _removeLocationAsked = _removeAllAsked = false; + SearchText = ""; + // HistoryThumbnails' own, shared with Home: never disposed here. + Thumbnail = HistoryThumbnails.CanShow(path) ? await Task.Run(() => HistoryThumbnails.Load(path)) : null; + + var exifTool = _engines.Locate(); + IReadOnlyList fields; + var unwritable = !ExifTool.WritesFile(path); + var needsExifTool = false; + try + { + fields = exifTool is not null ? await _engines.Read(exifTool, path) : await Task.Run(() => _engines.ReadWithout(path)); + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + _log?.LogInformation(ex, "Could not read the metadata of {File}", CurrentFileName); + // Without ExifTool: it would read it. ExifTool failed on it: MetadataExtractor may still read it, + // read-only. + needsExifTool = exifTool is null; + fields = exifTool is null ? [] : await ReadWithoutAsync(path); + unwritable |= exifTool is not null; + if (exifTool is not null && fields.Count == 0) + { + if (_closed) + return; + AddProblem(index, "metadata.unreadable"); + IsBusy = false; + await LoadFileAsync(index + 1); + return; + } + exifTool = null; + } + if (_closed) + return; + IsUnwritable = unwritable; + NeedsExifTool = needsExifTool; + _holdsMore = ExifTool.HoldsMore(fields); + EmbeddedNotice = _holdsMore ? _loc.Format("metadata.embedded", _loc["preset.remove_metadata"]) : ""; + CanEdit = exifTool is not null && !unwritable; + _rows = [.. fields.Where(f => !ExifTool.NotInTheFile.Contains(f.Group)).Select(f => NewRow(f, CanEdit && f.Editable, CanEdit && !f.Editable))]; + ShowDetails(); + Arrange(); + Recount(); + RemoveLocationCommand.NotifyCanExecuteChanged(); + RemoveAllCommand.NotifyCanExecuteChanged(); + IsBusy = false; + } + + private async Task> ReadWithoutAsync(string path) + { + try + { + return await Task.Run(() => _engines.ReadWithout(path)); + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + _log?.LogInformation(ex, "MetadataExtractor could not read {File} either", CurrentFileName); + return []; + } + } + + private MetadataRowViewModel NewRow(MetadataField field, bool canEdit, bool showsLock) => + new(field, LabelOf(field.Tag), _loc, canEdit, showsLock, OnRowChanged); + + private void ShowDetails() + { + var path = _files[CurrentIndex]; + var size = new FileInfo(path) is { Exists: true } info ? EnginePackageState.FormatBytes(info.Length) : ""; + Details = string.Join(" · ", new[] + { + Path.GetExtension(path).TrimStart('.').ToUpperInvariant(), size, Path.GetFileName(Path.GetDirectoryName(path)) ?? "", + _loc.Format("metadata.fields", _rows.Count(r => !r.IsAdded)), + }.Where(part => part.Length > 0)); + } + + /// "DateTimeOriginal" → "Date Time Original", "GPSLatitude" → "GPS Latitude". + internal static string LabelOf(string tag) => Regex.Replace(tag, "(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", " "); + + /// + /// Groups the rows (known groups first, File last), leaving out those that don't match the search. + /// + private void Arrange() + { + // Clearing the groups can take the focus from a box, whose row then asks for this again: done once more after. + if (_arranging) + { + _arrangeAgain = true; + return; + } + _arranging = true; + try + { + do + { + _arrangeAgain = false; + ArrangeGroups(); + } + while (_arrangeAgain); + } + finally + { + _arranging = false; + } + } + + private bool _arranging; + private bool _arrangeAgain; + + private void ArrangeGroups() + { + var search = SearchText.Trim(); + bool Matches(MetadataRowViewModel row) => search.Length == 0 + || row.Label.Contains(search, StringComparison.CurrentCultureIgnoreCase) + || row.Field.Tag.Contains(search, StringComparison.OrdinalIgnoreCase) + || row.Value.Contains(search, StringComparison.CurrentCultureIgnoreCase); + int Order(string group, string family) => + Array.FindIndex(KnownGroups, k => k.Group == group) is >= 0 and var known ? known + : family == "File" ? 1000 : 100; + var addable = CanEdit ? AddableHere() : []; + + var arranged = new List<(int Order, string Group, MetadataGroupViewModel View)>(); + foreach (var group in _rows.GroupBy(r => (r.Field.Family, r.Field.Group))) + { + var rows = group.Where(Matches).ToList(); + if (rows.Count == 0) + continue; + arranged.Add((Order(group.Key.Group, group.Key.Family), group.Key.Group, new MetadataGroupViewModel(TitleOf(group.Key.Family, group.Key.Group), + group.Key.Group, group.Key.Family, rows, + warning: group.Key.Group == "GPS" ? _loc["metadata.location_warning"] : null, + addable: addable.TryGetValue(group.Key.Group, out var tags) ? [.. tags.Where(tag => group.All(r => r.Field.Tag != tag))] : [], + add: AddField))); + } + // Somewhere to add a title or keywords to a file that has none yet. + foreach (var (groupName, tags) in addable) + { + if (search.Length > 0 || arranged.Any(a => a.Group == groupName)) + continue; + arranged.Add((Order(groupName, ""), groupName, new MetadataGroupViewModel(TitleOf("", groupName), groupName, "", [], null, [.. tags], AddField))); + } + Groups.Clear(); + foreach (var (_, _, view) in arranged.OrderBy(a => a.Order).ThenBy(a => a.Group, StringComparer.Ordinal)) + Groups.Add(view); + OnPropertyChanged(nameof(IsEmpty)); + } + + private string TitleOf(string family, string group) => + KnownGroups.FirstOrDefault(k => k.Group == group).Key is { } known ? _loc[known] + : family == "File" ? _loc["metadata.group.file"] : group; + + /// + /// The groups Add field offers for this kind of file: a PDF's document info, IPTC where it is written, XMP. + /// + private Dictionary AddableHere() => + IsPdf ? new() { ["PDF"] = AddableFields["PDF"] } + : _formatId is { } id && IptcFormats.Contains(id) ? new() { ["XMP-dc"] = AddableFields["XMP-dc"], ["IPTC"] = AddableFields["IPTC"] } + : new() { ["XMP-dc"] = AddableFields["XMP-dc"] }; + + private void AddField(MetadataGroupViewModel group, string tag) + { + // A search would hide the new row, and its box with it. + SearchText = ""; + var family = group.Raw switch { "PDF" => "PDF", "IPTC" => "IPTC", _ => "XMP" }; + var key = $"{group.Raw}:{tag}"; + var row = new MetadataRowViewModel(new MetadataField(family, group.Raw, tag, [""], ExifTool.ListFields.Contains(key), true), LabelOf(tag), _loc, true, + false, OnRowChanged) + { + IsAdded = true, + }; + _rows.Add(row); + Arrange(); + row.IsEditing = true; + Recount(); + } + + private void OnRowChanged(MetadataRowViewModel row) + { + // A new field left empty, or removed, is no field. + if (row is { IsAdded: true, IsEditing: false } && (row.Value.Length == 0 || row.IsRemoved)) + { + _rows.Remove(row); + Arrange(); + } + Recount(); + } + + private void Recount() + { + // Remove everything may take only what shows no row of its own to strike through. + ChangeCount = Math.Max(_rows.Count(r => r.IsChanged || r.IsRemoved || r.IsAdded && r.Value.Length > 0), RemovesAll ? 1 : 0); + } + + private bool CanRemoveLocation() => CanEdit && !IsBusy && _rows.Any(r => r.CanEdit && ExifTool.IsLocation(r.Field)); + + /// + /// Whatever the rows offer: what's left may be locked, and go all the same (a JPEG's comment, a PNG's text). + /// + private bool CanRemoveAll() => CanEdit && !IsBusy && !_holdsMore && ExifTool.RemovesEverythingFrom(_files[CurrentIndex]); + + [RelayCommand(CanExecute = nameof(CanRemoveLocation))] + private void RemoveLocation() + { + _removeLocationAsked = true; + foreach (var row in _rows.Where(r => ExifTool.IsLocation(r.Field) && r.CanEdit).ToList()) + row.MarkRemoved(); + DropRemovedAdditions(); + Recount(); + } + + [RelayCommand(CanExecute = nameof(CanRemoveAll))] + private void RemoveAll() + { + _removeAllAsked = true; + foreach (var row in _rows.Where(r => r.CanEdit && !ExifTool.KeptByRemoveAll(r.Field)).ToList()) + row.MarkRemoved(); + DropRemovedAdditions(); + Recount(); + } + + /// Fields added here and removed again are no fields: their rows go. + private void DropRemovedAdditions() + { + if (_rows.RemoveAll(r => r is { IsAdded: true, IsRemoved: true }) > 0) + Arrange(); + } + + /// What the rows say changed, for the conversion. + internal MetadataEdits Changes() + { + var changes = new MetadataEdits { RemoveLocation = RemovesLocation, RemoveAll = RemovesAll }; + foreach (var row in _rows) + { + if (row is { IsRemoved: true, IsAdded: false }) + { + // Taken by Remove location / Remove everything already. + if (!(RemovesAll && !ExifTool.KeptByRemoveAll(row.Field) || RemovesLocation && ExifTool.IsLocation(row.Field))) + changes.Remove.Add(row.Field.Key); + } + else if (!row.IsRemoved && (row.IsChanged || row.IsAdded && row.Value.Length > 0)) + { + // A value emptied is the field removed. + if (row.Items() is { Count: > 0 } items) + changes.Set[row.Field.Key] = items; + else + changes.Remove.Add(row.Field.Key); + } + } + return changes; + } + + // ───────── Saving ───────── + + private bool CanSave() => CanEdit && !IsBusy && HasChanges; + + /// + /// Starts the conversion that writes the changes (shown in the toast), into a copy named by the tool's rule or in + /// the original's place, and opens the next file or closes. When it can't be started, the file stays, to save + /// again or skip. + /// + [RelayCommand(CanExecute = nameof(CanSave))] + private async Task SaveAsync() + { + try + { + _export(_files[CurrentIndex], MetadataTool.ConversionOf(_tool, Changes(), ReplaceOriginal)); + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + _log?.LogError(ex, "Could not start writing the metadata of {File}", CurrentFileName); + AddProblem(CurrentIndex, "error.conversion_failed"); + return; + } + _outcomes[CurrentIndex] = CropItemState.Done; + ClearProblems(CurrentIndex); + await LoadFileAsync(CurrentIndex + 1); + } + + [RelayCommand] + private async Task SkipAsync() + { + if (IsBusy || IsFinished) + return; + _outcomes[CurrentIndex] ??= CropItemState.Skipped; + await LoadFileAsync(CurrentIndex + 1); + } + + [RelayCommand] + private void Close() => CloseRequested?.Invoke(); + + [RelayCommand] + private void Install() => InstallRequested?.Invoke(); + + /// + /// ExifTool may have been installed since the window opened (Install ExifTool): the file is read again with it. + /// + public async Task RecheckEngineAsync() + { + if (!CanEdit && !IsUnwritable && !IsBusy && !IsFinished && _engines.Locate() is not null) + await LoadFileAsync(CurrentIndex); + } + + private void AddProblem(int index, string key) + { + _outcomes[index] = CropItemState.Failed; + var line = $"{Path.GetFileName(_files[index])}: {_loc[key]}"; + Problems.Add(line); + _problemFiles.Add(index); + StatusNotice = line; + } + + /// A file saved after all: what went wrong with it before is no failure any more. + private void ClearProblems(int index) + { + for (var i = _problemFiles.Count - 1; i >= 0; i--) + { + if (_problemFiles[i] != index) + continue; + _problemFiles.RemoveAt(i); + Problems.RemoveAt(i); + } + StatusNotice = Problems.LastOrDefault() ?? ""; + } + + /// + /// Closes after the last file, or stays open on the summary when a file could not be opened or saved. + /// + private void Finish() + { + if (Problems.Count == 0) + { + Close(); + return; + } + FailedCount = _outcomes.Count(o => o == CropItemState.Failed); + DoneCount = _outcomes.Count(o => o == CropItemState.Done); + SkippedCount = _outcomes.Count(o => o == CropItemState.Skipped); + Summary = _loc.Format("toast.done_errors", FailedCount, _files.Count); + StatusNotice = ""; + HasMultipleFiles = false; + IsBusy = false; + _rows = []; + Groups.Clear(); + IsFinished = true; + } + + /// + /// Keeps "Replace the original" in the tool, as given to this window and as saved, and writes the library. + /// + private void Remember() + { + MetadataTool.Remember(_tool, ReplaceOriginal); + if (_store.FindPreset(_tool.Id) is not { IsMetadataTool: true } saved) + return; + MetadataTool.Remember(saved, ReplaceOriginal); + try + { + _store.SaveLibrary(); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + _log?.LogWarning(ex, "Could not remember Replace the original"); + } + } + + private void OnLanguageChanged(object? sender, EventArgs e) + { + if (CurrentIndex < _files.Count) + CounterText = _loc.Format("crop.page_counter", CurrentIndex + 1, _files.Count); + OnPropertyChanged(nameof(SaveText)); + OnPropertyChanged(nameof(ChangesText)); + OnPropertyChanged(nameof(ReadOnlyNotice)); + if (_holdsMore) + EmbeddedNotice = _loc.Format("metadata.embedded", _loc["preset.remove_metadata"]); + if (IsFinished) + { + Summary = _loc.Format("toast.done_errors", FailedCount, _files.Count); + return; + } + // The groups made again: their titles, and every row drawn again with what a screen reader says for it. + if (_rows.Count > 0 || Groups.Count > 0) + { + ShowDetails(); + Arrange(); + } + } + + public void Dispose() + { + _closed = true; + _loc.LanguageChanged -= OnLanguageChanged; + Thumbnail = null; + } +} + +/// A group of fields: its title, where it lives, its rows, what Add field offers. +public sealed partial class MetadataGroupViewModel(string title, string raw, string family, IReadOnlyList rows, string? warning, + IReadOnlyList addable, Action add) +{ + public string Title { get; } = title; + + /// The group's own name (GPS, IFD0, XMP-dc), and the family it belongs to. + public string Raw { get; } = raw; + + /// Beside the title: the group's own name, or its family's when the title is the name itself. + public string Origin { get; } = raw == title ? family : raw; + + public IReadOnlyList Rows { get; } = rows; + public string? Warning { get; } = warning; + public bool HasWarning => Warning is not null; + public IReadOnlyList Addable { get; } = addable; + public bool CanAdd => Addable.Count > 0; + + [RelayCommand] + private void Add(string tag) => add(this, tag); +} + +/// One field as its row shows it: edited in place, removed, put back. +public sealed partial class MetadataRowViewModel : ObservableObject +{ + private readonly Action _changed; + private readonly ILocalizer _loc; + private string _before = ""; + + /// A lock beside the value: the file decides it, while other fields can be edited. + public MetadataRowViewModel(MetadataField field, string label, ILocalizer loc, bool canEdit, bool showsLock, Action changed) + { + Field = field; + Label = label; + _loc = loc; + CanEdit = canEdit; + ShowsLock = showsLock; + _value = field.Value; + _changed = changed; + } + + public MetadataField Field { get; } + public string Label { get; } + public bool CanEdit { get; } + + /// A list (keywords, creators): edited one entry per line. + public bool IsList => Field.IsList; + + /// A lock beside a value the file's structure decides (only when editing is possible at all). + public bool ShowsLock { get; } + + /// A field the user added (not in the file yet). + public bool IsAdded { get; init; } + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsChanged), nameof(SpokenValue), nameof(ShownValue))] + private string _value; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ShowsValue))] + private bool _isEditing; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CanRemove), nameof(CanUndo), nameof(SpokenValue))] + private bool _isRemoved; + + public bool IsChanged => !IsAdded && Value != Field.Value; + public bool ShowsValue => !IsEditing; + public bool CanRemove => CanEdit && !IsRemoved; + public bool CanUndo => !IsAdded && (IsRemoved || IsChanged); + + /// + /// What a screen reader says for the value: the field's name and its value, its entries one after another. + /// + public string SpokenValue + { + get + { + var spoken = $"{Label}: {Value.Replace("\n", ", ", StringComparison.Ordinal)}"; + return IsRemoved ? _loc.Format("metadata.spoken_removed", spoken) : spoken; + } + } + + /// + /// The value as the window shows it. In a right-to-left UI each line is set apart by its own first letter, or a + /// date and "45 deg 30' N" would come out reordered around the digits; left to right there's nothing to add to what + /// can be selected and copied. + /// + public string ShownValue => System.Globalization.CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft + ? string.Join("\n", Value.Split('\n').Select(line => $"\u2068{line}\u2069")) + : Value; + + /// What a screen reader says for × ("Remove Title"). + public string RemoveName => _loc.Format("metadata.remove_named", Label); + + /// What a screen reader says for the arrow that puts it back ("Put Title back"). + public string UndoName => _loc.Format("metadata.undo_named", Label); + + /// + /// The value as the field takes it: one item, or a list's entries, one per line, empty lines left out. + /// + public List Items() => + IsList ? [.. Value.Split('\n').Select(line => line.TrimEnd('\r')).Where(line => line.Length > 0)] + : Value.Length > 0 ? [Value] : []; + + partial void OnIsEditingChanged(bool value) + { + if (value) + _before = Value; + else + _changed(this); + } + + partial void OnValueChanged(string value) + { + OnPropertyChanged(nameof(CanUndo)); + // While typing too: Save is there for the first change as soon as it is one. + _changed(this); + } + + [RelayCommand] + private void Edit() + { + if (CanEdit && !IsRemoved) + IsEditing = true; + } + + /// Enter, or the box losing focus: the value is kept. + [RelayCommand] + private void Commit() => IsEditing = false; + + /// Esc: the value as it was before this edit. + [RelayCommand] + private void CancelEdit() + { + Value = _before; + IsEditing = false; + } + + [RelayCommand] + private void Remove() => MarkRemoved(notify: true); + + internal void MarkRemoved(bool notify = false) + { + if (!CanEdit) + return; + IsEditing = false; + IsRemoved = true; + if (notify) + _changed(this); + } + + [RelayCommand] + private void Undo() + { + IsRemoved = false; + Value = Field.Value; + _changed(this); + } +} + +/// Small converters for the Metadata window. +public static class MetadataConverters +{ + /// "CopyrightNotice" → "Copyright Notice", for Add field's menu. + public static Avalonia.Data.Converters.IValueConverter Label { get; } = + new Avalonia.Data.Converters.FuncValueConverter(tag => MetadataViewModel.LabelOf(tag ?? "")); +} diff --git a/src/Filee.App/Views/MetadataWindow.axaml b/src/Filee.App/Views/MetadataWindow.axaml new file mode 100644 index 000000000..a4e6b8562 --- /dev/null +++ b/src/Filee.App/Views/MetadataWindow.axaml @@ -0,0 +1,259 @@ + + + + M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z + M12.5,8C9.85,8 7.45,9 5.6,10.6L2,7V16H11L7.38,12.38C8.77,11.22 10.54,10.5 12.5,10.5C16.04,10.5 19.05,12.81 20.1,16L22.47,15.22C21.08,11.03 17.15,8 12.5,8Z + M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z + M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + public Func Locate { get; init; } = ExifTool.Locate; + /// The ExifTool found can't run: it is a Perl script, on a computer without Perl. + public Func NeedsPerl { get; init; } = ExifTool.NeedsPerl; + public Func>> Read { get; init; } = (exifTool, path) => ExifTool.ReadAsync(exifTool, path); /// The fields without ExifTool, read-only. @@ -123,6 +126,15 @@ public MetadataViewModel(IReadOnlyList files, Preset tool, ILocalizer lo [NotifyPropertyChangedFor(nameof(ReadOnlyNotice), nameof(CanInstall), nameof(IsEmpty))] private bool _needsExifTool; + /// ExifTool is there as a Perl script, and no Perl to run it: installing it again wouldn't help. + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ReadOnlyNotice), nameof(CanInstall))] + private bool _lacksPerl; + + /// Location that Remove location leaves in the file; empty when none is known of. + [ObservableProperty] + private string _locationNotice = ""; + [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand), nameof(RemoveLocationCommand), nameof(RemoveAllCommand))] private bool _isBusy; @@ -170,10 +182,10 @@ public MetadataViewModel(IReadOnlyList files, Preset tool, ILocalizer lo /// /// Why the fields can't be changed, while they can't: no ExifTool, or a kind of file it doesn't write. /// - public string ReadOnlyNotice => CanEdit ? "" : _loc[NeedsExifTool ? "metadata.needs_exiftool" : IsUnwritable ? "metadata.unwritable" : "metadata.read_only_banner"]; + public string ReadOnlyNotice => CanEdit ? "" : _loc[LacksPerl && (NeedsExifTool || !IsUnwritable) ? "metadata.needs_perl" : NeedsExifTool ? "metadata.needs_exiftool" : IsUnwritable ? "metadata.unwritable" : "metadata.read_only_banner"]; /// Install ExifTool is offered: it would make the fields editable. - public bool CanInstall => !CanEdit && (NeedsExifTool || !IsUnwritable); + public bool CanInstall => !CanEdit && !LacksPerl && (NeedsExifTool || !IsUnwritable); public string SaveText => _loc[ReplaceOriginal ? "metadata.save" : CurrentIndex < _files.Count - 1 ? "metadata.save_next" : "metadata.save_copy"]; @@ -184,6 +196,8 @@ public MetadataViewModel(IReadOnlyList files, Preset tool, ILocalizer lo public bool RemovesAll => _removeAllAsked && !_rows.Any(r => r.CanEdit && !r.IsAdded && !ExifTool.KeptByRemoveAll(r.Field) && !r.IsRemoved); private bool _holdsMore; + private IReadOnlyList _outOfReach = []; + private bool _isVideo; private bool _removeLocationAsked; private bool _removeAllAsked; @@ -226,6 +240,9 @@ public async Task LoadFileAsync(int index) Thumbnail = HistoryThumbnails.CanShow(path) ? await Task.Run(() => HistoryThumbnails.Load(path)) : null; var exifTool = _engines.Locate(); + var lacksPerl = exifTool is not null && _engines.NeedsPerl(exifTool); + if (lacksPerl) + exifTool = null; IReadOnlyList fields; var unwritable = !ExifTool.WritesFile(path); var needsExifTool = false; @@ -256,9 +273,13 @@ public async Task LoadFileAsync(int index) return; IsUnwritable = unwritable; NeedsExifTool = needsExifTool; + LacksPerl = lacksPerl; _holdsMore = ExifTool.HoldsMore(fields); EmbeddedNotice = _holdsMore ? _loc.Format("metadata.embedded", _loc["preset.remove_metadata"]) : ""; CanEdit = exifTool is not null && !unwritable; + _outOfReach = CanEdit ? ExifTool.LocationOutOfReach(fields) : []; + _isVideo = CanEdit && FormatRegistry.Detect(path)?.Category == FormatCategory.Video; + ShowLocationNotice(); _rows = [.. fields.Where(f => !ExifTool.NotInTheFile.Contains(f.Group)).Select(f => NewRow(f, CanEdit && f.Editable, CanEdit && !f.Editable))]; ShowDetails(); Arrange(); @@ -268,6 +289,13 @@ public async Task LoadFileAsync(int index) IsBusy = false; } + /// What saving won't take away: a "successful" Remove location must not read as a clean file. + private void ShowLocationNotice() => LocationNotice = string.Join("\n", new[] + { + _outOfReach.Count > 0 ? _loc.Format("metadata.location_kept", string.Join(", ", _outOfReach.Select(LabelOf))) : "", + _isVideo ? _loc["metadata.location_tracks"] : "", + }.Where(line => line.Length > 0)); + private async Task> ReadWithoutAsync(string path) { try @@ -516,7 +544,8 @@ private async Task SkipAsync() /// public async Task RecheckEngineAsync() { - if (!CanEdit && !IsUnwritable && !IsBusy && !IsFinished && _engines.Locate() is not null) + // Without Perl it is read again once, to say so; what it said already, it doesn't say again each time. + if (!CanEdit && !IsUnwritable && !IsBusy && !IsFinished && _engines.Locate() is { } exifTool && !(LacksPerl && _engines.NeedsPerl(exifTool))) await LoadFileAsync(CurrentIndex); } @@ -592,6 +621,7 @@ private void OnLanguageChanged(object? sender, EventArgs e) OnPropertyChanged(nameof(ReadOnlyNotice)); if (_holdsMore) EmbeddedNotice = _loc.Format("metadata.embedded", _loc["preset.remove_metadata"]); + ShowLocationNotice(); if (IsFinished) { Summary = _loc.Format("toast.done_errors", FailedCount, _files.Count); diff --git a/src/Filee.App/Views/MetadataWindow.axaml b/src/Filee.App/Views/MetadataWindow.axaml index a4e6b8562..048e069b0 100644 --- a/src/Filee.App/Views/MetadataWindow.axaml +++ b/src/Filee.App/Views/MetadataWindow.axaml @@ -76,6 +76,8 @@ + diff --git a/src/Filee.Core/Presets/BuiltInData.cs b/src/Filee.Core/Presets/BuiltInData.cs index 4bf0a9035..7e9d5e61f 100644 --- a/src/Filee.Core/Presets/BuiltInData.cs +++ b/src/Filee.Core/Presets/BuiltInData.cs @@ -331,11 +331,11 @@ public static List BackgroundPresets() => }, ]; - /// Id of the built-in "Metadata" tool (settings schema 68): in the palette, on no donut. + /// Id of the built-in "Metadata" tool (settings schema 69): in the palette, on no donut. public const string MetadataId = "tool-metadata"; /// - /// "Metadata" (settings schema 68): every field of a picture, PDF, video or sound file, edited or removed with + /// "Metadata" (settings schema 69): every field of a picture, PDF, video or sound file, edited or removed with /// ExifTool, the file's content untouched. In the palette only: the donuts it suits are full. /// public static List MetadataPresets() => diff --git a/src/Filee.Core/Presets/Preset.cs b/src/Filee.Core/Presets/Preset.cs index 18fd291f8..b4d0ed0e4 100644 --- a/src/Filee.Core/Presets/Preset.cs +++ b/src/Filee.Core/Presets/Preset.cs @@ -290,7 +290,7 @@ public bool RecognizesTextOf(string sourceFormat) => public const string BackgroundTool = "background"; /// - /// of "Metadata" (settings schema 68): every metadata field of a file shown, edited or removed + /// of "Metadata" (settings schema 69): every metadata field of a file shown, edited or removed /// with ExifTool, the picture or document itself untouched. /// public const string MetadataTool = "metadata"; diff --git a/src/Filee.Engines/Metadata/ExifTool.cs b/src/Filee.Engines/Metadata/ExifTool.cs index f271d88db..29d29ab4c 100644 --- a/src/Filee.Engines/Metadata/ExifTool.cs +++ b/src/Filee.Engines/Metadata/ExifTool.cs @@ -9,13 +9,14 @@ using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; using Filee.Core.Formats; using Filee.Core.Presets; using Filee.Engines.Infrastructure; namespace Filee.Engines.Metadata; -public static class ExifTool +public static partial class ExifTool { /// The longest a read may take. private static readonly TimeSpan ReadLimit = TimeSpan.FromMinutes(2); @@ -106,6 +107,27 @@ public static bool IsLocation(MetadataField field) => || field.Tag.Contains("Location", StringComparison.Ordinal) || field.Tag.Contains("Altitude", StringComparison.Ordinal) || field.Tag is "City" or "State" or "Country" or "CountryCode" or "Sub-location" or "Province-State"; + /// + /// The place fields out of reach of Remove location, each once: a camera's maker notes (Panasonic's City), another + /// block kept whole (a C2PA manifest's ExifGPSLatitude), a PNG's or a PDF's own field, a GeoTIFF's map position, + /// named once for the directory that holds it. ExifTool can't take them one by one, and they stay in plain sight. + /// An empty one, or Panasonic's "---", says no place. + /// + public static IReadOnlyList LocationOutOfReach(IEnumerable fields) => + [ + .. fields.Where(f => !NotInTheFile.Contains(f.Group) && f.Value.Trim() is not ("" or "---") + && (f.Family == "GeoTiff" || ((!EditableFamilies.Contains(f.Family) || f is { Editable: false, Family: "PNG" or "PDF" }) + && SaysAPlace().IsMatch(f.Tag) && !NoPlace().IsMatch(f.Tag)))) + .Select(f => f.Family == "GeoTiff" ? "GeoTiffDirectory" : f.Tag).Distinct(), + ]; + + [GeneratedRegex("GPS|Location|^(Altitude|City[0-9]?|State|Country|CountryCode|Landmark|Province-State)$", RegexOptions.IgnoreCase)] + private static partial Regex SaysAPlace(); + + /// A "location" in a camera or a file: where it focused, its time zone, a spot in the picture. + [GeneratedRegex("(Focus|AF|Subject|WorldTime|Face|Point|Area|Thumbnail|Preview|Offset|Schema|Border|Block|XY|Tick|Data|Profile)[A-Za-z]*Location|LocationOffset|LocationInfoVersion|FromLocation|(?-i:Allocation)", RegexOptions.IgnoreCase)] + private static partial Regex NoPlace(); + /// /// The extensions of the files ExifTool writes (exiftool -listwf): only these get the Metadata tool's /// editing, every other file is shown read-only. @@ -177,6 +199,47 @@ f.Family is "MPF" or "Trailer" || f.Group is "XMP-Container" or "XMP-GContainer" EngineEnvironment.OwnProgram("exiftool", "exiftool.exe") ?? EngineEnvironment.BundledProgram("exiftool", "exiftool.exe") ?? EngineEnvironment.SystemProgram("exiftool", "exiftool.exe"); + /// + /// True when is the Perl script and nothing is there to run it: macOS and Linux, on a + /// computer without Perl. It would be found and fail on every file. + /// + public static bool NeedsPerl(string exifTool) + { + if (OperatingSystem.IsWindows()) + return false; + try + { + using var file = File.OpenRead(exifTool); + var start = new byte[256]; + var first = Encoding.UTF8.GetString(start, 0, file.ReadAtLeast(start, start.Length, throwOnEndOfStream: false)).Split('\n')[0]; + if (!first.StartsWith("#!", StringComparison.Ordinal)) + return false; + var words = first[2..].Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + if (words.Length == 0) + return false; + if (Path.GetFileName(words[0]) != "env") + return !File.Exists(words[0]); + // Through env: the first word that is not an option, its value or a NAME=VALUE setting is what runs. Only + // Perl by name counts; anything else is left to run. + for (var i = 1; i < words.Length; i++) + { + if (words[i] is "-C" or "-P" or "-a" or "-u" or "--argv0" or "--chdir" or "--unset") + i++; + else if (!words[i].StartsWith('-') && !words[i].Contains('=')) + return Path.GetFileName(words[i]).StartsWith("perl", StringComparison.Ordinal) && !OnPath(words[i]); + } + return false; + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + return false; + } + } + + private static bool OnPath(string program) => + (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) + .Any(folder => File.Exists(Path.Combine(folder, program))); + /// /// Every field of , in the file's order. Throws when ExifTool can't read it. /// @@ -401,7 +464,8 @@ internal static TimeSpan WriteLimit(string source) => /// private static IEnumerable>> IptcToUtf8(IReadOnlyList fields, MetadataEdits edits) { - if (fields.Any(f => f is { Group: "IPTC", Tag: "CodedCharacterSet" } && f.Value.Contains("UTF8", StringComparison.OrdinalIgnoreCase))) + // Remove everything takes the character set too: it is written again, whatever the file had. + if (!edits.RemoveAll && fields.Any(f => f is { Group: "IPTC", Tag: "CodedCharacterSet" } && f.Value.Contains("UTF8", StringComparison.OrdinalIgnoreCase))) return []; var again = edits.RemoveAll ? [] : fields .Where(f => f.Family == "IPTC" && f.Editable && !edits.Set.ContainsKey(f.Key) && !edits.Remove.Contains(f.Key) diff --git a/src/Filee.Engines/Metadata/ExifToolConverter.cs b/src/Filee.Engines/Metadata/ExifToolConverter.cs index 6d90d8e0f..1f4d92236 100644 --- a/src/Filee.Engines/Metadata/ExifToolConverter.cs +++ b/src/Filee.Engines/Metadata/ExifToolConverter.cs @@ -23,6 +23,7 @@ public EngineStatus GetStatus() { _executable = ExifTool.Locate(); return _executable is null ? EngineStatus.Unavailable("engine.reason.not_installed") + : ExifTool.NeedsPerl(_executable) ? EngineStatus.Unavailable("engine.reason.needs_perl") : EngineStatus.Available(_executable, EngineEnvironment.UsesFileesCopy("exiftool") ? EngineVersions.Component("exiftool") : null); } diff --git a/tests/Filee.App.Tests/MetadataWindowTests.cs b/tests/Filee.App.Tests/MetadataWindowTests.cs index 944202d3d..51bb7c091 100644 --- a/tests/Filee.App.Tests/MetadataWindowTests.cs +++ b/tests/Filee.App.Tests/MetadataWindowTests.cs @@ -229,6 +229,125 @@ public void Putting_a_field_back_and_removing_it_again_is_Remove_everything_agai Assert.True(vm.Changes() is { RemoveAll: true, Remove: ["IFD0:Orientation"] }); } + [AvaloniaFact] + public void A_place_in_the_maker_notes_is_named_as_staying_and_the_notice_follows_the_language() + { + var engines = new MetadataEngines + { + Locate = () => "/fake/exiftool", + Read = (_, _) => Task.FromResult>( + [new("EXIF", "GPS", "GPSLatitude", "45", true), new("MakerNotes", "Panasonic", "City", "Venice", false), new("MakerNotes", "Sony", "FocusLocation", "1", false)]), + }; + var vm = new MetadataViewModel(Files("trip.jpg"), Tool(), AppHost.Get(), AppHost.Get(), engines, (_, _) => { }); + Wait(() => !vm.IsBusy && vm.Groups.Count > 0); + var loc = AppHost.Get(); + + Assert.Equal(loc.Format("metadata.location_kept", "City"), vm.LocationNotice); + var window = new MetadataWindow { DataContext = vm }; + window.Show(); + RenderTests.Pump(); + Assert.Contains(window.GetVisualDescendants().OfType(), text => text.IsEffectivelyVisible && text.Text == vm.LocationNotice); + + var languages = AppHost.Get(); + languages.SetLanguage("ko"); + try + { + Assert.Equal(loc.Format("metadata.location_kept", "City"), vm.LocationNotice); + Assert.Contains("위치 지우기", vm.LocationNotice, StringComparison.Ordinal); + RenderTests.Pump(); + Assert.Contains(window.GetVisualDescendants().OfType(), text => text.IsEffectivelyVisible && text.Text == vm.LocationNotice); + } + finally + { + languages.SetLanguage("en"); + window.Close(); + } + } + + [AvaloniaTheory] + [InlineData("ride.mp4", true)] + [InlineData("ride.mov", true)] + [InlineData("photo.jpg", false)] + [InlineData("still.heic", false)] + public void A_video_says_its_tracks_can_hold_location_that_isnt_shown(string name, bool video) + { + var engines = new MetadataEngines + { + Locate = () => "/fake/exiftool", + Read = (_, _) => Task.FromResult>([new("EXIF", "IFD0", "Make", "Acme", true)]), + }; + var vm = new MetadataViewModel(Files(name), Tool(), AppHost.Get(), AppHost.Get(), engines, (_, _) => { }); + Wait(() => !vm.IsBusy && vm.Groups.Count > 0); + + Assert.Equal(video ? AppHost.Get()["metadata.location_tracks"] : "", vm.LocationNotice); + } + + [AvaloniaFact] + public void What_cant_be_saved_here_has_no_location_notice() + { + var harness = new FakeExifTool { Installed = false }; + var vm = new MetadataViewModel(Files("ride.mp4"), Tool(), AppHost.Get(), AppHost.Get(), harness.Engines, (_, _) => { }); + Wait(() => !vm.IsBusy && vm.Groups.Count > 0); + + Assert.False(vm.CanEdit); + Assert.Equal("", vm.LocationNotice); + } + + [AvaloniaFact] + public void ExifTool_without_Perl_shows_the_fields_read_only_and_says_what_is_missing() + { + var harness = new FakeExifTool(); + var engines = new MetadataEngines { Locate = harness.Engines.Locate, Read = harness.Engines.Read, ReadWithout = harness.Engines.ReadWithout, NeedsPerl = _ => true }; + var vm = new MetadataViewModel(Files("photo.jpg"), Tool(), AppHost.Get(), AppHost.Get(), engines, (_, _) => { }); + Wait(() => !vm.IsBusy && vm.Groups.Count > 0); + + Assert.False(vm.CanEdit); + Assert.False(vm.CanInstall); + Assert.Equal(AppHost.Get()["metadata.needs_perl"], vm.ReadOnlyNotice); + } + + [AvaloniaFact] + public void A_kind_of_file_ExifTool_doesnt_write_says_so_without_Perl_too() + { + var harness = new FakeExifTool(); + var engines = new MetadataEngines { Locate = harness.Engines.Locate, Read = harness.Engines.Read, ReadWithout = harness.Engines.ReadWithout, NeedsPerl = _ => true }; + var vm = new MetadataViewModel(Files("song.mp3"), Tool(), AppHost.Get(), AppHost.Get(), engines, (_, _) => { }); + Wait(() => !vm.IsBusy && vm.Groups.Count > 0); + + Assert.Equal(AppHost.Get()["metadata.unwritable"], vm.ReadOnlyNotice); + } + + [AvaloniaFact] + public async Task ExifTool_installed_on_a_computer_without_Perl_changes_the_notice_once() + { + var harness = new FakeExifTool { Installed = false }; + var reads = 0; + var engines = new MetadataEngines + { + Locate = harness.Engines.Locate, + Read = harness.Engines.Read, + ReadWithout = path => + { + reads++; + return harness.Engines.ReadWithout(path); + }, + NeedsPerl = _ => true, + }; + var vm = new MetadataViewModel(Files("photo.jpg"), Tool(), AppHost.Get(), AppHost.Get(), engines, (_, _) => { }); + Wait(() => !vm.IsBusy && vm.Groups.Count > 0); + Assert.True(vm.CanInstall); + + harness.Installed = true; + await vm.RecheckEngineAsync(); + Wait(() => !vm.IsBusy && vm.LacksPerl); + + Assert.False(vm.CanInstall); + Assert.Equal(AppHost.Get()["metadata.needs_perl"], vm.ReadOnlyNotice); + var after = reads; + await vm.RecheckEngineAsync(); + Assert.Equal(after, reads); + } + [AvaloniaFact] public void A_file_holding_more_than_its_picture_says_so_and_doesnt_offer_Remove_everything() { diff --git a/tests/Filee.Engines.Tests/MetadataTests.cs b/tests/Filee.Engines.Tests/MetadataTests.cs index d77bdefce..c3112b3cf 100644 --- a/tests/Filee.Engines.Tests/MetadataTests.cs +++ b/tests/Filee.Engines.Tests/MetadataTests.cs @@ -20,8 +20,9 @@ public class MetadataTests(EngineFixture fx) : IClassFixture private static string? FindExifTool() { - if (Environment.GetEnvironmentVariable("FILEE_TEST_EXIFTOOL") is { Length: > 0 } chosen && File.Exists(chosen)) - return chosen; + // Set and not there is a broken setup, as in CI: failing, not skipping, so the writer can't go untested unseen. + if (Environment.GetEnvironmentVariable("FILEE_TEST_EXIFTOOL") is { Length: > 0 } chosen) + return File.Exists(chosen) ? chosen : throw new FileNotFoundException("FILEE_TEST_EXIFTOOL is set, but no such file.", chosen); var name = OperatingSystem.IsWindows() ? "exiftool.exe" : "exiftool"; return (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator) .Concat(["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin"]) @@ -226,6 +227,92 @@ await ExifTool.WriteAsync(exifTool, path, target, Setting( Assert.Null(ValueOf(await ExifTool.ReadAsync(exifTool, emptied, Token), "XMP-dc:Creator")); } + [Fact] + public async Task Remove_everything_with_new_IPTC_text_outside_Latin_1_writes_its_character_set_again() + { + var (path, exifTool) = await PhotoAsync(); + var utf8 = Target(path, "utf8"); + await ExifTool.WriteAsync(exifTool, path, utf8, Setting(("IPTC:ObjectName", ["Порт"])), Token); + var target = Target(path, "clean"); + var edits = Setting(("IPTC:ObjectName", ["李"])); + edits.RemoveAll = true; + + await ExifTool.WriteAsync(exifTool, utf8, target, edits, Token); + + var copy = await ExifTool.ReadAsync(exifTool, target, Token); + Assert.Equal("李", ValueOf(copy, "IPTC:ObjectName")); + Assert.Contains("UTF8", ValueOf(copy, "IPTC:CodedCharacterSet"), StringComparison.Ordinal); + } + + [Fact] + public void Location_out_of_reach_is_a_place_kept_whole_and_not_a_cameras_focus_point() + { + MetadataField Field(string family, string group, string tag, bool editable = false) => new(family, group, tag, "x", editable); + + Assert.Equal(["City", "Country", "City2", "LocalLocationName", "Location", "ExifGPSLatitude", "LocationCity", "ContentLocation", "GPSLatitude", "SigningLocation", "Altitude", "GeoTiffDirectory"], + ExifTool.LocationOutOfReach( + [ + Field("MakerNotes", "Panasonic", "City"), Field("MakerNotes", "Panasonic", "Country"), Field("MakerNotes", "Panasonic", "City2"), + Field("MakerNotes", "Samsung", "LocalLocationName"), Field("JUMBF", "JSON", "Location"), Field("JUMBF", "JSON", "ExifGPSLatitude"), + Field("JUMBF", "JSON", "LocationCity"), Field("JUMBF", "JSON", "ContentLocation"), Field("MakerNotes", "Sony", "GPSLatitude"), + Field("MakerNotes", "Canon", "City"), Field("PDF", "PDF", "SigningLocation"), Field("PNG", "PNG", "Location"), + Field("MakerNotes", "Nikon", "Altitude"), Field("GeoTiff", "GeoTiff", "GeogCitation"), Field("GeoTiff", "GeoTiff", "Projection"), + ])); + Assert.Empty(ExifTool.LocationOutOfReach( + [ + // What Remove location takes, whether the window edits it or not. + Field("EXIF", "GPS", "GPSLatitude", editable: true), Field("EXIF", "GPS", "GPSProcessingMethod"), Field("XMP", "XMP-photoshop", "City", editable: true), + Field("QuickTime", "Keys", "GPSCoordinates", editable: true), + // Worked out from the file. + Field("Composite", "Composite", "GPSPosition"), + // No place. + Field("MakerNotes", "Sony", "FocusLocation"), Field("MakerNotes", "Pentax", "WorldTimeLocation"), Field("Photoshop", "Photoshop", "GlobalAltitude"), + Field("MakerNotes", "Pentax", "HometownCity"), Field("EXIF", "ExifIFD", "SubjectLocation"), + Field("XML", "XML-xsi", "ImageCreationSchemaLocation"), Field("MakerNotes", "Nikon", "LocationInfoVersion"), Field("MakerNotes", "Sony", "MemoryAllocation"), + Field("MakerNotes", "Canon", "BorderLocation"), Field("MakerNotes", "Canon", "BlockLocation"), Field("MakerNotes", "Canon", "XYLocation"), + Field("MakerNotes", "Nikon", "SetClockFromLocationData"), Field("MakerNotes", "Canon", "ExtraTickLocations"), + // A place not set: blank, or Panasonic's "---". + new("MakerNotes", "Panasonic", "State", "", false), new("MakerNotes", "Panasonic", "Landmark", " ", false), new("MakerNotes", "Panasonic", "Country", "---", false), + ])); + } + + [Theory] + [InlineData("#!/bin/sh", false)] + [InlineData("#!/usr/bin/env sh", false)] + [InlineData("#!/usr/bin/env -S sh -e", false)] + [InlineData("#!/no/such/perl -w", true)] + [InlineData("#!/usr/bin/env perl-not-here", true)] + [InlineData("#!/usr/bin/env PERL5LIB=/x perl-not-here", true)] + [InlineData("#!/usr/bin/env -u NAME sh", false)] + [InlineData("#!/usr/bin/env PERL5LIB=/usr/share/perl5 sh", false)] + [InlineData("#!/usr/bin/env -C /opt/perl5 sh", false)] + [InlineData("#!/usr/bin/env PERL5LIB=/usr/share/perl5 perl-not-here", true)] + [InlineData("not a script", false)] + public void A_script_with_nothing_to_run_it_needs_Perl_and_a_program_does_not(string firstLine, bool needsPerl) + { + Assert.SkipWhen(OperatingSystem.IsWindows(), "ExifTool is a program on Windows."); + var script = Path.Combine(fx.NewFolder(), "exiftool"); + File.WriteAllText(script, firstLine + "\nprint 1;\n"); + + Assert.Equal(needsPerl, ExifTool.NeedsPerl(script)); + } + + [Fact] + public void A_perl_in_a_folder_with_an_accented_name_is_found() + { + Assert.SkipWhen(OperatingSystem.IsWindows(), "ExifTool is a program on Windows."); + var folder = Directory.CreateDirectory(Path.Combine(fx.NewFolder(), "jörg")).FullName; + var perl = Path.Combine(folder, "perl"); + File.WriteAllText(perl, ""); + var script = Path.Combine(folder, "exiftool"); + File.WriteAllText(script, $"#!{perl}\nprint 1;\n"); + + Assert.False(ExifTool.NeedsPerl(script)); + } + + [Fact] + public void No_file_at_all_is_not_a_missing_Perl() => Assert.False(ExifTool.NeedsPerl("/no/such/exiftool")); + [Fact] public async Task IPTC_text_outside_Latin_1_is_written_in_UTF_8_and_the_files_Latin_1_text_stays_readable() { diff --git a/tests/Filee.Engines.Tests/OwnCopyTests.cs b/tests/Filee.Engines.Tests/OwnCopyTests.cs index 1ae8eb6fc..0d41ce4ea 100644 --- a/tests/Filee.Engines.Tests/OwnCopyTests.cs +++ b/tests/Filee.Engines.Tests/OwnCopyTests.cs @@ -4,6 +4,7 @@ using Filee.Engines.Ebooks; using Filee.Engines.Infrastructure; using Filee.Engines.Media; +using Filee.Engines.Metadata; using Filee.Engines.Office; namespace Filee.Engines.Tests; @@ -58,6 +59,17 @@ public void A_folder_without_every_program_is_not_used() Assert.Null(EngineEnvironment.OwnCopyFolder("ffmpeg")); Assert.NotEqual(ffmpeg, FfmpegConverter.Locate()?.Ffmpeg); } + + [Fact] + public void An_exiftool_script_with_nothing_to_run_it_reports_the_missing_perl() + { + Assert.SkipWhen(OperatingSystem.IsWindows(), "ExifTool is a program on Windows."); + var script = Path.Combine(_folder, "exiftool"); + File.WriteAllText(script, "#!/no/such/perl\nprint 1;\n"); + EngineEnvironment.OwnCopies = new Dictionary { ["exiftool"] = script }; + + Assert.Equal("engine.reason.needs_perl", new ExifToolConverter().GetStatus().ReasonKey); + } } /// Runs alone: while OwnCopies points at fake programs, no other engine test may convert.