diff --git a/MANIFEST.in b/MANIFEST.in
index a0034f1..0fb4002 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -7,6 +7,8 @@ include pyproject.toml requirements.txt setup.py setup_environment.py MANIFEST.i
include main.py notespacellm_launcher.py manage_translations.py translator.py
include run.bat start.bat build_exe.bat NoteSpaceLLM.ico store_package.json
include locales/translations.json
+include locales/gui_translations.json
+include locales/__init__.py
include scripts/check_store_readiness.py
recursive-include .github/workflows *.yml *.yaml
graft src
diff --git a/README.md b/README.md
index b8f2dad..6eadf7f 100644
--- a/README.md
+++ b/README.md
@@ -9,14 +9,13 @@
[](https://github.com/open-bricks)
[](llms.txt)
-[English](README.md) | [Deutsch](README_de.md)
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
> **Local-first document notebook for research and report generation with configurable local or remote language models.**
> [!NOTE]
> **For AI Agents & Automated Workflows:**
> NoteSpaceLLM provides a machine-readable architecture and feature overview in [`llms.txt`](llms.txt).
-> The Python and Web Companion test suites are configured to use isolated local data and avoid external provider calls.
NoteSpaceLLM is a local-first PySide6 desktop app for working with PDFs,
Word files, Markdown, mail exports, notes, and research folders. It keeps
@@ -96,7 +95,7 @@ graph TD
## Features
- **Document management**: Add files and directories via drag & drop
-- **Auto-indexing**: New documents are processed and indexed immediately for RAG
+- **Automatic extraction and indexing**: New documents are processed right away and prepared for RAG.
- **Selective analysis**: Check/uncheck documents to include or exclude from reports
- **Sub-queries**: Right-click a document for targeted document-specific research
- **Workflow visualization**: Graphical overview of the report pipeline
@@ -105,7 +104,7 @@ graph TD
- **Multi-format export**: Output to MD, PDF, DOCX, HTML, TXT
- **Prompt export**: Save the report context as a Markdown prompt for external LLM workflows
- **Ollama connection profiles**: App-wide provider settings can be reused across projects
-- **Language behavior**: The desktop interface is currently primarily German. The Web/PWA Companion has a six-language UI (de, en, es, zh, ja, ru); the report-language setting changes generated content, not the desktop UI.
+- **Language support**: The desktop and Web/PWA Companion interfaces provide persistent language selectors for English, German, Spanish, Simplified Chinese, Japanese, Russian, French, Hindi, Arabic, Bengali, and Portuguese. Arabic uses a right-to-left layout. The project language field is workspace-export metadata; it does not switch the interface language or determine the language of generated reports.
- **Umlaut-safe UI**: German UI strings use native umlauts; the translation scanner avoids English false positives
- **Profiles**: Reusable output-format combinations
@@ -271,7 +270,7 @@ Additional source smokes run in GitHub Actions:
NoteSpaceLLM/
├── main.py # Entry point
├── requirements.txt # Dependencies
-├── translator.py # Translation-catalog scan/helper; no desktop language switch
+├── translator.py # Translation system and catalog scanner used by the desktop UI
├── locales/ # Catalog inputs for the translation helper
├── src/
│ ├── core/ # Core functionality
@@ -349,90 +348,6 @@ own license is AGPL-3.0-only.
[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) is a separate local knowledge database with SQLite/FTS5 search and optional LLM summaries; it is not integrated with NoteSpaceLLM.
----
-
-## Deutsch
-
-Eine lokale Dokumenten- und Rechercheanwendung mit konfigurierbaren lokalen oder externen Sprachmodellen.
-
-### Features
-
-- **Dokumentenverwaltung**: Dateien und Verzeichnisse per Drag & Drop hinzufügen
-- **Automatische Extraktion & Auto-Indexierung**: Neue Dokumente werden direkt verarbeitet und für RAG vorbereitet
-- **Selektive Auswahl**: Dokumente für Berichterstellung auswählen/abwählen
-- **Detailrecherchen**: Rechtsklick für dokumentspezifische Analysen (Sub-Queries)
-- **Workflow-Visualisierung**: Grafische Darstellung des Berichtsprozesses
-- **Chat-Interface**: Interaktives Chatten über die Dokumente mit LLM
-- **Claude Code Integration**: Wahlweise strukturierte API-Antworten oder Übergabe an eine interaktive Claude-Code-Session
-- **Multi-Format-Export**: Ausgabe in MD, PDF, DOCX, HTML, TXT
-- **Prompt-Export**: Berichtskontext als Markdown-Prompt für externe LLM-Workflows exportieren
-- **Ollama-Verbindungsprofile**: Appweite Provider-Einstellungen können in mehreren Projekten verwendet werden
-- **Sprachoberflächen**: Die Desktop-Oberfläche ist derzeit überwiegend deutsch. Der Web/PWA-Companion bietet UI-Texte in Deutsch, Englisch, Spanisch, Chinesisch, Japanisch und Russisch; die Berichtssprache ändert nicht die Desktop-Oberfläche.
-- **Umlaut-sichere Oberfläche**: Deutsche UI-Texte verwenden echte Umlaute; der Übersetzungs-Scan vermeidet englische False Positives
-- **Profile**: Wiederverwendbare Ausgabeformat-Kombinationen
-
-### Installation
-
-```bash
-cd NoteSpaceLLM
-pip install -r requirements.txt
-python main.py
-```
-
-Unter Windows alternativ per `start.bat`. Build: `build_exe.bat`
-
-### LLM-Konfiguration
-
-#### Ollama (Lokal — Empfohlen)
-
-1. [Ollama installieren](https://ollama.ai)
-2. Modell herunterladen: `ollama pull llama3`
-3. In der App: Menü > LLM > Ollama verwenden
-
-#### Ollama (Remote-Server)
-
-Konfigurierbare Server-URL und Modell werden in den appweiten LLM-Einstellungen
-oder im ausgewählten Verbindungsprofil verwaltet, nicht pro Projekt. Kompatibel
-mit [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack).
-
-Gespeicherte API-Schlüssel verwendet die App über den nativen Zugangsdienst des
-Betriebssystems. `NOTESPACELLM_OLLAMA_API_KEY` überschreibt den gespeicherten
-Schlüssel, wird aber nie gespeichert. Alte Klartextschlüssel werden erst nach
-einer ausdrücklich gestarteten Migration verwendet. Ein entfernter Ollama-Server
-erhält Dokumenttext für Embeddings sowie Prompts und Kontext für die Generierung;
-der API-Schlüssel wird als Bearer-Header an die konfigurierte URL gesendet. HTTP
-verschlüsselt diese Daten nicht.
-
-#### OpenAI / Anthropic (Claude)
-
-```bash
-export OPENAI_API_KEY="..."
-export ANTHROPIC_API_KEY="..."
-```
-
-### Verwendung
-
-1. Dokumente hinzufügen (Drag & Drop oder Buttons)
-2. Dokumente auswählen (Checkboxen)
-3. Optional: Detailrecherchen per Rechtsklick
-4. Hauptfragestellung eingeben
-5. Workflow/Berichtsart wählen
-6. „Bericht erstellen" klicken
-7. Ergebnis exportieren (MD, PDF, DOCX, HTML, TXT)
-8. Optional: „Prompt exportieren" für externe LLM-Workflows
-
-### Plattformstrategie
-
-Desktop-App als autoritative Vollversion. Web/PWA-Companion für Android, iOS und Browser über `notespacellm-workspace-v1.json`. Details im [Exportformat](EXPORTFORMAT.md).
-
-## Verwandtes Projekt
-
-[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) ist eine eigenständige lokale Wissensdatenbank mit SQLite/FTS5-Suche und optionalen LLM-Zusammenfassungen; sie ist nicht in NoteSpaceLLM integriert.
-
----
-
-## Hinweise / Notes
-
-Die Bedingungen der Projektlizenz stehen in [LICENSE](LICENSE). Es gibt keine Zusage für laufende Wartung oder Verfügbarkeit.
+## License and maintenance notice
See [LICENSE](LICENSE) for the project's license terms. The maintainers do not promise ongoing maintenance or availability.
diff --git a/README_ar.md b/README_ar.md
new file mode 100644
index 0000000..a36fd57
--- /dev/null
+++ b/README_ar.md
@@ -0,0 +1,318 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **دفتر مستندات يعتمد على المعالجة المحلية أولاً للبحث وإنشاء التقارير، مع نماذج لغوية محلية أو عن بُعد قابلة للتهيئة.**
+
+> [!NOTE]
+> **لوكلاء الذكاء الاصطناعي وسير العمل الآلي:**
+> يوفّر NoteSpaceLLM نظرة عامة على البنية والميزات بصيغة قابلة للقراءة الآلية في [`llms.txt`](llms.txt).
+
+NoteSpaceLLM هو تطبيق سطح مكتب مبني على PySide6 يعتمد على المعالجة المحلية أولاً للعمل مع ملفات PDF وملفات Word وMarkdown وتصدير البريد والملاحظات ومجلدات الأبحاث. يحتفظ التطبيق ببيانات المشروع محلياً افتراضياً، ويدعم سير عمل Ollama وOpenAI وAnthropic وClaude Code محلياً أو عن بُعد، ويصدّر نتائج التحليل إلى Markdown وPDF وDOCX وHTML وTXT. تتوفر الآن أيضاً واجهة Web/PWA خفيفة المعروفة باسم Companion لمراجعة مساحات العمل المُصدَّرة على Android وiOS والأجهزة التي تعمل عبر المتصفح دون رفع المستندات إلى أي خادم.
+
+**عبارات البحث الأنسب:** بديل NotebookLM، تحليل المستندات بتقنية RAG المحلية، الدردشة مع المستندات محلياً، أداة أبحاث مبنية على PySide6، مولّد تقارير بنماذج محلية، سير عمل المستندات القائم على المعالجة المحلية أولاً.
+
+## البدء السريع
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+على نظام Windows يمكنك أيضاً التشغيل عبر `start.bat`. يمكن إنشاء مشغّل Windows محلي باستخدام `build_exe.bat`.
+
+لإعداد محلي موجَّه يثبّت المتطلبات وينشئ مجلدات بدء سير العمل والملفات الشخصية، شغّل `python setup_environment.py` صراحةً. أما بيانات الحزمة وعمليات البناء فتستخدم نقطة الدخول المنفصلة في setuptools ولا تُشغّله.
+
+شغّل الاختبارات وفحوص الترجمة الأولية بالأمر:
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## البنية المعمارية للنظام
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## الميزات
+
+- **إدارة المستندات**: إضافة الملفات والمجلدات عبر السحب والإفلات
+- **الاستخراج والفهرسة التلقائيان**: تتم معالجة المستندات الجديدة فوراً وتجهيزها لتقنية RAG.
+- **التحليل الانتقائي**: حدّد أو ألغِ تحديد المستندات لتضمينها في التقارير أو استبعادها منها
+- **الاستعلامات الفرعية**: انقر بالزر الأيمن على مستند لإجراء بحث موجَّه خاص بذلك المستند
+- **تصوّر سير العمل**: نظرة رسومية عامة على مسار إنشاء التقرير
+- **واجهة الدردشة**: محادثة تفاعلية مع المستندات المحمّلة باستخدام أي نموذج لغوي
+- **تكامل Claude Code**: استجابات API منظَّمة أو تسليم الجلسة إلى جلسة Claude Code تفاعلية
+- **التصدير متعدد الصيغ**: الإخراج بصيغ MD وPDF وDOCX وHTML وTXT
+- **تصدير المطالبات**: حفظ سياق التقرير كمطالبة Markdown لسير عمل النماذج اللغوية الخارجية
+- **ملفات تعريف اتصال Ollama**: يمكن إعادة استخدام إعدادات الموفّر على مستوى التطبيق عبر المشاريع
+- **دعم اللغات**: توفّر واجهتا سطح المكتب وWeb/PWA Companion محددَي لغة محفوظَين للإنجليزية والألمانية والإسبانية والصينية المبسطة واليابانية والروسية والفرنسية والهندية والعربية والبنغالية والبرتغالية. يستخدم عرض العربية تخطيطًا من اليمين إلى اليسار. حقل لغة المشروع بيانات وصفية تُضمَّن في تصدير مساحة العمل؛ ولا يغيّر لغة الواجهة ولا يحدد لغة التقارير المُنشأة.
+- **واجهة آمنة للأحرف الخاصة**: تستخدم سلاسل واجهة المستخدم الألمانية الأحرف الخاصة بصيغتها الأصلية؛ ويتجنب الماسح الخاص بالترجمة الإيجابيات الكاذبة الإنجليزية
+- **ملفات التعريف**: مجموعات قابلة لإعادة الاستخدام من صيغ الإخراج
+
+## التثبيت
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+تحقق من التبعيات:
+
+```bash
+python main.py --check
+```
+
+## تهيئة النماذج اللغوية
+
+### Ollama (محلي — موصى به)
+
+1. [ثبّت Ollama](https://ollama.ai)
+2. نزّل نموذجاً: `ollama pull llama3`
+3. في التطبيق: القائمة > LLM > Use Ollama
+
+### Ollama (خادم عن بُعد)
+
+يمكن لـ NoteSpaceLLM الاتصال بخادم Ollama عن بُعد، مثلاً عبر [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack):
+
+1. في التطبيق: القائمة > LLM > Settings
+2. **عنوان Ollama:** `http://your-server:11435` (أو المنفذ الخاص بك)
+3. **مفتاح API:** إذا كان الخادم خلف وكيل مصادقة (موصى به)
+
+> **نصيحة:** لا يتضمن Ollama مصادقة مدمجة. للوصول عن بُعد، استخدم وكيلاً عكسياً (مثل Nginx) مع مفتاح API أو Basic Auth.
+
+تُهيَّأ نقطة نهاية Ollama والنموذج في إعدادات التطبيق العامة أو في ملف تعريف الاتصال المحدد، لا لكل مشروع على حدة. تُخزَّن مفاتيح API عبر مخزن بيانات الاعتماد في نظام التشغيل عند حفظها داخل التطبيق. يتجاوز متغيّر البيئة الاختياري `NOTESPACELLM_OLLAMA_API_KEY` المفتاح المحفوظ، ولا يكتبه التطبيق أبداً. لا تُرسَل المفاتيح القديمة المخزّنة كنص عادي إلا بعد أن تنقلها صراحةً من الإعدادات.
+
+عندما يكون خادم Ollama المُهيَّأ بعيدًا، فإنه يتلقى نص المستندات المستخدم في التضمينات (embeddings)، كما يتلقى المطالبات (prompts) والسياق المرسل للتوليد. يُرسَل مفتاح API إلى عنوان URL المُهيَّأ على هيئة ترويسة Bearer. لا يُشفِّر بروتوكول HTTP العادي المحتوى ولا بيانات الاعتماد، أما سلوك HTTPS فيعتمد على نقطة النهاية وشهادتها.
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+يتطلب واجهة سطر الأوامر المحلية:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+يتوفر في التطبيق وضعان:
+- **وضع API**: تُعاد الاستجابة المنظَّمة مباشرة إلى NoteSpaceLLM
+- **وضع الدردشة**: تُسلَّم المطالبة إلى وحدة تحكم تفاعلية في Claude Code
+
+## الاستخدام
+
+### 1. إضافة المستندات
+
+- **السحب والإفلات**: أسقِط الملفات أو المجلدات في اللوحة اليسرى
+- **الزر**: "Add files" أو "Add folder"
+
+### 2. تحديد المستندات
+
+- مربع الاختيار: تضمين المستندات في التحليل أو استبعادها منه
+- الأزرار: "All" / "None" للتحديد الجماعي
+
+### 3. الاستعلامات الفرعية (اختياري)
+
+انقر بالزر الأيمن على مستند:
+- **Create summary**: ملخص تلقائي
+- **Extract information**: البحث عن بيانات محددة
+- **Analyse**: تحليل موجَّه
+- **Ask question**: سؤال محدد عن المستند
+
+### 4. تحديد السؤال الرئيسي
+
+أدخِل سؤال البحث المحوري في لوحة سير العمل.
+
+### 5. اختيار نوع سير العمل/التقرير
+
+- **Analysis**: تحليل شامل مع توصيات
+- **Summary**: ملخص قصير
+- **Research report**: بنية أكاديمية
+- **Comparison**: مقارنة منهجية للمستندات
+
+### 6. إنشاء التقرير
+
+انقر "Create report"، وسيظهر الناتج في اللوحة اليمنى.
+
+### 7. التصدير
+
+اختر تنسيقات المخرجات وانقر "Export".
+
+### 8. تصدير المطالبة
+
+استخدم **"Export prompt"** لحفظ سياق العمل الحالي كملف Markdown لاستخدامه في Claude Code أو في سير عمل LLM آخر.
+
+## الرفيق على الويب/PWA
+
+تحت `web_companion/` يوجد مسار Companion للقراءة فقط يعمل على Android وiOS والمتصفح. يستورد ملفات `notespacellm-workspace-v1.json` المُصدَّرة محليًا في المتصفح، ويعرض التقرير وبيانات المستندات الوصفية والمقتطفات المحددة، ويمكنه تصدير ملاحظات المراجعة بصيغة Markdown. تبقى مساحة العمل في جلسة المتصفح الحالية، ويجب إعادة استيرادها بعد إعادة فتح الصفحة؛ ولا يُحفَظ محليًا إلا ملاحظات المراجعة وإعداد لغة الواجهة.
+
+اختبار المتصفح المحلي:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+اختبارات Companion السريعة:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+خطة اختبار PWA لـ Android/iOS موثقة في `web_companion/PWA_TESTPLAN.md`.
+
+## استراتيجية المنصات
+
+تطبيق سطح المكتب هو التطبيق الكامل للمستندات المحلية وفهرس RAG واتصالات المزودين. يتوفر Web/PWA Companion على Android وiOS والمتصفح لمراجعة ملف `notespacellm-workspace-v1.json` الذي يُنشئه المستخدم.
+للمواد الحساسة، هيِّئ نقاط نهاية محلية للنموذج والتضمينات، وراجع الملفات المُصدَّرة قبل مشاركتها. عقد التبادل موثق في `EXPORTFORMAT.md`.
+
+### اختبارات المصدر على macOS وLinux
+
+تعمل اختبارات مصدر إضافية في GitHub Actions:
+- `tests/platform_smoke.py` — تشغيل PySide6 دون واجهة، واستيراد المستندات، وتصدير التقرير، وتصدير JSON لمساحة العمل
+- `tests/linux_platform_smoke.py` — غلاف خاص بـ Linux فوق `ubuntu-latest`
+
+## بنية المشروع
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## صيغ الملفات المدعومة
+
+| الصيغة | القراءة | الكتابة |
+|--------|---------|---------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ يتطلب ملف .doc الأداة antiword أو LibreOffice
+
+## الخصوصية
+
+يخزّن NoteSpaceLLM بيانات المشروع وفهارس المستندات والملفات الشخصية وإعدادات سير العمل والتصديرات محليًا في `data/` و`profiles/` و`workflows/` و`output/` و`chroma_db/`. هذه المجلدات مستثناة من Git عمدًا.
+
+عند تفعيل مزودي LLM الخارجيين (OpenAI وAnthropic وClaude Code وOllama البعيد)، قد تُرسَل المطالبات ومقتطفات المستندات المحددة إلى تلك الخدمات. كما يتلقى نقطة نهاية Ollama البعيدة نص المستندات المستخدم في التضمينات. للمستندات الحساسة، استخدم نقاط نهاية محلية للنموذج والتضمين، وراجع التقارير المُصدَّرة وسجل الدردشة والمقتطفات المحددة قبل مشاركتها؛ فتصديرات مساحة العمل غير مجهَّلة الهوية.
+
+## نصائح
+
+1. **مجموعات المستندات الكبيرة**: حدِّد المستندات ذات الصلة فقط أولًا للحصول على نتائج أسرع
+2. **الاستعلامات الفرعية**: استخدم استعلامات فرعية موجَّهة للحصول على تحليل أعلى جودة
+3. **Ollama**: تتيح نقطة نهاية Ollama المحلية المعالجة دون اتصال.
+4. **ترتيب سير العمل**: يمكن إعادة ترتيب الخطوات في لوحة سير العمل
+
+## الترخيص
+
+AGPL-3.0-only — انظر [LICENSE](LICENSE)
+
+يُعلن هذا المشروع استخدام PySide6 وPyMuPDF. انظر مراجع التراخيص الأصلية المختارة في [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md)؛ أما ترخيص المشروع نفسه فهو AGPL-3.0-only.
+
+## مشروع ذو صلة
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) قاعدة معرفية محلية منفصلة تعتمد على البحث SQLite/FTS5 وملخصات LLM اختيارية؛ وهي غير مدمجة مع NoteSpaceLLM.
+
+## إشعار الترخيص والصيانة
+
+راجع [LICENSE](LICENSE) لشروط ترخيص المشروع. لا يَعِد القائمون على المشروع بصيانة مستمرة أو بتوافر دائم.
diff --git a/README_bn.md b/README_bn.md
new file mode 100644
index 0000000..a5fdd3d
--- /dev/null
+++ b/README_bn.md
@@ -0,0 +1,331 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **গবেষণা ও প্রতিবেদন তৈরির জন্য স্থানীয়-প্রথম (local-first) নথি নোটবুক, যেখানে কনফিগারযোগ্য স্থানীয় বা দূরবর্তী ভাষা মডেল ব্যবহার করা যায়।**
+
+> [!NOTE]
+> **AI এজেন্ট ও স্বয়ংক্রিয় ওয়ার্কফ্লোর জন্য:**
+> NoteSpaceLLM [`llms.txt`](llms.txt)-এ একটি মেশিন-পাঠযোগ্য আর্কিটেকচার ও বৈশিষ্ট্যের সংক্ষিপ্ত বিবরণ প্রদান করে।
+
+NoteSpaceLLM হলো একটি স্থানীয়-প্রথম PySide6 ডেস্কটপ অ্যাপ্লিকেশন, যা PDF,
+Word ফাইল, Markdown, মেইল এক্সপোর্ট, নোট এবং গবেষণা ফোল্ডারের সাথে কাজ করতে
+ব্যবহৃত হয়। এটি ডিফল্টভাবে প্রকল্পের ডেটা স্থানীয়ভাবে রাখে, স্থানীয় বা দূরবর্তী
+Ollama, OpenAI, Anthropic ও Claude Code ওয়ার্কফ্লো সমর্থন করে এবং বিশ্লেষণের
+ফলাফল Markdown, PDF, DOCX, HTML ও TXT ফরম্যাটে রপ্তানি করে। একটি হালকা Web/PWA
+Companion এখন উপলব্ধ, যার মাধ্যমে কোনো সার্ভারে নথি আপলোড না করেই Android, iOS ও
+ব্রাউজার ডিভাইসে রপ্তানিকৃত ওয়ার্কস্পেস পর্যালোচনা করা যায়।
+
+**সেরা-মানানসই অনুসন্ধান:** NotebookLM বিকল্প, স্থানীয় RAG নথি বিশ্লেষণ,
+স্থানীয় নথি চ্যাট, PySide6 গবেষণা টুল, স্থানীয়-মডেল প্রতিবেদন জেনারেটর,
+স্থানীয়-প্রথম নথি ওয়ার্কফ্লো।
+
+## দ্রুত শুরু
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+Windows-এ আপনি `start.bat` দিয়েও চালু করতে পারেন। একটি স্থানীয় Windows লঞ্চার `build_exe.bat` ব্যবহার করে তৈরি করা যায়।
+
+প্রয়োজনীয় নির্ভরতা ইনস্টল করে এবং স্টার্টার ওয়ার্কফ্লো/প্রোফাইল ফোল্ডার তৈরি করে
+এমন একটি নির্দেশিত স্থানীয় বুটস্ট্র্যাপের জন্য স্পষ্টভাবে `python setup_environment.py` চালান।
+প্যাকেজ মেটাডেটা ও বিল্ডে আলাদা setuptools এন্ট্রি পয়েন্ট ব্যবহৃত হয় এবং তা এটি চালায় না।
+
+টেস্ট চালাতে এবং কম্পাইল স্মোক চেক করতে:
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## সিস্টেম আর্কিটেকচার
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## বৈশিষ্ট্যসমূহ
+
+- **নথি ব্যবস্থাপনা**: ড্র্যাগ ও ড্রপের মাধ্যমে ফাইল ও ডিরেক্টরি যোগ করুন
+- **স্বয়ংক্রিয় নিষ্কাশন ও ইনডেক্সিং**: নতুন নথি সঙ্গে সঙ্গে প্রক্রিয়া করা হয় এবং RAG-এর জন্য প্রস্তুত করা হয়।
+- **নির্বাচনভিত্তিক বিশ্লেষণ**: প্রতিবেদনে অন্তর্ভুক্ত বা বাদ দিতে নথি চেক/আনচেক করুন
+- **উপ-প্রশ্ন (Sub-queries)**: নির্দিষ্ট নথি-ভিত্তিক লক্ষ্যযুক্ত গবেষণার জন্য কোনো নথিতে ডান-ক্লিক করুন
+- **ওয়ার্কফ্লো ভিজ্যুয়ালাইজেশন**: প্রতিবেদন পাইপলাইনের গ্রাফিক্যাল ওভারভিউ
+- **চ্যাট ইন্টারফেস**: যেকোনো LLM দিয়ে লোড করা নথির উপর ইন্টারেক্টিভ চ্যাট
+- **Claude Code ইন্টিগ্রেশন**: কাঠামোবদ্ধ API প্রতিক্রিয়া বা একটি ইন্টারেক্টিভ Claude Code সেশনে হস্তান্তর
+- **মাল্টি-ফরম্যাট রপ্তানি**: MD, PDF, DOCX, HTML, TXT-এ আউটপুট
+- **প্রম্পট রপ্তানি**: বাহ্যিক LLM ওয়ার্কফ্লোর জন্য প্রতিবেদনের প্রসঙ্গ Markdown প্রম্পট হিসেবে সংরক্ষণ করুন
+- **Ollama সংযোগ প্রোফাইল**: অ্যাপ-ব্যাপী প্রদানকারী সেটিংস বিভিন্ন প্রকল্পে পুনর্ব্যবহার করা যায়
+- **ভাষা সমর্থন**: ডেস্কটপ ও Web/PWA Companion ইন্টারফেসে ইংরেজি, জার্মান, স্প্যানিশ, সরলীকৃত চীনা, জাপানি, রুশ, ফরাসি, হিন্দি, আরবি, বাংলা ও পর্তুগিজের জন্য সংরক্ষিত ভাষা নির্বাচক রয়েছে। আরবি ডান-থেকে-বামে বিন্যাস ব্যবহার করে। প্রকল্পের ভাষা-ক্ষেত্রটি ওয়ার্কস্পেস রপ্তানিতে মেটাডেটা হিসেবে অন্তর্ভুক্ত হয়; এটি ইন্টারফেসের ভাষা বদলায় না বা তৈরি প্রতিবেদনের ভাষা নির্ধারণ করে না।
+- **Umlaut-নিরাপদ UI**: জার্মান UI স্ট্রিংয়ে স্থানীয় umlaut ব্যবহৃত হয়; অনুবাদ স্ক্যানার ইংরেজি মিথ্যা পজিটিভ এড়ায়
+- **প্রোফাইল**: পুনর্ব্যবহারযোগ্য আউটপুট-ফরম্যাট সমন্বয়
+
+## ইনস্টলেশন
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+নির্ভরতা পরীক্ষা করুন:
+
+```bash
+python main.py --check
+```
+
+## LLM কনফিগারেশন
+
+### Ollama (স্থানীয় — প্রস্তাবিত)
+
+1. [Ollama ইনস্টল করুন](https://ollama.ai)
+2. একটি মডেল ডাউনলোড করুন: `ollama pull llama3`
+3. অ্যাপে: Menu > LLM > Use Ollama
+
+### Ollama (দূরবর্তী সার্ভার)
+
+NoteSpaceLLM একটি দূরবর্তী Ollama সার্ভারের সাথে সংযুক্ত হতে পারে, যেমন [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack)-এর মাধ্যমে:
+
+1. অ্যাপে: Menu > LLM > Settings
+2. **Ollama URL:** `http://your-server:11435` (বা আপনার পোর্ট)
+3. **API Key:** সার্ভার যদি auth proxy-এর পেছনে থাকে (প্রস্তাবিত)
+
+> **টিপ:** Ollama-তে কোনো অন্তর্নির্মিত প্রমাণীকরণ নেই। দূরবর্তী অ্যাক্সেসের জন্য API key বা Basic Auth সহ একটি reverse proxy (যেমন Nginx) ব্যবহার করুন।
+
+Ollama endpoint ও মডেল অ্যাপ-ব্যাপী সেটিংসে বা নির্বাচিত সংযোগ প্রোফাইলে কনফিগার
+করা হয়, প্রতি প্রকল্পে নয়। অ্যাপে সংরক্ষণ করা হলে API key অপারেটিং সিস্টেমের
+credential store-এর মাধ্যমে সংরক্ষিত হয়। ঐচ্ছিক `NOTESPACELLM_OLLAMA_API_KEY` এনভায়রনমেন্ট ভেরিয়েবল
+সংরক্ষিত কী-কে ওভাররাইড করে এবং অ্যাপ তা কখনো লেখে না। লিগ্যাসি প্লেইনটেক্সট কী
+সেটিংসে আপনি স্পষ্টভাবে মাইগ্রেট না করা পর্যন্ত পাঠানো হয় না।
+
+কনফিগার করা Ollama সার্ভারটি দূরবর্তী হলে, এমবেডিংয়ের জন্য ব্যবহৃত নথির পাঠ্য এবং তৈরি করার জন্য পাঠানো প্রম্পট/প্রসঙ্গ সেই সার্ভার পায়। API key কনফিগার করা URL-এ Bearer হেডার হিসেবে পাঠানো হয়। সাধারণ HTTP বিষয়বস্তু বা প্রমাণীকরণ-তথ্য এনক্রিপ্ট করে না; HTTPS-এর আচরণ নির্ভর করে এন্ডপয়েন্ট ও তার সার্টিফিকেটের ওপর।
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+স্থানীয় CLI প্রয়োজন:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+অ্যাপে দুটি মোড উপলব্ধ:
+- **API মোড**: কাঠামোগত প্রতিক্রিয়া সরাসরি NoteSpaceLLM-এ ফেরত আসে
+- **চ্যাট মোড**: প্রম্পটটি একটি ইন্টারেক্টিভ Claude Code কনসোলে হস্তান্তর করা হয়
+
+## ব্যবহার
+
+### ১. নথি যোগ করুন
+
+- **ড্র্যাগ ও ড্রপ**: বাম প্যানেলে ফাইল বা ফোল্ডার ড্রপ করুন
+- **বোতাম**: "Add files" বা "Add folder"
+
+### ২. নথি নির্বাচন করুন
+
+- চেকবক্স: বিশ্লেষণের জন্য নথি অন্তর্ভুক্ত/বাদ দিন
+- বোতাম: বাল্ক নির্বাচনের জন্য "All" / "None"
+
+### ৩. সাব-কোয়েরি (ঐচ্ছিক)
+
+একটি নথিতে ডান-ক্লিক করুন:
+- **Create summary**: স্বয়ংক্রিয় সারসংক্ষেপ
+- **Extract information**: নির্দিষ্ট তথ্য খুঁজুন
+- **Analyse**: লক্ষ্যভিত্তিক বিশ্লেষণ
+- **Ask question**: নথি সম্পর্কে নির্দিষ্ট প্রশ্ন
+
+### ৪. মূল প্রশ্ন সংজ্ঞায়িত করুন
+
+ওয়ার্কফ্লো প্যানেলে কেন্দ্রীয় গবেষণা প্রশ্নটি লিখুন।
+
+### ৫. ওয়ার্কফ্লো/রিপোর্ট প্রকার বেছে নিন
+
+- **Analysis**: সুপারিশসহ ব্যাপক বিশ্লেষণ
+- **Summary**: সংক্ষিপ্ত সারসংক্ষেপ
+- **Research report**: একাডেমিক কাঠামো
+- **Comparison**: নথির পদ্ধতিগত তুলনা
+
+### ৬. রিপোর্ট তৈরি করুন
+
+"Create report" ক্লিক করুন — আউটপুট ডান প্যানেলে প্রদর্শিত হবে।
+
+### ৭. রপ্তানি
+
+আউটপুট ফর্ম্যাট নির্বাচন করুন এবং "Export" ক্লিক করুন।
+
+### ৮. প্রম্পট রপ্তানি
+
+বর্তমান কার্যপ্রবাহের প্রেক্ষাপট একটি Markdown ফাইলে সংরক্ষণ করতে **"Export prompt"** ব্যবহার করুন, যা Claude Code বা অন্যান্য LLM কার্যপ্রবাহে ব্যবহারের জন্য।
+
+## Web/PWA কম্প্যানিয়ন
+
+`web_companion/`-এর অধীনে Android, iOS এবং ব্রাউজারের জন্য একটি শুধুমাত্র-পঠনযোগ্য Companion স্ট্র্যান্ড রয়েছে। এটি ব্রাউজারে স্থানীয়ভাবে রপ্তানি করা `notespacellm-workspace-v1.json` ফাইল আমদানি করে, রিপোর্ট, নথির মেটাডেটা ও নির্বাচিত অংশ দেখায়, এবং রিভিউ নোটগুলো Markdown হিসেবে রপ্তানি করতে পারে। ওয়ার্কস্পেস বর্তমান ব্রাউজার সেশনে থাকে এবং ব্রাউজার পুনরায় খোলার পর আবার আমদানি করতে হবে; শুধুমাত্র রিভিউ নোট ও UI লোকেল স্থানীয়ভাবে সংরক্ষিত হয়।
+
+স্থানীয় ব্রাউজার পরীক্ষা:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Companion স্মোক:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+Android/iOS PWA পরীক্ষার পরিকল্পনা `web_companion/PWA_TESTPLAN.md`-এ নথিভুক্ত আছে।
+
+## প্ল্যাটফর্ম কৌশল
+
+ডেস্কটপ অ্যাপটি স্থানীয় নথি, RAG ইনডেক্স এবং প্রদানকারী সংযোগের জন্য সম্পূর্ণ অ্যাপ্লিকেশন। Android, iOS ও ব্রাউজার Web/PWA Companion ব্যবহারকারী-তৈরি `notespacellm-workspace-v1.json` রপ্তানি পর্যালোচনার জন্য উপলব্ধ। সংবেদনশীল উপাদানের ক্ষেত্রে স্থানীয় মডেল ও এম্বেডিং এন্ডপয়েন্ট কনফিগার করুন এবং শেয়ার করার আগে রপ্তানি পর্যালোচনা করুন। বিনিময় চুক্তি `EXPORTFORMAT.md`-এ নথিভুক্ত আছে।
+
+### macOS ও Linux সোর্স স্মোক
+
+GitHub Actions-এ অতিরিক্ত সোর্স স্মোক চলে:
+- `tests/platform_smoke.py` — অফস্ক্রিন PySide6 চালু, নথি আমদানি, রিপোর্ট রপ্তানি, ওয়ার্কস্পেস JSON রপ্তানি
+- `tests/linux_platform_smoke.py` — `ubuntu-latest`-এর উপর Linux-নির্দিষ্ট র্যাপার
+
+## প্রকল্পের কাঠামো
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## সমর্থিত ফাইল ফরম্যাট
+
+| ফরম্যাট | পড়া | লেখা |
+|--------|------|-------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ .doc-এর জন্য antiword বা LibreOffice প্রয়োজন
+
+## গোপনীয়তা
+
+NoteSpaceLLM প্রকল্পের ডেটা, নথি ইনডেক্স, প্রোফাইল, ওয়ার্কফ্লো সেটিংস এবং রপ্তানি স্থানীয়ভাবে `data/`, `profiles/`, `workflows/`, `output/` এবং `chroma_db/`-এ সংরক্ষণ করে। এই ফোল্ডারগুলো ইচ্ছাকৃতভাবে Git থেকে বাদ দেওয়া হয়েছে।
+
+বাহ্যিক LLM প্রদানকারী (OpenAI, Anthropic, Claude Code, দূরবর্তী Ollama) সক্রিয় থাকলে প্রম্পট ও নির্বাচিত নথির অংশ সেই সেবাগুলোতে পাঠানো হতে পারে। দূরবর্তী Ollama এন্ডপয়েন্ট এম্বেডিংয়ে ব্যবহৃত নথির টেক্সটও পায়। সংবেদনশীল নথির জন্য স্থানীয় মডেল ও এম্বেডিং এন্ডপয়েন্ট ব্যবহার করুন এবং শেয়ার করার আগে রপ্তানি করা রিপোর্ট, চ্যাট ইতিহাস ও নির্বাচিত অংশ পর্যালোচনা করুন; ওয়ার্কস্পেস রপ্তানি বেনামী করা হয় না।
+
+## টিপস
+
+1. **বড় নথির সেট**: দ্রুত ফলাফলের জন্য প্রথমে শুধু প্রাসঙ্গিক নথি নির্বাচন করুন
+2. **সাব-কোয়েরি**: উচ্চমানের বিশ্লেষণের জন্য লক্ষ্যভিত্তিক সাব-কোয়েরি ব্যবহার করুন
+3. **Ollama**: একটি স্থানীয় Ollama এন্ডপয়েন্ট অফলাইন প্রক্রিয়াকরণ সমর্থন করে।
+4. **ওয়ার্কফ্লো ক্রম**: ওয়ার্কফ্লো প্যানেলে ধাপগুলোর ক্রম পরিবর্তন করা যায়
+
+## লাইসেন্স
+
+AGPL-3.0-only — দেখুন [LICENSE](LICENSE)
+
+এই প্রকল্পটি PySide6 ও PyMuPDF ঘোষণা করে। নির্বাচিত upstream লাইসেন্স রেফারেন্সের জন্য দেখুন [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md); প্রকল্পের নিজস্ব লাইসেন্স AGPL-3.0-only।
+
+## সম্পর্কিত প্রকল্প
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) একটি আলাদা স্থানীয় জ্ঞানভান্ডার, যেখানে SQLite/FTS5 অনুসন্ধান ও ঐচ্ছিক LLM সারসংক্ষেপ রয়েছে; এটি NoteSpaceLLM-এর সাথে সংযুক্ত নয়।
+
+## লাইসেন্স ও রক্ষণাবেক্ষণ বিজ্ঞপ্তি
+
+প্রকল্পের লাইসেন্স শর্তাবলীর জন্য দেখুন [LICENSE](LICENSE)। রক্ষণাবেক্ষকরা চলমান রক্ষণাবেক্ষণ বা প্রাপ্যতার কোনো প্রতিশ্রুতি দেন না।
diff --git a/README_de.md b/README_de.md
index bd77ec5..3f72a8e 100644
--- a/README_de.md
+++ b/README_de.md
@@ -1,24 +1,33 @@
-
+
# NoteSpaceLLM
-[](LICENSE)
+[](LICENSE)
[](https://www.python.org/)
-[]()
-[](https://github.com/file-bricks)
-[](https://github.com/open-bricks)
-[](llms.txt)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
-[Deutsch](README_de.md) | [English](README.md)
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
-> **Lokale Dokumenten- und Rechercheanwendung mit konfigurierbaren lokalen oder externen Sprachmodellen.**
+> **Lokale Dokumenten- und Rechercheanwendung für Analysen und Berichte mit konfigurierbaren lokalen oder externen Sprachmodellen.**
> [!NOTE]
-> **Für KI-Agenten & automatisierte Systeme:**
-> NoteSpaceLLM bietet eine maschinenlesbare Architektur- und Funktionsübersicht in [`llms.txt`](llms.txt).
-> Die Python- und Web-Companion-Tests sind für isolierte lokale Daten eingerichtet und rufen keine externen Modellanbieter auf.
+> **Für KI-Agenten und automatisierte Arbeitsabläufe:**
+> NoteSpaceLLM stellt in [`llms.txt`](llms.txt) eine maschinenlesbare Architektur- und Funktionsübersicht bereit.
-Analysiert lokale Dokumente (PDF, DOCX, TXT, Markdown, XLSX, EML, MSG) lokal. Bei ausdrücklich konfigurierten externen LLM-Anbietern werden ausgewählte Texte und Prompts an diese Anbieter übertragen. Unterstützt lokale und externe Ollama-Server, OpenAI, Anthropic sowie Claude-Code-Workflows. Ergebnisse lassen sich in Markdown, PDF, DOCX, HTML und TXT exportieren. Mit dem Web/PWA-Companion können exportierte Workspaces auch unterwegs auf Smartphone oder Tablet (Android/iOS) eingesehen werden.
+NoteSpaceLLM ist eine lokale PySide6-Desktopanwendung für die Arbeit mit PDFs,
+Word-Dateien, Markdown, E-Mail-Exporten, Notizen und Rechercheordnern. Sie hält
+Projektdaten standardmäßig lokal, unterstützt lokale oder entfernte Ollama-
+Verbindungen, OpenAI, Anthropic und Claude-Code-Arbeitsabläufe und exportiert
+Analyseergebnisse als Markdown, PDF, DOCX, HTML und TXT. Ein schlanker
+Web/PWA-Companion ist verfügbar, um exportierte Workspaces auf Android, iOS und
+im Browser zu prüfen, ohne Dokumente auf einen Server hochzuladen.
+
+**Passende Suchbegriffe:** NotebookLM-Alternative, lokale RAG-Dokumentenanalyse,
+lokaler Dokumentenchat, PySide6-Recherchewerkzeug, Berichtsgenerator mit lokalem
+Modell, lokaler Dokumentenworkflow.
## Schnellstart
@@ -29,13 +38,15 @@ pip install -r requirements.txt
python main.py
```
-Unter Windows kann die Anwendung auch über `start.bat` gestartet werden. Ein Windows-Executable lässt sich mit `build_exe.bat` bauen.
+Unter Windows kann die Anwendung auch mit `start.bat` gestartet werden. Mit
+`build_exe.bat` lässt sich ein lokaler Windows-Launcher erstellen.
-Für einen geführten lokalen Bootstrap, der Abhängigkeiten installiert und
-Startordner für Workflows/Profile anlegt, kann `python setup_environment.py`
-ausdrücklich ausgeführt werden. Paketmetadaten und Builds starten diesen Installer nicht.
+Für einen ausdrücklich gestarteten, geführten lokalen Bootstrap, der die
+Abhängigkeiten installiert und Startordner für Workflows und Profile anlegt,
+führe `python setup_environment.py` aus. Paketmetadaten und Builds verwenden
+einen separaten setuptools-Einstiegspunkt und führen dieses Skript nicht aus.
-Tests und Kompilierungs-Checks ausführen:
+Tests und Kompilierungs-Smoke-Checks lassen sich so ausführen:
```bash
pytest
@@ -48,21 +59,21 @@ npm test
```mermaid
graph TD
- subgraph UI ["PySide6 Desktop-Anwendung"]
- DP["Dokumentenverwaltung (Drag & Drop)"]
- WP["Workflow & Berichts-Generator"]
- CP["Interaktive Chat-Schnittstelle"]
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
OP["Multi-Format Exporter"]
end
- subgraph Engine ["Lokale RAG-Engine"]
- TE["Text-Extraktor (PDF/DOCX/TXT/XLSX/EML)"]
- CDB[("ChromaDB Vektordatenbank")]
- RM["Berichtsgenerator & Prompts"]
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
end
- subgraph LLM ["LLM-Integrationen"]
- OL["Ollama (Lokal / Remote)"]
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
OAI["OpenAI API"]
ANT["Anthropic Claude"]
CC["Claude Code CLI"]
@@ -83,35 +94,36 @@ graph TD
JSON --> SW
```
-## Hauptfunktionen
+## Funktionen
-- **Dokumentenverwaltung**: Dateien und Ordner per Drag & Drop hinzufügen
-- **Auto-Indizierung**: Neue Dokumente werden sofort für die RAG-Vektordatenbank (ChromaDB) verarbeitet
-- **Selektive Analyse**: Dokumente gezielt per Checkbox für Berichte aktivieren oder deaktivieren
-- **Unteranfragen (Sub-queries)**: Rechtsklick auf Dokumente für gezielte Einzeldokument-Recherche
-- **Workflow-Visualisierung**: Grafische Übersicht der Berichtspipeline
-- **Chat-Schnittstelle**: Interaktiver Chat über alle geladenen Dokumente mit beliebigen LLMs
-- **Claude Code Integration**: Strukturierte Antwort-Prompts oder Übergabe an eine interaktive Claude-Code-Session
-- **Multi-Format Export**: Ausgabe nach MD, PDF, DOCX, HTML, TXT
-- **Prompt Export**: Kontext als Markdown-Prompt für externe KI-Tools speichern
-- **Ollama-Verbindungsprofile**: Appweite Provider-Einstellungen können in mehreren Projekten verwendet werden
-- **Desktop-Oberfläche**: Derzeit überwiegend deutsch; die Berichtssprache ändert den Bericht, nicht die Desktop-Oberfläche.
-- **Web/PWA Companion**: Oberfläche zur Durchsicht exportierter Workspaces mit Sprachwahl in Deutsch, Englisch, Spanisch, Chinesisch, Japanisch und Russisch
+- **Dokumentenverwaltung:** Dateien und Verzeichnisse per Drag & Drop hinzufügen
+- **Automatische Extraktion und Indizierung:** Neue Dokumente werden sofort verarbeitet und für RAG vorbereitet.
+- **Gezielte Analyse:** Dokumente für Berichte per Kontrollkästchen ein- oder ausschließen
+- **Teilabfragen:** Ein Rechtsklick auf ein Dokument startet eine gezielte Recherche zu diesem Dokument.
+- **Workflow-Visualisierung:** Grafische Übersicht über die Berichtspipeline
+- **Chatoberfläche:** Interaktiver Chat mit beliebigen LLMs über die geladenen Dokumente
+- **Claude-Code-Integration:** Strukturierte API-Antworten oder Übergabe an eine interaktive Claude-Code-Sitzung
+- **Export in mehreren Formaten:** Ausgabe als MD, PDF, DOCX, HTML und TXT
+- **Prompt-Export:** Den Berichtskontext als Markdown-Prompt für externe LLM-Arbeitsabläufe speichern
+- **Ollama-Verbindungsprofile:** Appweite Provider-Einstellungen lassen sich projektübergreifend wiederverwenden
+- **Sprachunterstützung:** Desktop- und Web-/PWA-Oberfläche bieten dauerhaft gespeicherte Sprachauswahlen für Englisch, Deutsch, Spanisch, vereinfachtes Chinesisch, Japanisch, Russisch, Französisch, Hindi, Arabisch, Bengalisch und Portugiesisch. Für Arabisch wird ein Rechts-nach-links-Layout verwendet. Das Sprachfeld des Projekts ist Workspace-Export-Metadatum; es ändert weder die Oberflächensprache noch legt es die Sprache generierter Berichte fest.
+- **Umlautsichere Oberfläche:** Deutsche UI-Texte verwenden echte Umlaute; der Übersetzungsscanner vermeidet englische Fehlalarme.
+- **Profile:** Wiederverwendbare Kombinationen von Ausgabeformaten
-## Installation & Abhängigkeiten
+## Installation
```bash
-# Repository klonen
+# Clone or copy the project folder
cd NoteSpaceLLM
-# Python-Abhängigkeiten installieren
+# Install dependencies
pip install -r requirements.txt
-# Anwendung starten
+# Start the app
python main.py
```
-Abhängigkeiten überprüfen:
+Abhängigkeiten prüfen:
```bash
python main.py --check
@@ -119,44 +131,235 @@ python main.py --check
## LLM-Konfiguration
-### Ollama (Lokal — Empfohlen)
+### Ollama (lokal — empfohlen)
1. [Ollama installieren](https://ollama.ai)
-2. Modell herunterladen: `ollama pull llama3`
+2. Ein Modell herunterladen: `ollama pull llama3`
3. In der App: Menü > LLM > Ollama verwenden
-### Ollama (Remote Server)
+### Ollama (entfernter Server)
-NoteSpaceLLM kann mit entfernten Ollama-Instanzen verbunden werden (z. B. via [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack)):
+NoteSpaceLLM kann sich mit einem entfernten Ollama-Server verbinden, zum
+Beispiel über [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack):
1. In der App: Menü > LLM > Einstellungen
-2. **Ollama URL:** `http://dein-server:11435`
-3. **API Key:** Falls der Server durch einen Auth-Proxy geschützt ist
+2. **Ollama-URL:** `http://your-server:11435` (oder den verwendeten Port)
+3. **API-Schlüssel:** Falls der Server hinter einem Authentifizierungs-Proxy liegt (empfohlen)
-Server-URL und Modell werden in den appweiten LLM-Einstellungen oder im
-ausgewählten Verbindungsprofil verwaltet, nicht pro Projekt. Gespeicherte
-API-Schlüssel verwendet die App über den nativen Zugangsdienst des
-Betriebssystems. `NOTESPACELLM_OLLAMA_API_KEY` überschreibt den gespeicherten
-Schlüssel, wird aber nie gespeichert. Alte Klartextschlüssel werden erst nach
-einer ausdrücklich gestarteten Migration verwendet.
+> **Tipp:** Ollama bietet keine integrierte Authentifizierung. Verwende für den
+> Fernzugriff einen Reverse-Proxy (zum Beispiel Nginx) mit API-Schlüssel oder
+> Basic Auth.
-Ein entfernter Ollama-Server erhält Dokumenttext für Embeddings sowie Prompts
-und Kontext für die Generierung. Der API-Schlüssel wird als Bearer-Header an die
-konfigurierte URL gesendet. HTTP verschlüsselt diese Inhalte und Zugangsdaten
-nicht; bei HTTPS hängt der Schutz vom Endpunkt und dessen Zertifikat ab.
+Der Ollama-Endpunkt und das Modell werden in den appweiten Einstellungen oder
+im ausgewählten Verbindungsprofil konfiguriert, nicht pro Projekt. In der App
+gespeicherte API-Schlüssel werden über den Zugangsdienst des Betriebssystems
+gespeichert. Die optionale Umgebungsvariable
+`NOTESPACELLM_OLLAMA_API_KEY` überschreibt den gespeicherten Schlüssel; die App
+schreibt sie niemals selbst. Vorhandene Klartextschlüssel werden erst gesendet,
+wenn du sie ausdrücklich in den Einstellungen migrierst.
-## Lizenz & Community
+Wenn der konfigurierte Ollama-Server entfernt läuft, erhält er Dokumenttext für
+Embeddings sowie Prompts und Kontext für die Generierung. Der API-Schlüssel wird
+als Bearer-Header an die konfigurierte URL gesendet. Bei reinem HTTP werden
+weder Inhalte noch Zugangsdaten verschlüsselt; bei HTTPS hängt der Schutz vom
+Endpunkt und dessen Zertifikat ab.
-- **Lizenz**: AGPL-3.0-only
-- **Verzeichnis**: Teil des `file-bricks` Ökosystems
-- **Lokale Datenhaltung**: Projektdateien und der Vektorindex werden standardmäßig lokal gespeichert; konfigurierte externe Anbieter erhalten die für eine Anfrage verwendeten Texte und Prompts.
+### OpenAI
-## Verwandtes Projekt
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+Die lokale CLI wird benötigt:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+In der App stehen zwei Modi zur Verfügung:
+- **API-Modus:** Eine strukturierte Antwort wird direkt an NoteSpaceLLM zurückgegeben.
+- **Chat-Modus:** Der Prompt wird an eine interaktive Claude-Code-Konsole übergeben.
+
+## Verwendung
+
+### 1. Dokumente hinzufügen
+
+- **Drag & Drop:** Dateien oder Ordner in den linken Bereich ziehen
+- **Schaltflächen:** „Dateien hinzufügen“ oder „Ordner hinzufügen“
+
+### 2. Dokumente auswählen
+
+- Kontrollkästchen: Dokumente in die Analyse ein- oder ausschließen
+- Schaltflächen: „Alle“ oder „Keine“ für die Mehrfachauswahl
+
+### 3. Teilabfragen (optional)
+
+Rechtsklick auf ein Dokument:
+- **Zusammenfassung erstellen:** Automatische Zusammenfassung
+- **Informationen extrahieren:** Bestimmte Angaben finden
+- **Analysieren:** Gezielte Analyse
+- **Frage stellen:** Eine konkrete Frage zum Dokument
+
+### 4. Hauptfrage festlegen
+
+Gib die zentrale Recherchefrage im Workflow-Bereich ein.
+
+### 5. Workflow- oder Berichtstyp wählen
+
+- **Analyse:** Umfassende Analyse mit Empfehlungen
+- **Zusammenfassung:** Kurze Zusammenfassung
+- **Recherchebericht:** Wissenschaftlicher Aufbau
+- **Vergleich:** Systematischer Vergleich von Dokumenten
+
+### 6. Bericht erstellen
+
+Klicke auf „Bericht erstellen“ – die Ausgabe erscheint im rechten Bereich.
+
+### 7. Exportieren
+
+Wähle die Ausgabeformate und klicke auf „Exportieren“.
+
+### 8. Prompt exportieren
+
+Verwende **„Prompt exportieren“**, um den aktuellen Arbeitskontext als
+Markdown-Datei für Claude Code oder andere LLM-Arbeitsabläufe zu speichern.
-[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) ist eine eigenständige lokale Wissensdatenbank mit SQLite/FTS5-Suche und optionalen LLM-Zusammenfassungen; sie ist nicht in NoteSpaceLLM integriert.
+## Web-/PWA-Companion
+
+Unter `web_companion/` befindet sich ein schreibgeschützter Companion für
+Android, iOS und Browser. Er importiert exportierte
+`notespacellm-workspace-v1.json`-Dateien lokal im Browser, zeigt den Bericht,
+Dokumentenmetadaten und ausgewählte Ausschnitte an und kann Prüfnotizen als
+Markdown exportieren. Der Workspace bleibt in der aktuellen Browsersitzung und
+muss nach einem Neustart erneut importiert werden; nur Prüfnotizen und die
+Oberflächensprache werden lokal gespeichert.
+
+Lokaler Browser-Test:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Companion-Smoke-Test:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+Der Android-/iOS-PWA-Testplan steht in `web_companion/PWA_TESTPLAN.md`.
+
+## Plattformstrategie
+
+Die Desktopanwendung bietet den vollständigen Funktionsumfang für lokale
+Dokumente, den RAG-Index und Provider-Verbindungen. Der Web-/PWA-Companion für
+Android, iOS und Browser dient dazu, einen selbst erstellten
+`notespacellm-workspace-v1.json`-Export zu prüfen. Bei sensiblen Inhalten
+konfiguriere lokale Modell- und Embedding-Endpunkte und prüfe Exporte vor der
+Weitergabe. Der Austauschvertrag ist in `EXPORTFORMAT.md` beschrieben.
+
+### macOS- und Linux-Smoke-Tests aus dem Quellcode
+
+Zusätzliche Quellcode-Smokes laufen in GitHub Actions:
+- `tests/platform_smoke.py` — PySide6-Start ohne sichtbares Fenster, Dokumentimport, Berichtsexport und Workspace-JSON-Export
+- `tests/linux_platform_smoke.py` — Linux-spezifischer Wrapper auf `ubuntu-latest`
+
+## Projektstruktur
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## Unterstützte Dateiformate
+
+| Format | Lesen | Schreiben |
+|--------|-------|-----------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ Für .doc wird antiword oder LibreOffice benötigt.
+
+## Datenschutz
+
+NoteSpaceLLM speichert Projektdaten, Dokumentindizes, Profile,
+Workflow-Einstellungen und Exporte lokal in `data/`, `profiles/`, `workflows/`,
+`output/` und `chroma_db/`. Diese Ordner sind absichtlich von Git ausgeschlossen.
+
+Wenn externe LLM-Anbieter (OpenAI, Anthropic, Claude Code oder entferntes
+Ollama) aktiviert sind, können Prompts und ausgewählte Dokumentausschnitte an
+diese Dienste übertragen werden. Ein entfernter Ollama-Endpunkt erhält außerdem
+Dokumenttext für Embeddings. Verwende für sensible Dokumente lokale Modell- und
+Embedding-Endpunkte und prüfe exportierte Berichte, Chatverläufe und ausgewählte
+Ausschnitte vor der Weitergabe; Workspace-Exporte sind nicht anonymisiert.
+
+## Tipps
+
+1. **Große Dokumentmengen:** Wähle zunächst nur relevante Dokumente aus, damit die Verarbeitung schneller läuft.
+2. **Teilabfragen:** Gezielte Teilabfragen verbessern die Qualität der Analyse.
+3. **Ollama:** Ein lokaler Ollama-Endpunkt ermöglicht die Verarbeitung ohne Internetverbindung.
+4. **Workflow-Reihenfolge:** Die Schritte lassen sich im Workflow-Bereich neu anordnen.
+
+## Lizenz
+
+AGPL-3.0-only — siehe [LICENSE](LICENSE).
+
+Das Projekt deklariert PySide6 und PyMuPDF. Die ausgewählten Lizenzhinweise
+der Abhängigkeiten stehen in [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md);
+die Projektlizenz ist AGPL-3.0-only.
+
+## Verwandtes Projekt
----
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) ist eine
+separate lokale Wissensdatenbank mit SQLite/FTS5-Suche und optionalen
+LLM-Zusammenfassungen; sie ist nicht in NoteSpaceLLM integriert.
-## Hinweise
+## Lizenz- und Wartungshinweis
-Die Bedingungen der Projektlizenz stehen in [LICENSE](LICENSE). Es gibt keine Zusage für laufende Wartung oder Verfügbarkeit.
+Die Lizenzbedingungen des Projekts stehen in [LICENSE](LICENSE). Die
+Maintainer versprechen weder fortlaufende Wartung noch dauerhafte Verfügbarkeit.
diff --git a/README_es.md b/README_es.md
new file mode 100644
index 0000000..1759222
--- /dev/null
+++ b/README_es.md
@@ -0,0 +1,331 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **Cuaderno de documentos local-first para investigación y generación de informes con modelos de lenguaje locales o remotos configurables.**
+
+> [!NOTE]
+> **Para agentes de IA y flujos de trabajo automatizados:**
+> NoteSpaceLLM proporciona una descripción de arquitectura y funcionalidades legible por máquinas en [`llms.txt`](llms.txt).
+
+NoteSpaceLLM es una aplicación de escritorio local-first basada en PySide6 para trabajar con PDF,
+archivos de Word, Markdown, exportaciones de correo, notas y carpetas de investigación. Mantiene los
+datos del proyecto en local por defecto, admite flujos de trabajo con Ollama, OpenAI, Anthropic y Claude Code
+locales o remotos, y exporta los resultados del análisis a Markdown,
+PDF, DOCX, HTML y TXT. Ahora está disponible un Companion Web/PWA ligero para revisar
+espacios de trabajo exportados en Android, iOS y dispositivos con navegador sin subir documentos a un servidor.
+
+**Búsquedas más relevantes:** alternativa a NotebookLM, análisis de documentos RAG local,
+chat de documentos local, herramienta de investigación con PySide6, generador de informes con modelos locales,
+flujo de trabajo de documentos local-first.
+
+## Inicio rápido
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+En Windows también puede iniciarlo mediante `start.bat`. Un lanzador local para Windows puede compilarse con `build_exe.bat`.
+
+Para una inicialización local guiada que instala los requisitos y crea las carpetas iniciales de
+flujos de trabajo y perfiles, ejecute `python setup_environment.py` de forma explícita. Los metadatos del paquete y las compilaciones
+usan el punto de entrada de setuptools independiente y no lo ejecutan.
+
+Ejecute las pruebas y las comprobaciones rápidas de compilación con:
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## Arquitectura del sistema
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## Funcionalidades
+
+- **Gestión de documentos**: Añada archivos y directorios mediante arrastrar y soltar
+- **Extracción e indexación automáticas**: Los nuevos documentos se procesan de inmediato y se preparan para RAG.
+- **Análisis selectivo**: Marque/desmarque documentos para incluirlos o excluirlos de los informes
+- **Subconsultas**: Haga clic derecho en un documento para una investigación específica de ese documento
+- **Visualización del flujo de trabajo**: Vista gráfica general de la canalización de informes
+- **Interfaz de chat**: Chat interactivo sobre los documentos cargados con cualquier LLM
+- **Integración con Claude Code**: Respuestas de API estructuradas o traspaso a una sesión interactiva de Claude Code
+- **Exportación multiformato**: Salida a MD, PDF, DOCX, HTML, TXT
+- **Exportación de prompts**: Guarde el contexto del informe como prompt en Markdown para flujos de trabajo con LLM externos
+- **Perfiles de conexión de Ollama**: La configuración de proveedor válida para toda la aplicación puede reutilizarse entre proyectos
+- **Compatibilidad de idiomas**: Las interfaces de escritorio y del Web/PWA Companion ofrecen selectores de idioma persistentes para inglés, alemán, español, chino simplificado, japonés, ruso, francés, hindi, árabe, bengalí y portugués. El árabe usa una disposición de derecha a izquierda. El campo de idioma del proyecto es metadato de la exportación del espacio de trabajo; no cambia el idioma de la interfaz ni determina el idioma de los informes generados.
+- **Interfaz compatible con los umlauts alemanes**: Los textos de la interfaz en alemán usan los caracteres originales; el escáner de traducciones evita falsos positivos en inglés
+- **Perfiles**: Combinaciones reutilizables de formatos de salida
+
+## Instalación
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+Compruebe las dependencias:
+
+```bash
+python main.py --check
+```
+
+## Configuración de LLM
+
+### Ollama (local — recomendado)
+
+1. [Instale Ollama](https://ollama.ai)
+2. Descargue un modelo: `ollama pull llama3`
+3. En la aplicación: Menú > LLM > Usar Ollama
+
+### Ollama (servidor remoto)
+
+NoteSpaceLLM puede conectarse a un servidor Ollama remoto, por ejemplo mediante [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack):
+
+1. En la aplicación: Menú > LLM > Configuración
+2. **URL de Ollama:** `http://your-server:11435` (o su puerto)
+3. **Clave API:** Si el servidor está detrás de un proxy de autenticación (recomendado)
+
+> **Consejo:** Ollama no incluye autenticación integrada. Para el acceso remoto, use un proxy inverso (p. ej., Nginx) con clave API o autenticación básica.
+
+El endpoint y el modelo de Ollama se configuran en la configuración válida para toda la aplicación o en el
+perfil de conexión seleccionado, no por proyecto. Las claves API se almacenan mediante el almacén de credenciales
+del sistema operativo cuando se guardan en la aplicación. La variable de entorno opcional
+`NOTESPACELLM_OLLAMA_API_KEY` sustituye la clave guardada y
+la aplicación nunca la escribe. Las claves heredadas en texto plano no se envían hasta que
+las migre explícitamente en la configuración.
+
+Cuando el servidor Ollama configurado es remoto, recibe el texto de los documentos utilizado para los embeddings y los prompts/contexto enviados para la generación. La clave API se envía a la URL configurada como cabecera Bearer. El HTTP simple no cifra ni el contenido ni las credenciales; el comportamiento de HTTPS depende del endpoint y de su certificado.
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+Requiere la CLI local:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+La aplicación ofrece dos modos:
+- **Modo API**: la respuesta estructurada se devuelve directamente a NoteSpaceLLM
+- **Modo chat**: entrega el prompt a una consola interactiva de Claude Code
+
+## Uso
+
+### 1. Añadir documentos
+
+- **Arrastrar y soltar**: suelte archivos o carpetas en el panel izquierdo
+- **Botón**: "Add files" o "Add folder"
+
+### 2. Seleccionar documentos
+
+- Casilla: incluye/excluye documentos para el análisis
+- Botones: "All" / "None" para la selección masiva
+
+### 3. Subconsultas (opcional)
+
+Haga clic derecho en un documento:
+- **Create summary**: resumen automático
+- **Extract information**: buscar datos específicos
+- **Analyse**: análisis dirigido
+- **Ask question**: pregunta específica sobre el documento
+
+### 4. Definir la pregunta principal
+
+Introduzca la pregunta central de investigación en el panel de flujo de trabajo.
+
+### 5. Elegir el tipo de flujo de trabajo/informe
+
+- **Analysis**: análisis exhaustivo con recomendaciones
+- **Summary**: resumen breve
+- **Research report**: estructura académica
+- **Comparison**: comparación sistemática de documentos
+
+### 6. Generar el informe
+
+Haga clic en "Create report"; la salida aparece en el panel derecho.
+
+### 7. Exportar
+
+Seleccione los formatos de salida y haga clic en "Export".
+
+### 8. Exportar el prompt
+
+Use **"Export prompt"** para guardar el contexto de trabajo actual como archivo Markdown para usarlo en Claude Code u otros flujos de trabajo con LLM.
+
+## Compañero Web/PWA
+
+Bajo `web_companion/` se encuentra un compañero de solo lectura para Android, iOS y navegador. Importa archivos `notespacellm-workspace-v1.json` exportados de forma local en el navegador, muestra el informe, los metadatos de los documentos y los fragmentos seleccionados, y puede exportar notas de revisión en Markdown. El espacio de trabajo permanece en la sesión actual del navegador y debe volver a importarse tras reabrirlo; solo las notas de revisión y el idioma de la interfaz se guardan localmente.
+
+Prueba local en el navegador:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Pruebas rápidas del compañero:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+El plan de pruebas de la PWA para Android/iOS está documentado en `web_companion/PWA_TESTPLAN.md`.
+
+## Estrategia de plataforma
+
+La aplicación de escritorio es la aplicación completa para documentos locales, el índice RAG y las conexiones con proveedores. El compañero Web/PWA para Android, iOS y navegador está disponible para revisar una exportación `notespacellm-workspace-v1.json` creada por el usuario. Para material sensible, configure endpoints locales de modelo y embeddings, y revise las exportaciones antes de compartirlas. El contrato de intercambio está documentado en `EXPORTFORMAT.md`.
+
+### Pruebas rápidas de código fuente en macOS y Linux
+
+Pruebas rápidas adicionales se ejecutan en GitHub Actions:
+- `tests/platform_smoke.py` — inicio sin pantalla de PySide6, importación de documentos, exportación de informes, exportación de JSON del espacio de trabajo
+- `tests/linux_platform_smoke.py` — envoltorio específico de Linux sobre `ubuntu-latest`
+
+## Estructura del proyecto
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## Formatos de archivo compatibles
+
+| Formato | Lectura | Escritura |
+|--------|------|-------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ .doc requiere antiword o LibreOffice
+
+## Privacidad
+
+NoteSpaceLLM almacena localmente los datos de proyectos, los índices de documentos, los perfiles, la configuración de flujos de trabajo y las exportaciones en `data/`, `profiles/`, `workflows/`, `output/` y `chroma_db/`. Estas carpetas se excluyen intencionadamente de Git.
+
+Cuando se habilitan proveedores externos de LLM (OpenAI, Anthropic, Claude Code, Ollama remoto), los prompts y los fragmentos de documentos seleccionados pueden transmitirse a esos servicios. Un endpoint remoto de Ollama también recibe el texto de los documentos utilizado para los embeddings. Para documentos sensibles, use endpoints locales de modelo y embeddings, y revise los informes exportados, el historial de chat y los fragmentos seleccionados antes de compartirlos; las exportaciones del espacio de trabajo no están anonimizadas.
+
+## Consejos
+
+1. **Conjuntos grandes de documentos**: seleccione primero solo los documentos relevantes para obtener resultados más rápidos
+2. **Subconsultas**: use subconsultas dirigidas para un análisis de mayor calidad
+3. **Ollama**: un endpoint local de Ollama permite el procesamiento sin conexión
+4. **Orden del flujo de trabajo**: los pasos pueden reordenarse en el panel de flujo de trabajo
+
+## Licencia
+
+AGPL-3.0-only — véase [LICENSE](LICENSE)
+
+Este proyecto declara PySide6 y PyMuPDF. Consulte las referencias de licencia upstream seleccionadas en [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md); la licencia propia del proyecto es AGPL-3.0-only.
+
+## Proyecto relacionado
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) es una base de conocimiento local separada con búsqueda SQLite/FTS5 y resúmenes opcionales con LLM; no está integrada con NoteSpaceLLM.
+
+## Aviso de licencia y mantenimiento
+
+Consulte [LICENSE](LICENSE) para los términos de licencia del proyecto. Los mantenedores no prometen mantenimiento ni disponibilidad continuos.
diff --git a/README_fr.md b/README_fr.md
new file mode 100644
index 0000000..09a439b
--- /dev/null
+++ b/README_fr.md
@@ -0,0 +1,332 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **Carnet documentaire privilégiant le stockage local pour la recherche et la génération de rapports, avec des modèles de langage configurables, exécutés localement ou à distance.**
+
+> [!NOTE]
+> **Pour les agents IA et les flux de travail automatisés :**
+> NoteSpaceLLM fournit une présentation de l'architecture et des fonctionnalités lisible par machine dans [`llms.txt`](llms.txt).
+
+NoteSpaceLLM est une application de bureau PySide6 qui privilégie le traitement local et permet de travailler avec des PDF,
+des fichiers Word, du Markdown, des exports de messagerie, des notes et des dossiers de recherche. Elle conserve
+les données de projet localement par défaut, prend en charge les flux de travail Ollama locaux ou distants, OpenAI,
+Anthropic et Claude Code, et exporte les résultats d'analyse en Markdown,
+PDF, DOCX, HTML et TXT. Un Companion Web/PWA léger est désormais disponible pour
+consulter les espaces de travail exportés sur Android, iOS et les appareils équipés d'un navigateur, sans
+téléverser de documents sur un serveur.
+
+**Recherches les plus pertinentes :** alternative à NotebookLM, analyse de documents RAG locale,
+conversation locale avec des documents, outil de recherche PySide6, générateur de rapports reposant sur un modèle local,
+flux de travail documentaire privilégiant le local.
+
+## Démarrage rapide
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+Sous Windows, vous pouvez également lancer l'application via `start.bat`. Un lanceur Windows local peut être créé avec `build_exe.bat`.
+
+Pour une initialisation locale guidée qui installe les dépendances et crée les dossiers de départ
+des flux de travail et des profils, exécutez explicitement `python setup_environment.py`. Les métadonnées du paquet et les
+builds utilisent le point d'entrée setuptools distinct et ne l'exécutent pas.
+
+Exécutez les tests et les vérifications de compilation rapides avec :
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## Architecture du système
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## Fonctionnalités
+
+- **Gestion des documents** : ajoutez des fichiers et des dossiers par glisser-déposer
+- **Extraction et indexation automatiques** : les nouveaux documents sont traités immédiatement et préparés pour le RAG.
+- **Analyse sélective** : cochez/décochez les documents à inclure ou exclure des rapports
+- **Sous-requêtes** : faites un clic droit sur un document pour une recherche ciblée sur ce document
+- **Visualisation du flux de travail** : vue graphique du pipeline de rapport
+- **Interface de discussion** : discussion interactive sur les documents chargés avec n'importe quel LLM
+- **Intégration de Claude Code** : réponses d'API structurées ou transmission à une session Claude Code interactive
+- **Export multi-format** : sortie en MD, PDF, DOCX, HTML, TXT
+- **Export de prompt** : enregistrez le contexte du rapport sous forme de prompt Markdown pour des flux de travail LLM externes
+- **Profils de connexion Ollama** : les paramètres de fournisseur à l'échelle de l'application peuvent être réutilisés d'un projet à l'autre
+- **Prise en charge des langues** Les interfaces de bureau et du Web/PWA Companion proposent des sélecteurs de langue persistants pour l’anglais, l’allemand, l’espagnol, le chinois simplifié, le japonais, le russe, le français, l’hindi, l’arabe, le bengali et le portugais. L’arabe utilise une disposition de droite à gauche. Le champ de langue du projet est une métadonnée de l’export de l’espace de travail ; il ne change ni la langue de l’interface ni celle des rapports générés.
+- **Interface compatible avec les umlauts** : les chaînes de l'interface allemande utilisent les umlauts natifs ; le scanner de traduction évite les faux positifs en anglais
+- **Profils** : combinaisons réutilisables de formats de sortie
+
+## Installation
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+Vérifiez les dépendances :
+
+```bash
+python main.py --check
+```
+
+## Configuration des LLM
+
+### Ollama (local — recommandé)
+
+1. [Installez Ollama](https://ollama.ai)
+2. Téléchargez un modèle : `ollama pull llama3`
+3. Dans l'application : Menu > LLM > Utiliser Ollama
+
+### Ollama (serveur distant)
+
+NoteSpaceLLM peut se connecter à un serveur Ollama distant, par exemple via [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack) :
+
+1. Dans l'application : Menu > LLM > Paramètres
+2. **URL Ollama :** `http://your-server:11435` (ou votre port)
+3. **Clé API :** à renseigner si le serveur se trouve derrière un proxy d'authentification (recommandé)
+
+> **Astuce :** Ollama ne dispose pas d'authentification intégrée. Pour un accès distant, utilisez un proxy inverse (par exemple Nginx) avec une clé API ou une authentification Basic.
+
+Le point de terminaison Ollama et le modèle se configurent dans les paramètres à l'échelle de l'application ou
+dans le profil de connexion sélectionné, et non par projet. Les clés API sont stockées via le
+gestionnaire d'identifiants du système d'exploitation lorsqu'elles sont enregistrées dans l'application. La variable d'environnement
+facultative `NOTESPACELLM_OLLAMA_API_KEY` remplace la clé enregistrée et n'est jamais écrite par
+l'application. Les clés héritées en clair ne sont pas envoyées tant que vous ne les migrez pas explicitement
+dans les paramètres.
+
+Lorsque le serveur Ollama configuré est distant, il reçoit le texte des documents utilisé pour les embeddings, ainsi que les prompts et le contexte transmis pour la génération. La clé API est envoyée à l’URL configurée dans un en-tête Bearer. HTTP en clair ne chiffre ni le contenu ni les identifiants ; le comportement de HTTPS dépend du point de terminaison et de son certificat.
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+La CLI locale est requise :
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+Deux modes sont disponibles dans l’application :
+- **Mode API** : la réponse structurée est renvoyée directement dans NoteSpaceLLM
+- **Mode chat** : le prompt est transmis à une console Claude Code interactive
+
+## Utilisation
+
+### 1. Ajouter des documents
+
+- **Glisser-déposer** : déposez des fichiers ou des dossiers dans le panneau de gauche
+- **Bouton** : « Ajouter des fichiers » ou « Ajouter un dossier »
+
+### 2. Sélectionner des documents
+
+- Case à cocher : inclure ou exclure des documents de l’analyse
+- Boutons : « Tout » / « Aucun » pour la sélection groupée
+
+### 3. Sous-requêtes (facultatif)
+
+Faites un clic droit sur un document :
+- **Créer un résumé** : résumé automatique
+- **Extraire des informations** : rechercher des données précises
+- **Analyser** : analyse ciblée
+- **Poser une question** : question précise sur le document
+
+### 4. Définir la question principale
+
+Saisissez la question de recherche centrale dans le panneau de flux de travail.
+
+### 5. Choisir le type de flux de travail ou de rapport
+
+- **Analyse** : analyse complète avec recommandations
+- **Résumé** : résumé court
+- **Rapport de recherche** : structure académique
+- **Comparaison** : comparaison systématique de documents
+
+### 6. Générer le rapport
+
+Cliquez sur « Créer le rapport » ; le résultat apparaît dans le panneau de droite.
+
+### 7. Exporter
+
+Sélectionnez les formats de sortie et cliquez sur « Exporter ».
+
+### 8. Exporter le prompt
+
+Utilisez **« Exporter le prompt »** pour enregistrer le contexte de travail actuel dans un fichier Markdown à utiliser dans Claude Code ou d’autres flux de travail LLM.
+
+## Compagnon Web/PWA
+
+Le dossier `web_companion/` contient une application compagnon en lecture seule pour Android, iOS et les navigateurs. Elle importe localement dans le navigateur les fichiers `notespacellm-workspace-v1.json` exportés, affiche le rapport, les métadonnées des documents et les extraits sélectionnés, et peut exporter des notes de relecture en Markdown. L’espace de travail reste dans la session actuelle du navigateur et doit être importé de nouveau après sa réouverture ; seules les notes de relecture et la langue de l’interface sont enregistrées localement.
+
+Test du navigateur en local :
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Tests rapides du Companion :
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+Le plan de test PWA pour Android/iOS est décrit dans `web_companion/PWA_TESTPLAN.md`.
+
+## Stratégie de plateforme
+
+L’application de bureau est l’application complète pour les documents locaux, l’index RAG et les connexions aux fournisseurs. Le compagnon Web/PWA pour Android, iOS et navigateur permet de consulter un export `notespacellm-workspace-v1.json` créé par l’utilisateur. Pour les données sensibles, configurez des points de terminaison locaux pour les modèles et les embeddings, puis relisez les exports avant de les partager. Le contrat d’échange est décrit dans `EXPORTFORMAT.md`.
+
+### Tests rapides du code source sur macOS et Linux
+
+D’autres tests rapides du code source s’exécutent dans GitHub Actions :
+- `tests/platform_smoke.py` — démarrage de PySide6 sans affichage, import de documents, export de rapport et export JSON de l’espace de travail
+- `tests/linux_platform_smoke.py` — enveloppe spécifique à Linux exécutée sur `ubuntu-latest`
+
+## Structure du projet
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## Formats de fichiers pris en charge
+
+| Format | Lecture | Écriture |
+|--------|---------|----------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ Le format .doc nécessite antiword ou LibreOffice.
+
+## Confidentialité
+
+NoteSpaceLLM stocke localement les données des projets, les index de documents, les profils, les paramètres de flux de travail et les exports dans `data/`, `profiles/`, `workflows/`, `output/` et `chroma_db/`. Ces dossiers sont volontairement exclus de Git.
+
+Lorsque des fournisseurs LLM externes (OpenAI, Anthropic, Claude Code, Ollama distant) sont activés, les prompts et les extraits de documents sélectionnés peuvent leur être transmis. Un point de terminaison Ollama distant reçoit également le texte des documents utilisé pour les embeddings. Pour les documents sensibles, utilisez des points de terminaison locaux pour les modèles et les embeddings et relisez les rapports exportés, l’historique de discussion et les extraits sélectionnés avant de les partager ; les exports d’espace de travail ne sont pas anonymisés.
+
+## Conseils
+
+1. **Grands ensembles de documents** : sélectionnez d’abord uniquement les documents pertinents pour accélérer le traitement
+2. **Sous-requêtes** : utilisez des sous-requêtes ciblées pour obtenir une analyse de meilleure qualité
+3. **Ollama** : un point de terminaison Ollama local permet un traitement hors ligne.
+4. **Ordre du flux de travail** : les étapes peuvent être réorganisées dans le panneau de flux de travail
+
+## Licence
+
+AGPL-3.0-only — voir [LICENSE](LICENSE)
+
+Le projet déclare utiliser PySide6 et PyMuPDF. Les références aux licences amont sélectionnées figurent dans [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md) ; la licence propre au projet est AGPL-3.0-only.
+
+## Projet associé
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) est une base de connaissances locale distincte, avec une recherche SQLite/FTS5 et des résumés LLM facultatifs ; elle n’est pas intégrée à NoteSpaceLLM.
+
+## Avis de licence et de maintenance
+
+Consultez [LICENSE](LICENSE) pour les conditions de licence du projet. Les mainteneurs ne garantissent ni maintenance continue ni disponibilité.
diff --git a/README_hi.md b/README_hi.md
new file mode 100644
index 0000000..211f77e
--- /dev/null
+++ b/README_hi.md
@@ -0,0 +1,329 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **शोध और रिपोर्ट निर्माण के लिए स्थानीय-प्रथम दस्तावेज़ नोटबुक, जिसमें कॉन्फ़िगर करने योग्य स्थानीय या दूरस्थ भाषा मॉडल हैं।**
+
+> [!NOTE]
+> **AI एजेंट्स और स्वचालित वर्कफ़्लो के लिए:**
+> NoteSpaceLLM [`llms.txt`](llms.txt) में मशीन-पठनीय आर्किटेक्चर और फ़ीचर अवलोकन प्रदान करता है।
+
+NoteSpaceLLM एक स्थानीय-प्रथम PySide6 डेस्कटॉप ऐप है जो PDF,
+Word फ़ाइलों, Markdown, मेल एक्सपोर्ट, नोट्स और शोध फ़ोल्डरों के साथ काम करने के लिए है। यह डिफ़ॉल्ट रूप से
+प्रोजेक्ट डेटा को स्थानीय रखता है, स्थानीय या दूरस्थ Ollama, OpenAI,
+Anthropic और Claude Code वर्कफ़्लो का समर्थन करता है, और विश्लेषण परिणामों को Markdown,
+PDF, DOCX, HTML और TXT में एक्सपोर्ट करता है। एक हल्का Web/PWA Companion अब उपलब्ध है, जिससे
+Android, iOS और ब्राउज़र डिवाइसों पर एक्सपोर्ट किए गए वर्कस्पेस की समीक्षा की जा सकती है, बिना दस्तावेज़ों को सर्वर पर अपलोड किए।
+
+**सर्वोत्तम-मेल खोजें:** NotebookLM विकल्प, स्थानीय RAG दस्तावेज़ विश्लेषण,
+स्थानीय दस्तावेज़ चैट, PySide6 शोध उपकरण, स्थानीय-मॉडल रिपोर्ट जनरेटर,
+स्थानीय-प्रथम दस्तावेज़ वर्कफ़्लो।
+
+## त्वरित प्रारंभ
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+Windows पर आप `start.bat` के माध्यम से भी लॉन्च कर सकते हैं। `build_exe.bat` के साथ एक स्थानीय Windows लॉन्चर बनाया जा सकता है।
+
+आवश्यकताएँ इंस्टॉल करने और स्टार्टर वर्कफ़्लो/प्रोफ़ाइल फ़ोल्डर बनाने वाले निर्देशित स्थानीय बूटस्ट्रैप के लिए, `python setup_environment.py` को स्पष्ट रूप से चलाएँ। पैकेज मेटाडेटा और बिल्ड अलग setuptools एंट्री पॉइंट का उपयोग करते हैं और उसे नहीं चलाते।
+
+टेस्ट चलाने और कंपाइल स्मोक चेक के लिए:
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## सिस्टम आर्किटेक्चर
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## फ़ीचर
+
+- **दस्तावेज़ प्रबंधन**: ड्रैग और ड्रॉप के माध्यम से फ़ाइलें और डायरेक्टरी जोड़ें
+- **स्वचालित निष्कर्षण और इंडेक्सिंग**: नए दस्तावेज़ तुरंत संसाधित होते हैं और RAG के लिए तैयार किए जाते हैं।
+- **चयनात्मक विश्लेषण**: रिपोर्ट में शामिल या बाहर रखने के लिए दस्तावेज़ों को चेक/अनचेक करें
+- **उप-क्वेरी**: लक्षित दस्तावेज़-विशिष्ट शोध के लिए किसी दस्तावेज़ पर राइट-क्लिक करें
+- **वर्कफ़्लो विज़ुअलाइज़ेशन**: रिपोर्ट पाइपलाइन का ग्राफ़िकल अवलोकन
+- **चैट इंटरफ़ेस**: किसी भी LLM के साथ लोड किए गए दस्तावेज़ों पर इंटरैक्टिव चैट
+- **Claude Code एकीकरण**: संरचित API प्रतिक्रियाएँ या इंटरैक्टिव Claude Code सत्र में हैंड-ऑफ़
+- **मल्टी-फ़ॉर्मेट एक्सपोर्ट**: MD, PDF, DOCX, HTML, TXT में आउटपुट
+- **प्रॉम्प्ट एक्सपोर्ट**: बाहरी LLM वर्कफ़्लो के लिए रिपोर्ट संदर्भ को Markdown प्रॉम्प्ट के रूप में सहेजें
+- **Ollama कनेक्शन प्रोफ़ाइल**: ऐप-व्यापी प्रदाता सेटिंग्स को प्रोजेक्ट्स में पुनः उपयोग किया जा सकता है
+- **भाषा समर्थन**: डेस्कटॉप और Web/PWA Companion इंटरफ़ेस में अंग्रेज़ी, जर्मन, स्पेनिश, सरलीकृत चीनी, जापानी, रूसी, फ़्रेंच, हिंदी, अरबी, बांग्ला और पुर्तगाली के लिए सहेजे जाने वाला भाषा चयन उपलब्ध है। अरबी में दाएँ-से-बाएँ लेआउट का उपयोग होता है। प्रोजेक्ट का भाषा फ़ील्ड कार्यक्षेत्र निर्यात में मेटाडेटा के रूप में शामिल होता है; यह इंटरफ़ेस की भाषा नहीं बदलता और जनरेट की गई रिपोर्टों की भाषा तय नहीं करता।
+- **Umlaut-सुरक्षित UI**: जर्मन UI स्ट्रिंग्स मूल umlauts का उपयोग करती हैं; अनुवाद स्कैनर अंग्रेज़ी के गलत सकारात्मक परिणामों से बचता है
+- **प्रोफ़ाइल**: पुन: उपयोग योग्य आउटपुट-फ़ॉर्मेट संयोजन
+
+## इंस्टॉलेशन
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+निर्भरताएँ जाँचें:
+
+```bash
+python main.py --check
+```
+
+## LLM कॉन्फ़िगरेशन
+
+### Ollama (स्थानीय — अनुशंसित)
+
+1. [Ollama इंस्टॉल करें](https://ollama.ai)
+2. एक मॉडल डाउनलोड करें: `ollama pull llama3`
+3. ऐप में: Menu > LLM > Use Ollama
+
+### Ollama (दूरस्थ सर्वर)
+
+NoteSpaceLLM एक दूरस्थ Ollama सर्वर से कनेक्ट हो सकता है, उदाहरण के लिए [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack) के माध्यम से:
+
+1. ऐप में: Menu > LLM > Settings
+2. **Ollama URL:** `http://your-server:11435` (या आपका पोर्ट)
+3. **API Key:** यदि सर्वर किसी auth proxy के पीछे है (अनुशंसित)
+
+> **सुझाव:** Ollama में अंतर्निर्मित प्रमाणीकरण नहीं है। दूरस्थ पहुँच के लिए API key या Basic Auth के साथ रिवर्स प्रॉक्सी (जैसे Nginx) का उपयोग करें।
+
+Ollama endpoint और मॉडल ऐप-व्यापी सेटिंग्स या चयनित कनेक्शन प्रोफ़ाइल में
+कॉन्फ़िगर होते हैं, प्रति प्रोजेक्ट नहीं। ऐप में सहेजे जाने पर API keys ऑपरेटिंग सिस्टम के
+credential store के माध्यम से संग्रहीत होती हैं। वैकल्पिक
+`NOTESPACELLM_OLLAMA_API_KEY` environment variable सहेजी गई key को ओवरराइड करता है और
+ऐप द्वारा कभी लिखा नहीं जाता। Legacy plaintext keys तब तक नहीं भेजी जातीं जब तक आप
+सेटिंग्स में उन्हें स्पष्ट रूप से माइग्रेट नहीं करते।
+
+जब कॉन्फ़िगर किया गया Ollama सर्वर रिमोट होता है, तो वह एम्बेडिंग के लिए उपयोग किया जाने वाला दस्तावेज़ टेक्स्ट और जेनरेशन के लिए भेजे गए prompts/context प्राप्त करता है। API key को Bearer header के रूप में कॉन्फ़िगर किए गए URL पर भेजा जाता है। सादा HTTP न तो सामग्री को और न ही क्रेडेंशियल्स को एन्क्रिप्ट करता है; HTTPS का व्यवहार endpoint और उसके certificate पर निर्भर करता है।
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+इसके लिए स्थानीय CLI आवश्यक है:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+ऐप में दो मोड उपलब्ध हैं:
+- **API मोड**: संरचित प्रतिक्रिया सीधे NoteSpaceLLM में लौटाई जाती है
+- **Chat मोड**: prompt को एक इंटरैक्टिव Claude Code console को सौंप देता है
+
+## उपयोग
+
+### 1. दस्तावेज़ जोड़ें
+
+- **Drag & Drop**: फ़ाइलें या फ़ोल्डर बाएँ पैनल में छोड़ें
+- **बटन**: "Add files" या "Add folder"
+
+### 2. दस्तावेज़ चुनें
+
+- Checkbox: विश्लेषण के लिए दस्तावेज़ों को शामिल/बाहर करें
+- बटन: बल्क चयन के लिए "All" / "None"
+
+### 3. Sub-queries (वैकल्पिक)
+
+किसी दस्तावेज़ पर राइट-क्लिक करें:
+- **Create summary**: स्वचालित सारांश
+- **Extract information**: विशिष्ट डेटा खोजें
+- **Analyse**: लक्षित विश्लेषण
+- **Ask question**: दस्तावेज़ के बारे में विशिष्ट प्रश्न
+
+### 4. मुख्य प्रश्न परिभाषित करें
+
+workflow पैनल में केंद्रीय शोध प्रश्न दर्ज करें।
+
+### 5. Workflow/रिपोर्ट प्रकार चुनें
+
+- **Analysis**: सिफारिशों के साथ व्यापक विश्लेषण
+- **Summary**: संक्षिप्त सारांश
+- **Research report**: शैक्षणिक संरचना
+- **Comparison**: व्यवस्थित दस्तावेज़ तुलना
+
+### 6. रिपोर्ट बनाएँ
+
+"Create report" पर क्लिक करें — आउटपुट दाएँ पैनल में दिखाई देगा।
+
+### 7. एक्सपोर्ट
+
+आउटपुट फ़ॉर्मेट चुनें और "Export" पर क्लिक करें।
+
+### 8. Prompt एक्सपोर्ट
+
+वर्तमान कार्यशील context को Claude Code या अन्य LLM workflows में उपयोग के लिए Markdown फ़ाइल के रूप में सहेजने हेतु **"Export prompt"** का उपयोग करें।
+
+## Web/PWA Companion
+
+`web_companion/` के अंतर्गत Android, iOS और browser के लिए एक केवल-पढ़ने योग्य Companion strand है। यह browser में स्थानीय रूप से निर्यात किए गए `notespacellm-workspace-v1.json` फ़ाइलों को आयात करता है, रिपोर्ट, दस्तावेज़ मेटाडेटा और चयनित अंश दिखाता है, और समीक्षा नोट्स को Markdown के रूप में एक्सपोर्ट कर सकता है। workspace वर्तमान browser session में रहता है और ब्राउज़र दोबारा खोलने के बाद फिर से आयात करना होगा; केवल समीक्षा नोट्स और UI locale स्थानीय रूप से सहेजे जाते हैं।
+
+स्थानीय browser परीक्षण:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Companion smokes:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+Android/iOS PWA परीक्षण योजना `web_companion/PWA_TESTPLAN.md` में दस्तावेज़ित है।
+
+## प्लेटफ़ॉर्म रणनीति
+
+डेस्कटॉप ऐप स्थानीय दस्तावेज़ों, RAG index और provider कनेक्शनों के लिए पूर्ण एप्लिकेशन है। Android, iOS और browser Web/PWA Companion उपयोगकर्ता द्वारा बनाए गए `notespacellm-workspace-v1.json` एक्सपोर्ट की समीक्षा के लिए उपलब्ध है। संवेदनशील सामग्री के लिए, स्थानीय model और embedding endpoints कॉन्फ़िगर करें और साझा करने से पहले एक्सपोर्ट की समीक्षा करें। विनिमय अनुबंध `EXPORTFORMAT.md` में दस्तावेज़ित है।
+
+### macOS और Linux Source Smokes
+
+GitHub Actions में अतिरिक्त source smokes चलते हैं:
+- `tests/platform_smoke.py` — offscreen PySide6 start, दस्तावेज़ आयात, रिपोर्ट एक्सपोर्ट, workspace JSON एक्सपोर्ट
+- `tests/linux_platform_smoke.py` — `ubuntu-latest` के ऊपर Linux-विशिष्ट wrapper
+
+## प्रोजेक्ट संरचना
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## समर्थित फ़ाइल फ़ॉर्मेट
+
+| फ़ॉर्मेट | पढ़ना | लिखना |
+|--------|------|-------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ .doc के लिए antiword या LibreOffice आवश्यक है
+
+## गोपनीयता
+
+NoteSpaceLLM प्रोजेक्ट डेटा, दस्तावेज़ indexes, profiles, workflow settings और exports को `data/`, `profiles/`, `workflows/`, `output/` और `chroma_db/` में स्थानीय रूप से संग्रहीत करता है। ये फ़ोल्डर जानबूझकर Git से बाहर रखे गए हैं।
+
+जब बाहरी LLM providers (OpenAI, Anthropic, Claude Code, रिमोट Ollama) सक्षम होते हैं, तो prompts और चयनित दस्तावेज़ अंश उन सेवाओं को भेजे जा सकते हैं। रिमोट Ollama endpoint को एम्बेडिंग के लिए उपयोग किया जाने वाला दस्तावेज़ टेक्स्ट भी प्राप्त होता है। संवेदनशील दस्तावेज़ों के लिए स्थानीय model और embedding endpoints का उपयोग करें और साझा करने से पहले निर्यात की गई रिपोर्ट, chat history और चयनित अंशों की समीक्षा करें; workspace exports अनामित नहीं होते।
+
+## सुझाव
+
+1. **बड़े दस्तावेज़ सेट**: तेज़ परिणामों के लिए पहले केवल प्रासंगिक दस्तावेज़ चुनें
+2. **Sub-queries**: उच्च-गुणवत्ता विश्लेषण के लिए लक्षित sub-queries का उपयोग करें
+3. **Ollama**: स्थानीय Ollama endpoint ऑफ़लाइन प्रोसेसिंग का समर्थन करता है।
+4. **Workflow क्रम**: workflow पैनल में चरणों का क्रम बदला जा सकता है
+
+## लाइसेंस
+
+AGPL-3.0-only — देखें [LICENSE](LICENSE)
+
+यह प्रोजेक्ट PySide6 और PyMuPDF घोषित करता है। चयनित upstream लाइसेंस संदर्भों के लिए [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md) देखें; प्रोजेक्ट का अपना लाइसेंस AGPL-3.0-only है।
+
+## संबंधित प्रोजेक्ट
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) SQLite/FTS5 खोज और वैकल्पिक LLM सारांश वाला एक अलग स्थानीय ज्ञान डेटाबेस है; यह NoteSpaceLLM में एकीकृत नहीं है।
+
+## लाइसेंस और रखरखाव सूचना
+
+प्रोजेक्ट की लाइसेंस शर्तों के लिए [LICENSE](LICENSE) देखें। maintainers निरंतर रखरखाव या उपलब्धता का वादा नहीं करते।
diff --git a/README_ja.md b/README_ja.md
new file mode 100644
index 0000000..8755ba7
--- /dev/null
+++ b/README_ja.md
@@ -0,0 +1,317 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **研究およびレポート生成のための、ローカル優先のドキュメントノートブックです。設定可能なローカルまたはリモートの言語モデルに対応しています。**
+
+> [!NOTE]
+> **AIエージェントおよび自動化ワークフロー向け:**
+> NoteSpaceLLMは、機械可読なアーキテクチャと機能の概要を [`llms.txt`](llms.txt) で提供しています。
+
+NoteSpaceLLMは、PDF、Word文書、Markdown、メールのエクスポート、メモ、研究フォルダを扱うためのローカル優先のPySide6デスクトップアプリです。デフォルトでプロジェクトデータをローカルに保持し、ローカルまたはリモートのOllama、OpenAI、Anthropic、Claude Codeのワークフローに対応し、分析結果をMarkdown、PDF、DOCX、HTML、TXTにエクスポートします。軽量なWeb/PWAコンパニオンも利用可能になり、文書をサーバーにアップロードすることなく、Android、iOS、ブラウザのデバイス上でエクスポートされたワークスペースを確認できます。
+
+**検索キーワードの例:** NotebookLM の代替、ローカル RAG 文書分析、ローカル文書チャット、PySide6 の研究ツール、ローカルモデルを使ったレポート生成、ローカル優先の文書ワークフロー
+
+## クイックスタート
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+Windowsでは、`start.bat` から起動することもできます。ローカルのWindowsランチャーは `build_exe.bat` でビルドできます。
+
+依存関係をインストールし、スターター用のワークフロー/プロフィールフォルダを作成する、ガイド付きのローカルブートストラップを行うには、`python setup_environment.py` を明示的に実行してください。パッケージのメタデータとビルドは別の setuptools エントリポイントを使用し、これを実行しません。
+
+テストとコンパイルのスモークチェックは次のコマンドで実行します:
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## システムアーキテクチャ
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## 機能
+
+- **ドキュメント管理**: ドラッグ&ドロップでファイルやディレクトリを追加
+- **自動抽出とインデックス作成**: 新しい文書は直ちに処理され、RAG用に準備されます。
+- **選択的な分析**: 文書にチェックを入れる/外すことで、レポートに含めるか除外するかを指定
+- **サブクエリ**: 文書を右クリックして、文書ごとの対象を絞った調査を実行
+- **ワークフローの可視化**: レポートパイプラインのグラフィカルな概要
+- **チャットインターフェース**: 任意のLLMを使い、読み込んだ文書に対して対話形式で質問
+- **Claude Code連携**: 構造化されたAPIレスポンス、または対話型のClaude Codeセッションへの引き継ぎ
+- **マルチフォーマットエクスポート**: MD、PDF、DOCX、HTML、TXTへの出力
+- **プロンプトのエクスポート**: レポートのコンテキストを、外部のLLMワークフロー用にMarkdownプロンプトとして保存
+- **Ollama接続プロフィール**: アプリ全体のプロバイダー設定を、複数のプロジェクトで再利用可能
+- **言語サポート**: デスクトップと Web/PWA Companion の各インターフェースには、英語、ドイツ語、スペイン語、簡体字中国語、日本語、ロシア語、フランス語、ヒンディー語、アラビア語、ベンガル語、ポルトガル語を選べる永続保存型の言語セレクターがあります。アラビア語では右から左に読むレイアウトを使用します。プロジェクトの言語フィールドはワークスペース書き出しのメタデータです。インターフェースの言語を切り替えたり、生成レポートの言語を決めたりするものではありません。
+- **ウムラウト対応のUI**: ドイツ語のUI文字列では本来のウムラウトを使用し、翻訳スキャナーは英語の誤検出を回避します
+- **プロフィール**: 再利用可能な出力フォーマットの組み合わせ
+
+## インストール
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+依存関係を確認します:
+
+```bash
+python main.py --check
+```
+
+## LLM の設定
+
+### Ollama(ローカル — 推奨)
+
+1. [Ollama をインストール](https://ollama.ai)
+2. モデルをダウンロード: `ollama pull llama3`
+3. アプリで: メニュー > LLM > Use Ollama
+
+### Ollama(リモートサーバー)
+
+NoteSpaceLLM は、たとえば [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack) を介して、リモートの Ollama サーバーに接続できます:
+
+1. アプリで: メニュー > LLM > Settings
+2. **Ollama URL:** `http://your-server:11435`(またはお使いのポート)
+3. **API Key:** サーバーが認証プロキシの背後にある場合(推奨)
+
+> **ヒント:** Ollama には組み込みの認証機能がありません。リモートアクセスには、API キーまたは Basic 認証を備えたリバースプロキシ(例: Nginx)を使用してください。
+
+Ollama のエンドポイントとモデルは、プロジェクトごとではなく、アプリ全体の設定または選択された接続プロフィールで構成されます。アプリで保存された API キーは、オペレーティングシステムの資格情報ストアを通じて保存されます。任意の `NOTESPACELLM_OLLAMA_API_KEY` 環境変数は、保存されたキーより優先され、アプリによって書き込まれることはありません。レガシーの平文キーは、設定で明示的に移行するまで送信されません。
+
+構成されたOllamaサーバーがリモートの場合、埋め込み用のドキュメント本文と、生成用に送信されるプロンプトやコンテキストを受信します。APIキーは、Bearerヘッダーとして設定されたURLに送信されます。プレーンHTTPではコンテンツも認証情報も暗号化されず、HTTPSの動作はエンドポイントとその証明書によって異なります。
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+ローカルのCLIが必要です:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+アプリでは2つのモードが利用できます:
+- **APIモード**: 構造化された応答がNoteSpaceLLMに直接返されます
+- **チャットモード**: プロンプトを対話型のClaude Codeコンソールに引き渡します
+
+## 使い方
+
+### 1. ドキュメントの追加
+
+- **ドラッグ&ドロップ**: 左パネルにファイルまたはフォルダーをドロップします
+- **ボタン**: 「Add files」または「Add folder」
+
+### 2. ドキュメントの選択
+
+- チェックボックス: 分析対象のドキュメントを含める/除外する
+- ボタン: 一括選択のための「All」/「None」
+
+### 3. サブクエリ(任意)
+
+ドキュメントを右クリックします:
+- **Create summary**: 自動要約
+- **Extract information**: 特定のデータを検索
+- **Analyse**: 対象を絞った分析
+- **Ask question**: ドキュメントに関する特定の質問
+
+### 4. 主要な質問の定義
+
+ワークフローパネルに中心となる研究上の質問を入力します。
+
+### 5. ワークフロー/レポートタイプの選択
+
+- **Analysis**: 提言を含む包括的な分析
+- **Summary**: 短い要約
+- **Research report**: 学術的な構成
+- **Comparison**: 体系的なドキュメント比較
+
+### 6. レポートの生成
+
+「Create report」をクリックすると、出力が右パネルに表示されます。
+
+### 7. エクスポート
+
+出力形式を選択し、「Export」をクリックします。
+
+### 8. プロンプトのエクスポート
+
+**「Export prompt」**を使用すると、現在の作業コンテキストをMarkdownファイルとして保存でき、Claude Codeやその他のLLMワークフローで利用できます。
+
+## Web/PWAコンパニオン
+
+`web_companion/` の下には、Android、iOS、ブラウザー向けの読み取り専用のCompanionストランドがあります。エクスポートされた`notespacellm-workspace-v1.json`ファイルをブラウザー内でローカルにインポートし、レポート、ドキュメントのメタデータ、選択された抜粋を表示し、レビューメモをMarkdownとしてエクスポートできます。ワークスペースは現在のブラウザーセッション内に保持され、再度開いた後は再インポートが必要です。ローカルに保存されるのはレビューメモとUIのロケールのみです。
+
+ローカルブラウザーテスト:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Companionのスモークテスト:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+Android/iOS PWAのテスト計画は`web_companion/PWA_TESTPLAN.md`に記載されています。
+
+## プラットフォーム戦略
+
+デスクトップアプリは、ローカルのドキュメント、RAGインデックス、プロバイダー接続のためのフルアプリケーションです。Android、iOS、ブラウザーのWeb/PWAコンパニオンは、ユーザーが作成した`notespacellm-workspace-v1.json`エクスポートを確認するために利用できます。機密性の高い資料については、ローカルのモデルおよび埋め込みエンドポイントを設定し、共有前にエクスポートを確認してください。データのやり取りの仕様は`EXPORTFORMAT.md`に記載されています。
+
+### macOSおよびLinuxのソーススモークテスト
+
+GitHub Actionsで追加のソーススモークテストが実行されます:
+- `tests/platform_smoke.py` — オフスクリーンのPySide6起動、ドキュメントのインポート、レポートのエクスポート、ワークスペースJSONのエクスポート
+- `tests/linux_platform_smoke.py` — `ubuntu-latest`向けのLinux固有のラッパー
+
+## プロジェクト構造
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## 対応ファイル形式
+
+| 形式 | 読み取り | 書き出し |
+|--------|----------|----------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ .docにはantiwordまたはLibreOfficeが必要です
+
+## プライバシー
+
+NoteSpaceLLMは、プロジェクトデータ、ドキュメントのインデックス、プロファイル、ワークフロー設定、エクスポートを、`data/`、`profiles/`、`workflows/`、`output/`、`chroma_db/`にローカルで保存します。これらのフォルダーはGitの管理対象から意図的に除外されています。
+
+外部のLLMプロバイダー(OpenAI、Anthropic、Claude Code、リモートのOllama)が有効な場合、プロンプトと選択されたドキュメントの抜粋がそれらのサービスに送信される可能性があります。リモートのOllamaエンドポイントは、埋め込みに使用されるドキュメント本文も受信します。機密性の高いドキュメントについては、ローカルのモデルおよび埋め込みエンドポイントを使用し、共有前にエクスポートされたレポート、チャット履歴、選択された抜粋を確認してください。ワークスペースのエクスポートは匿名化されていません。
+
+## ヒント
+
+1. **大量のドキュメントセット**: より速い結果を得るために、まず関連するドキュメントのみを選択してください
+2. **サブクエリ**: より高品質な分析のために、対象を絞ったサブクエリを使用してください
+3. **Ollama**: ローカルのOllamaエンドポイントはオフライン処理に対応しています。
+4. **ワークフローの順序**: ワークフローパネルでステップの順序を変更できます
+
+## ライセンス
+
+AGPL-3.0-only — [LICENSE](LICENSE) を参照
+
+本プロジェクトはPySide6およびPyMuPDFを宣言しています。選択された上流のライセンス参照については[THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md)を参照してください。本プロジェクト自体のライセンスはAGPL-3.0-onlyです。
+
+## 関連プロジェクト
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest)は、SQLite/FTS5検索と任意のLLM要約機能を備えた、別個のローカル知識データベースです。NoteSpaceLLMとは統合されていません。
+
+## ライセンスおよび保守に関する通知
+
+プロジェクトのライセンス条件については[LICENSE](LICENSE)を参照してください。メンテナーは、継続的な保守や提供を約束するものではありません。
diff --git a/README_pt.md b/README_pt.md
new file mode 100644
index 0000000..4ad3e13
--- /dev/null
+++ b/README_pt.md
@@ -0,0 +1,332 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **Caderno de documentos local-first para pesquisa e geração de relatórios com modelos de linguagem locais ou remotos configuráveis.**
+
+> [!NOTE]
+> **Para Agentes de IA e Fluxos de Trabalho Automatizados:**
+> O NoteSpaceLLM fornece uma visão geral da arquitetura e dos recursos legível por máquina em [`llms.txt`](llms.txt).
+
+O NoteSpaceLLM é um aplicativo de desktop local-first em PySide6 para trabalhar com PDFs,
+arquivos Word, Markdown, exportações de e-mail, notas e pastas de pesquisa. Ele mantém
+os dados do projeto localmente por padrão, oferece suporte a fluxos de trabalho com Ollama local ou remoto, OpenAI,
+Anthropic e Claude Code, e exporta os resultados de análise para Markdown,
+PDF, DOCX, HTML e TXT. Um Companion Web/PWA leve já está disponível para
+revisar workspaces exportados em dispositivos Android, iOS e navegadores, sem
+enviar documentos a um servidor.
+
+**Buscas mais relevantes:** alternativa ao NotebookLM, análise de documentos com RAG local,
+chat com documentos locais, ferramenta de pesquisa em PySide6, gerador de relatórios com modelos locais,
+fluxo de trabalho de documentos local-first.
+
+## Início Rápido
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+No Windows, você também pode iniciar via `start.bat`. Um iniciador local para Windows pode ser criado com `build_exe.bat`.
+
+Para uma inicialização local guiada, que instala os requisitos e cria as pastas iniciais de fluxos de trabalho/perfis, execute explicitamente `python setup_environment.py`. Os metadados do pacote e as compilações usam o ponto de entrada separado do setuptools e não o executam.
+
+Execute os testes e as verificações de compilação rápidas com:
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## Arquitetura do Sistema
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## Recursos
+
+- **Gerenciamento de documentos**: Adicione arquivos e diretórios por arrastar e soltar
+- **Extração e indexação automáticas**: Novos documentos são processados imediatamente e preparados para RAG.
+- **Análise seletiva**: Marque/desmarque documentos para incluí-los ou excluí-los dos relatórios
+- **Subconsultas**: Clique com o botão direito em um documento para pesquisas direcionadas a esse documento
+- **Visualização do fluxo de trabalho**: Visão gráfica do pipeline de relatórios
+- **Interface de chat**: Chat interativo sobre os documentos carregados com qualquer LLM
+- **Integração com Claude Code**: Respostas estruturadas da API ou transferência para uma sessão interativa do Claude Code
+- **Exportação em múltiplos formatos**: Saída para MD, PDF, DOCX, HTML, TXT
+- **Exportação de prompt**: Salve o contexto do relatório como um prompt em Markdown para fluxos de trabalho com LLMs externos
+- **Perfis de conexão com Ollama**: As configurações de provedor válidas para todo o aplicativo podem ser reutilizadas entre projetos
+- **Suporte a idiomas**: As interfaces desktop e do Web/PWA Companion oferecem seletores persistentes de idioma para inglês, alemão, espanhol, chinês simplificado, japonês, russo, francês, hindi, árabe, bengali e português. O árabe usa uma disposição da direita para a esquerda. O campo de idioma do projeto é metadado da exportação do espaço de trabalho; ele não altera o idioma da interface nem determina o idioma dos relatórios gerados.
+- **Interface segura para umlauts**: Textos da interface em alemão usam umlauts nativos; o verificador de tradução evita falsos positivos em inglês
+- **Perfis**: Combinações reutilizáveis de formatos de saída
+
+## Instalação
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+Verifique as dependências:
+
+```bash
+python main.py --check
+```
+
+## Configuração de LLM
+
+### Ollama (Local — Recomendado)
+
+1. [Instale o Ollama](https://ollama.ai)
+2. Baixe um modelo: `ollama pull llama3`
+3. No aplicativo: Menu > LLM > Usar Ollama
+
+### Ollama (Servidor Remoto)
+
+O NoteSpaceLLM pode se conectar a um servidor Ollama remoto, por exemplo via [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack):
+
+1. No aplicativo: Menu > LLM > Configurações
+2. **URL do Ollama:** `http://your-server:11435` (ou a sua porta)
+3. **Chave de API:** Se o servidor estiver atrás de um proxy de autenticação (recomendado)
+
+> **Dica:** O Ollama não possui autenticação integrada. Para acesso remoto, use um proxy reverso (por exemplo, Nginx) com chave de API ou Basic Auth.
+
+O endpoint e o modelo do Ollama são configurados nas configurações válidas para todo o aplicativo ou no
+perfil de conexão selecionado, não por projeto. As chaves de API são armazenadas pelo
+cofre de credenciais do sistema operacional quando salvas no aplicativo. A variável de ambiente
+`NOTESPACELLM_OLLAMA_API_KEY` opcional substitui a chave salva e
+nunca é gravada pelo aplicativo. Chaves em texto simples legadas não são enviadas até que você
+migre-as explicitamente nas configurações.
+
+Quando o servidor Ollama configurado é remoto, ele recebe o texto dos documentos usado para embeddings e os prompts/contexto enviados para geração. A chave de API é enviada para a URL configurada como cabeçalho Bearer. HTTP simples não criptografa nem o conteúdo nem as credenciais; o comportamento do HTTPS depende do endpoint e do seu certificado.
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+Requer a CLI local:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+Há dois modos disponíveis no aplicativo:
+- **Modo API**: a resposta estruturada é retornada diretamente ao NoteSpaceLLM
+- **Modo chat**: entrega o prompt a um console interativo do Claude Code
+
+## Uso
+
+### 1. Adicionar documentos
+
+- **Arrastar e soltar**: solte arquivos ou pastas no painel esquerdo
+- **Botão**: "Add files" ou "Add folder"
+
+### 2. Selecionar documentos
+
+- Caixa de seleção: inclui/exclui documentos da análise
+- Botões: "All" / "None" para seleção em massa
+
+### 3. Subconsultas (opcional)
+
+Clique com o botão direito em um documento:
+- **Create summary**: resumo automático
+- **Extract information**: localizar dados específicos
+- **Analyse**: análise direcionada
+- **Ask question**: pergunta específica sobre o documento
+
+### 4. Definir a pergunta principal
+
+Insira a pergunta central da pesquisa no painel de fluxo de trabalho.
+
+### 5. Escolher o tipo de fluxo de trabalho/relatório
+
+- **Analysis**: análise abrangente com recomendações
+- **Summary**: resumo curto
+- **Research report**: estrutura acadêmica
+- **Comparison**: comparação sistemática de documentos
+
+### 6. Gerar relatório
+
+Clique em "Create report" — a saída aparece no painel direito.
+
+### 7. Exportar
+
+Selecione os formatos de saída e clique em "Export".
+
+### 8. Exportação de prompt
+
+Use **"Export prompt"** para salvar o contexto de trabalho atual como arquivo Markdown para uso no Claude Code ou em outros fluxos de trabalho com LLM.
+
+## Companion Web/PWA
+
+Sob `web_companion/` há um Companion somente leitura para Android, iOS e navegador. Ele importa localmente, no navegador, arquivos `notespacellm-workspace-v1.json` exportados, exibe o relatório, os metadados dos documentos e os trechos selecionados, e pode exportar notas de revisão em Markdown. O workspace permanece na sessão atual do navegador e deve ser importado novamente após reabrir; somente as notas de revisão e o idioma da interface são persistidos localmente.
+
+Teste local no navegador:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Smokes do Companion:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+O plano de testes do PWA para Android/iOS está documentado em `web_companion/PWA_TESTPLAN.md`.
+
+## Estratégia de plataforma
+
+O aplicativo desktop é a aplicação completa para documentos locais, o índice RAG e as conexões com provedores. O Companion Web/PWA para Android, iOS e navegador está disponível para revisar uma exportação `notespacellm-workspace-v1.json` criada pelo usuário.
+Para material sensível, configure endpoints locais de modelo e de embeddings e revise as exportações antes de compartilhar. O contrato de troca está documentado em `EXPORTFORMAT.md`.
+
+### Smokes de código-fonte para macOS e Linux
+
+Smokes adicionais de código-fonte são executados no GitHub Actions:
+- `tests/platform_smoke.py` — início offscreen do PySide6, importação de documentos, exportação de relatório e exportação de workspace em JSON
+- `tests/linux_platform_smoke.py` — wrapper específico para Linux sobre `ubuntu-latest`
+
+## Estrutura do projeto
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## Formatos de arquivo suportados
+
+| Formato | Leitura | Escrita |
+|---------|---------|---------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ .doc requer antiword ou LibreOffice
+
+## Privacidade
+
+O NoteSpaceLLM armazena localmente dados de projetos, índices de documentos, perfis, configurações de fluxo de trabalho e exportações em `data/`, `profiles/`, `workflows/`, `output/` e
+`chroma_db/`. Essas pastas são intencionalmente excluídas do Git.
+
+Quando provedores externos de LLM (OpenAI, Anthropic, Claude Code, Ollama remoto) estão habilitados, prompts e trechos de documentos selecionados podem ser transmitidos a esses serviços. Um endpoint Ollama remoto também recebe o texto dos documentos usado para embeddings. Para documentos sensíveis, use endpoints locais de modelo e de embeddings e revise os relatórios exportados, o histórico de chat e os trechos selecionados antes de compartilhar; as exportações de workspace não são anonimizadas.
+
+## Dicas
+
+1. **Conjuntos grandes de documentos**: selecione primeiro apenas os documentos relevantes para resultados mais rápidos
+2. **Subconsultas**: use subconsultas direcionadas para análises de maior qualidade
+3. **Ollama**: um endpoint Ollama local permite processamento offline.
+4. **Ordem do fluxo de trabalho**: as etapas podem ser reordenadas no painel de fluxo de trabalho
+
+## Licença
+
+AGPL-3.0-only — veja [LICENSE](LICENSE)
+
+Este projeto declara PySide6 e PyMuPDF. Veja as referências de licença upstream selecionadas em [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md); a licença do próprio projeto é AGPL-3.0-only.
+
+## Projeto relacionado
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) é um banco de dados de conhecimento local separado, com busca SQLite/FTS5 e resumos opcionais por LLM; ele não é integrado ao NoteSpaceLLM.
+
+## Aviso de licença e manutenção
+
+Veja [LICENSE](LICENSE) para os termos de licença do projeto. Os mantenedores não prometem manutenção contínua nem disponibilidade.
diff --git a/README_ru.md b/README_ru.md
new file mode 100644
index 0000000..c080b32
--- /dev/null
+++ b/README_ru.md
@@ -0,0 +1,333 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **Локальный блокнот для документов с приоритетом локальной работы, предназначенный для исследований и генерации отчётов с настраиваемыми локальными или удалёнными языковыми моделями.**
+
+> [!NOTE]
+> **Для ИИ-агентов и автоматизированных рабочих процессов:**
+> NoteSpaceLLM предоставляет машиночитаемый обзор архитектуры и функций в [`llms.txt`](llms.txt).
+
+NoteSpaceLLM — это настольное приложение на PySide6 с приоритетом локальной работы для работы с PDF,
+файлами Word, Markdown, экспортами почты, заметками и папками с исследовательскими материалами. По умолчанию оно
+хранит данные проектов локально, поддерживает локальные или удалённые сценарии работы с Ollama, OpenAI,
+Anthropic и Claude Code и экспортирует результаты анализа в Markdown,
+PDF, DOCX, HTML и TXT. Облегчённый Web/PWA-компаньон теперь доступен для
+просмотра экспортированных рабочих пространств на Android, iOS и в браузерах без
+загрузки документов на сервер.
+
+**Наиболее подходящие поисковые запросы:** альтернатива NotebookLM, локальный анализ документов по схеме RAG,
+локальный чат с документами, исследовательский инструмент на PySide6, генератор отчётов на локальных моделях,
+рабочий процесс с документами с приоритетом локальной работы.
+
+## Быстрый старт
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+В Windows можно также запустить через `start.bat`. Локальный загрузчик для Windows можно собрать с помощью `build_exe.bat`.
+
+Для направляемой локальной начальной настройки, которая устанавливает требования и создаёт стартовые
+папки рабочих процессов и профилей, явно выполните `python setup_environment.py`. Метаданные пакета и сборки используют
+отдельную точку входа setuptools и её не запускают.
+
+Запуск тестов и проверок компиляции (smoke-тестов):
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## Архитектура системы
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## Возможности
+
+- **Управление документами**: добавление файлов и каталогов перетаскиванием (drag & drop)
+- **Автоматическое извлечение и индексирование**: новые документы обрабатываются сразу и подготавливаются для RAG.
+- **Выборочный анализ**: отметьте или снимите отметку с документов, чтобы включить их в отчёты или исключить из них
+- **Подзапросы**: щёлкните документ правой кнопкой мыши для целевого исследования конкретного документа
+- **Визуализация рабочего процесса**: графический обзор конвейера формирования отчёта
+- **Интерфейс чата**: интерактивный чат по загруженным документам с любой LLM
+- **Интеграция с Claude Code**: структурированные ответы API или передача интерактивному сеансу Claude Code
+- **Экспорт в несколько форматов**: вывод в MD, PDF, DOCX, HTML, TXT
+- **Экспорт промптов**: сохранение контекста отчёта как Markdown-промпта для внешних рабочих процессов с LLM
+- **Профили подключения к Ollama**: настройки провайдера для всего приложения можно повторно использовать в разных проектах
+- **Поддержка языков**: В настольном интерфейсе и Web/PWA Companion есть сохраняемый выбор из одиннадцати языков: английского, немецкого, испанского, упрощённого китайского, японского, русского, французского, хинди, арабского, бенгальского и португальского. Для арабского используется раскладка справа налево. Поле языка проекта включается в экспорт рабочего пространства как метаданные; оно не меняет язык интерфейса и не определяет язык создаваемых отчётов.
+- **Интерфейс, безопасный для умлаутов**: немецкие строки интерфейса используют собственные умлауты; сканер переводов исключает ложные срабатывания английского языка
+- **Профили**: повторно используемые комбинации форматов вывода
+
+## Установка
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+Проверьте зависимости:
+
+```bash
+python main.py --check
+```
+
+## Настройка LLM
+
+### Ollama (локально — рекомендуется)
+
+1. [Установите Ollama](https://ollama.ai)
+2. Загрузите модель: `ollama pull llama3`
+3. В приложении: Меню > LLM > Использовать Ollama
+
+### Ollama (удалённый сервер)
+
+NoteSpaceLLM может подключаться к удалённому серверу Ollama, например через [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack):
+
+1. В приложении: Меню > LLM > Настройки
+2. **URL Ollama:** `http://your-server:11435` (или ваш порт)
+3. **Ключ API:** если сервер находится за прокси-сервером аутентификации (рекомендуется)
+
+> **Совет:** Ollama не имеет встроенной аутентификации. Для удалённого доступа используйте обратный прокси (например, Nginx) с ключом API или Basic Auth.
+
+Конечная точка Ollama и модель настраиваются в общих настройках приложения или
+выбранном профиле подключения, а не для каждого проекта. Ключи API сохраняются через
+хранилище учётных данных операционной системы, если они сохранены в приложении. Необязательная
+переменная окружения `NOTESPACELLM_OLLAMA_API_KEY` переопределяет сохранённый ключ и
+никогда не записывается приложением. Устаревшие ключи в открытом виде не отправляются, пока вы
+явно не выполните их миграцию в настройках.
+
+Когда настроенный сервер Ollama является удалённым, он получает текст документов, используемый для эмбеддингов, а также промпты и контекст, отправляемые для генерации. API-ключ передаётся на настроенный URL в заголовке Bearer. Обычный HTTP не шифрует ни содержимое, ни учётные данные; поведение HTTPS зависит от конечной точки и её сертификата.
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+Требуется локальный CLI:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+В приложении доступны два режима:
+- **Режим API**: структурированный ответ возвращается напрямую в NoteSpaceLLM
+- **Режим чата**: промпт передаётся в интерактивную консоль Claude Code
+
+## Использование
+
+### 1. Добавление документов
+
+- **Перетаскивание**: перетащите файлы или папки на левую панель
+- **Кнопка**: «Add files» или «Add folder»
+
+### 2. Выбор документов
+
+- Флажок: включить/исключить документ из анализа
+- Кнопки: «All» / «None» для массового выбора
+
+### 3. Подзапросы (необязательно)
+
+Щёлкните документ правой кнопкой мыши:
+- **Create summary**: автоматическое резюме
+- **Extract information**: поиск конкретных данных
+- **Analyse**: целевой анализ
+- **Ask question**: конкретный вопрос о документе
+
+### 4. Определение основного вопроса
+
+Введите центральный исследовательский вопрос на панели рабочего процесса.
+
+### 5. Выбор типа рабочего процесса/отчёта
+
+- **Analysis**: всесторонний анализ с рекомендациями
+- **Summary**: краткое резюме
+- **Research report**: академическая структура
+- **Comparison**: систематическое сравнение документов
+
+### 6. Генерация отчёта
+
+Нажмите «Create report» — результат появится на правой панели.
+
+### 7. Экспорт
+
+Выберите форматы вывода и нажмите «Export».
+
+### 8. Экспорт промпта
+
+Используйте **«Export prompt»**, чтобы сохранить текущий рабочий контекст в файл Markdown для использования в Claude Code или других рабочих процессах LLM.
+
+## Web/PWA-компаньон
+
+Под `web_companion/` находится компаньон только для чтения для Android, iOS и браузера. Он локально импортирует экспортированные файлы `notespacellm-workspace-v1.json` в браузере, показывает отчёт, метаданные документов и выбранные фрагменты и может экспортировать заметки для проверки в Markdown. Рабочее пространство остаётся в текущей сессии браузера и должно быть импортировано повторно после открытия; локально сохраняются только заметки для проверки и язык интерфейса.
+
+Локальный тест в браузере:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+Дымовые тесты компаньона:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+План тестирования PWA для Android/iOS задокументирован в `web_companion/PWA_TESTPLAN.md`.
+
+## Стратегия платформ
+
+Настольное приложение является полноценным приложением для локальных документов, индекса RAG и подключений к провайдерам. Web/PWA-компаньон для Android, iOS и браузера доступен для просмотра созданного пользователем экспорта `notespacellm-workspace-v1.json`.
+Для конфиденциальных материалов настройте локальные конечные точки моделей и эмбеддингов и проверяйте экспорты перед отправкой. Контракт обмена задокументирован в `EXPORTFORMAT.md`.
+
+### Дымовые тесты исходного кода для macOS и Linux
+
+Дополнительные дымовые тесты исходного кода выполняются в GitHub Actions:
+- `tests/platform_smoke.py` — запуск PySide6 без отображения, импорт документов, экспорт отчёта, экспорт JSON рабочего пространства
+- `tests/linux_platform_smoke.py` — обёртка для Linux поверх `ubuntu-latest`
+
+## Структура проекта
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## Поддерживаемые форматы файлов
+
+| Формат | Чтение | Запись |
+|--------|--------|--------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ Для .doc требуется antiword или LibreOffice
+
+## Конфиденциальность
+
+NoteSpaceLLM хранит данные проектов, индексы документов, профили, настройки рабочих процессов и экспорты локально в `data/`, `profiles/`, `workflows/`, `output/` и `chroma_db/`. Эти папки намеренно исключены из Git.
+
+Когда включены внешние провайдеры LLM (OpenAI, Anthropic, Claude Code, удалённый Ollama), промпты и выбранные фрагменты документов могут передаваться этим сервисам. Удалённая конечная точка Ollama также получает текст документов, используемый для эмбеддингов. Для конфиденциальных документов используйте локальные конечные точки моделей и эмбеддингов и проверяйте экспортированные отчёты, историю чата и выбранные фрагменты перед отправкой; экспорты рабочего пространства не анонимизированы.
+
+## Советы
+
+1. **Большие наборы документов**: сначала выбирайте только релевантные документы для более быстрых результатов
+2. **Подзапросы**: используйте целевые подзапросы для более качественного анализа
+3. **Ollama**: локальная конечная точка Ollama поддерживает обработку без подключения к сети.
+4. **Порядок шагов рабочего процесса**: шаги можно переупорядочить на панели рабочего процесса
+
+## Лицензия
+
+AGPL-3.0-only — см. [LICENSE](LICENSE)
+
+Проект объявляет зависимости PySide6 и PyMuPDF. См. ссылки на выбранные лицензии вышестоящих проектов в [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md); собственная лицензия проекта — AGPL-3.0-only.
+
+## Связанный проект
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) — отдельная локальная база знаний с поиском SQLite/FTS5 и необязательными резюме LLM; она не интегрирована с NoteSpaceLLM.
+
+## Уведомление о лицензии и сопровождении
+
+Условия лицензии проекта см. в [LICENSE](LICENSE). Сопровождающие не обещают постоянной поддержки или доступности.
diff --git a/README_zh.md b/README_zh.md
new file mode 100644
index 0000000..8454a0e
--- /dev/null
+++ b/README_zh.md
@@ -0,0 +1,322 @@
+
+
+
+# NoteSpaceLLM
+
+[](LICENSE)
+[](https://www.python.org/)
+[]()
+[](https://github.com/file-bricks)
+[](https://github.com/open-bricks)
+[](llms.txt)
+
+[English](README.md) | [Deutsch](README_de.md) | [Español](README_es.md) | [简体中文](README_zh.md) | [日本語](README_ja.md) | [Русский](README_ru.md) | [Français](README_fr.md) | [हिन्दी](README_hi.md) | [العربية](README_ar.md) | [বাংলা](README_bn.md) | [Português](README_pt.md)
+
+> **面向研究与报告生成的本地优先文档笔记工具,支持配置本地或远程语言模型。**
+
+> [!NOTE]
+> **面向 AI 代理与自动化工作流:**
+> NoteSpaceLLM 在 [`llms.txt`](llms.txt) 中提供机器可读的架构与功能概览。
+
+NoteSpaceLLM 是一款基于 PySide6 的本地优先桌面应用,用于处理 PDF、
+Word 文件、Markdown、邮件导出、笔记和研究文件夹。它默认将项目数据保存在本地,
+支持本地或远程的 Ollama、OpenAI、Anthropic 以及 Claude Code 工作流,并可将分析结果
+导出为 Markdown、PDF、DOCX、HTML 和 TXT。现在还提供轻量级 Web/PWA 伴侣应用,
+可在 Android、iOS 和浏览器设备上查看已导出的工作区,无需将文档上传至服务器。
+
+**最适合的搜索词:** NotebookLM 替代方案、本地 RAG 文档分析、
+本地文档对话、PySide6 研究工具、本地模型报告生成器、本地优先文档工作流。
+
+## 快速开始
+
+```bash
+git clone https://github.com/file-bricks/NoteSpaceLLM.git
+cd NoteSpaceLLM
+pip install -r requirements.txt
+python main.py
+```
+
+在 Windows 上,也可以通过 `start.bat` 启动。可以使用 `build_exe.bat` 构建本地 Windows 启动器。
+
+如需通过引导式本地初始化来安装依赖并创建入门工作流/配置文件夹,请显式运行 `python setup_environment.py`。包元数据和构建使用单独的 setuptools 入口点,不会运行该步骤。
+
+运行测试和编译冒烟检查:
+
+```bash
+pytest
+python -m compileall -q main.py manage_translations.py setup_environment.py translator.py src
+cd web_companion
+npm test
+```
+
+## 系统架构
+
+```mermaid
+graph TD
+ subgraph UI ["PySide6 Desktop Application"]
+ DP["Document Panel (Drag & Drop)"]
+ WP["Workflow & Report Builder"]
+ CP["Interactive Chat Panel"]
+ OP["Multi-Format Exporter"]
+ end
+
+ subgraph Engine ["Local RAG Engine"]
+ TE["Text Extractor (PDF/DOCX/TXT/XLSX/EML)"]
+ CDB[("ChromaDB Vector Store")]
+ RM["Report Generator & Prompts"]
+ end
+
+ subgraph LLM ["LLM Integrations"]
+ OL["Ollama (Local / Remote)"]
+ OAI["OpenAI API"]
+ ANT["Anthropic Claude"]
+ CC["Claude Code CLI"]
+ end
+
+ subgraph PWA ["Web / PWA Companion"]
+ JSON["notespacellm-workspace-v1.json"]
+ SW["PWA Service Worker & Mobile Review"]
+ end
+
+ DP --> TE
+ TE --> CDB
+ WP --> RM
+ RM --> CDB
+ RM --> LLM
+ CP --> LLM
+ OP --> JSON
+ JSON --> SW
+```
+
+## 功能特性
+
+- **文档管理**:通过拖放添加文件和目录
+- **自动提取与索引**:新文档会立即处理并为 RAG 做好准备。
+- **选择性分析**:勾选或取消勾选文档,以决定是否将其纳入报告
+- **子查询**:右键单击文档,进行针对特定文档的定向研究
+- **工作流可视化**:报告流程的图形化概览
+- **聊天界面**:可与任意 LLM 对已加载文档进行交互式对话
+- **Claude Code 集成**:返回结构化 API 响应,或移交至交互式 Claude Code 会话
+- **多格式导出**:输出为 MD、PDF、DOCX、HTML、TXT
+- **提示词导出**:将报告上下文保存为 Markdown 提示词,用于外部 LLM 工作流
+- **Ollama 连接配置**:应用级提供方设置可在多个项目间复用
+- **语言支持**桌面界面和 Web/PWA Companion 均提供持久保存的语言选择器,支持英语、德语、西班牙语、简体中文、日语、俄语、法语、印地语、阿拉伯语、孟加拉语和葡萄牙语。阿拉伯语界面采用从右向左的布局。项目语言字段是工作区导出的元数据;它不会切换界面语言,也不会决定生成报告的语言。
+- **德语变音符号兼容界面**:德语界面文本使用原生字符;翻译扫描器可避免将英语文本误报为问题
+- **配置文件**:可复用的输出格式组合
+
+## 安装
+
+```bash
+# Clone or copy the project folder
+cd NoteSpaceLLM
+
+# Install dependencies
+pip install -r requirements.txt
+
+# Start the app
+python main.py
+```
+
+检查依赖项:
+
+```bash
+python main.py --check
+```
+
+## LLM 配置
+
+### Ollama(本地 — 推荐)
+
+1. [安装 Ollama](https://ollama.ai)
+2. 下载模型:`ollama pull llama3`
+3. 在应用中:菜单 > LLM > 使用 Ollama
+
+### Ollama(远程服务器)
+
+NoteSpaceLLM 可以连接远程 Ollama 服务器,例如通过 [ellmos-stack](https://github.com/ellmos-ai/ellmos-stack):
+
+1. 在应用中:菜单 > LLM > 设置
+2. **Ollama URL:** `http://your-server:11435`(或您的端口)
+3. **API Key:** 如果服务器位于认证代理之后(推荐)
+
+> **提示:** Ollama 没有内置身份验证。如需远程访问,请使用带有 API Key 或 Basic Auth 的反向代理(例如 Nginx)。
+
+Ollama 端点和模型在应用级设置或所选连接配置文件中配置,而不是按项目配置。在应用中保存 API Key 时,会通过操作系统的凭据存储进行保存。可选的 `NOTESPACELLM_OLLAMA_API_KEY` 环境变量会覆盖已保存的密钥,且应用从不写入该变量。旧版明文密钥在您于设置中显式迁移之前不会被发送。
+
+当配置的 Ollama 服务器为远程服务器时,它会接收用于嵌入的文档文本,以及用于生成的提示和上下文。API 密钥会以 Bearer 请求头的形式发送到所配置的 URL。纯 HTTP 不会加密内容或凭据;HTTPS 的行为取决于端点及其证书。
+
+### OpenAI
+
+```bash
+export OPENAI_API_KEY=""
+```
+
+### Anthropic (Claude)
+
+```bash
+export ANTHROPIC_API_KEY=""
+```
+
+### Claude Code
+
+需要本地 CLI:
+
+```bash
+npm install -g @anthropic-ai/claude-code
+```
+
+应用中提供两种模式:
+- **API 模式**:结构化响应直接返回到 NoteSpaceLLM
+- **聊天模式**:将提示交给交互式 Claude Code 控制台
+
+## 用法
+
+### 1. 添加文档
+
+- **拖放**:将文件或文件夹拖入左侧面板
+- **按钮**:"Add files"(添加文件)或 "Add folder"(添加文件夹)
+
+### 2. 选择文档
+
+- 复选框:包含/排除用于分析的文档
+- 按钮:"All"(全选)/ "None"(全不选),用于批量选择
+
+### 3. 子查询(可选)
+
+右键单击文档:
+- **Create summary**(创建摘要):自动生成摘要
+- **Extract information**(提取信息):查找特定数据
+- **Analyse**(分析):有针对性的分析
+- **Ask question**(提问):关于该文档的具体问题
+
+### 4. 定义主要问题
+
+在工作流面板中输入核心研究问题。
+
+### 5. 选择工作流/报告类型
+
+- **Analysis**(分析):包含建议的综合分析
+- **Summary**(摘要):简短摘要
+- **Research report**(研究报告):学术结构
+- **Comparison**(对比):系统的文档对比
+
+### 6. 生成报告
+
+点击 "Create report"(创建报告),输出结果会显示在右侧面板中。
+
+### 7. 导出
+
+选择输出格式并点击 "Export"(导出)。
+
+### 8. 提示导出
+
+使用 **"Export prompt"**(导出提示)将当前工作上下文保存为 Markdown 文件,以便在 Claude Code 或其他 LLM 工作流中使用。
+
+## Web/PWA 配套应用
+
+`web_companion/` 下是一个面向 Android、iOS 和浏览器的只读配套应用分支。它会在浏览器中本地导入已导出的 `notespacellm-workspace-v1.json` 文件,显示报告、文档元数据和所选摘录,并可将审阅笔记导出为 Markdown。工作区仅保留在当前浏览器会话中,重新打开后必须重新导入;只有审阅笔记和界面语言会在本地持久保存。
+
+本地浏览器测试:
+
+```powershell
+$env:PYTHONIOENCODING='utf-8'
+python -m http.server 8765 -d web_companion
+```
+
+配套应用冒烟测试:
+
+```powershell
+cd web_companion
+node --test tests/library.test.mjs
+```
+
+Android/iOS PWA 测试计划记录在 `web_companion/PWA_TESTPLAN.md` 中。
+
+## 平台策略
+
+桌面应用是完整应用,用于本地文档、RAG 索引和提供商连接。Android、iOS 和浏览器 Web/PWA 配套应用可用于审阅用户创建的 `notespacellm-workspace-v1.json` 导出文件。对于敏感材料,请配置本地模型和嵌入端点,并在分享前审阅导出内容。交换约定记录在 `EXPORTFORMAT.md` 中。
+
+### macOS 和 Linux 源码冒烟测试
+
+GitHub Actions 中运行以下额外的源码冒烟测试:
+- `tests/platform_smoke.py` — 离屏 PySide6 启动、文档导入、报告导出、工作区 JSON 导出
+- `tests/linux_platform_smoke.py` — `ubuntu-latest` 的 Linux 专用包装器
+
+## 项目结构
+
+```
+NoteSpaceLLM/
+├── main.py # Entry point
+├── requirements.txt # Dependencies
+├── translator.py # Translation system and catalog scanner used by the desktop UI
+├── locales/ # Catalog inputs for the translation helper
+├── src/
+│ ├── core/ # Core functionality
+│ │ ├── document_manager.py
+│ │ ├── text_extractor.py
+│ │ ├── sub_query.py
+│ │ └── project.py
+│ ├── gui/ # PySide6 UI
+│ │ ├── main_window.py
+│ │ ├── document_panel.py
+│ │ ├── workflow_panel.py
+│ │ ├── chat_panel.py
+│ │ └── output_panel.py
+│ ├── llm/ # LLM integration (local + remote)
+│ │ ├── client.py
+│ │ ├── ollama_client.py
+│ │ ├── openai_client.py
+│ │ └── anthropic_client.py
+│ └── reports/ # Report generation
+│ ├── generator.py
+│ ├── templates.py
+│ └── exporter.py
+├── web_companion/ # PWA Companion
+├── data/ # Project data (gitignored)
+├── workflows/ # Workflow definitions
+├── profiles/ # Output profiles
+└── output/ # Exported reports
+```
+
+## 支持的文件格式
+
+| 格式 | 读取 | 写入 |
+|------|------|------|
+| PDF | ✅ | ✅ |
+| DOCX | ✅ | ✅ |
+| DOC | ⚠️ | — |
+| TXT | ✅ | ✅ |
+| MD | ✅ | ✅ |
+| XLSX | ✅ | — |
+| HTML | — | ✅ |
+| EML | ✅ | — |
+| MSG | ✅ | — |
+
+⚠️ .doc 需要 antiword 或 LibreOffice
+
+## 隐私
+
+NoteSpaceLLM 将项目数据、文档索引、配置文件、工作流设置和导出内容本地存储在 `data/`、`profiles/`、`workflows/`、`output/` 和 `chroma_db/` 中。这些文件夹有意排除在 Git 之外。
+
+启用外部 LLM 提供商(OpenAI、Anthropic、Claude Code、远程 Ollama)时,提示和所选文档摘录可能会被传输到这些服务。远程 Ollama 端点还会接收用于嵌入的文档文本。对于敏感文档,请使用本地模型和嵌入端点,并在分享前审阅导出的报告、聊天记录和所选摘录;工作区导出内容未经匿名化处理。
+
+## 提示
+
+1. **大型文档集**:先只选择相关文档,以获得更快的结果
+2. **子查询**:使用有针对性的子查询以获得更高质量的分析
+3. **Ollama**:本地 Ollama 端点支持离线处理。
+4. **工作流顺序**:可在工作流面板中重新排列步骤
+
+## 许可证
+
+AGPL-3.0-only — 参见 [LICENSE](LICENSE)
+
+本项目声明使用 PySide6 和 PyMuPDF。所选上游许可证参考请见 [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md);本项目自身的许可证为 AGPL-3.0-only。
+
+## 相关项目
+
+[KnowledgeDigest](https://github.com/file-bricks/KnowledgeDigest) 是一个独立的本地知识数据库,具有 SQLite/FTS5 搜索和可选的 LLM 摘要功能;它未与 NoteSpaceLLM 集成。
+
+## 许可证与维护声明
+
+项目的许可条款请参见 [LICENSE](LICENSE)。维护者不承诺持续维护或可用性。
diff --git a/locales/__init__.py b/locales/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/locales/gui_translations.json b/locales/gui_translations.json
new file mode 100644
index 0000000..4d02bc4
--- /dev/null
+++ b/locales/gui_translations.json
@@ -0,0 +1,5021 @@
+{
+ "schema": "notespacellm.gui-i18n-runtime.v1",
+ "status": "complete 294 x 11 candidate; pending Root acceptance and runtime integration",
+ "source_commit": "7111162277e73ab91b6809ea074b60dfb6e2b4fa",
+ "base_source_catalog_sha256": "22b2d00d3d9eceba90d601d20473e16ab7ce89d8e5c1faba289895129192631a",
+ "delta_source_sha256": "68cf99b15599bfd0e2061816afd13ab4ebb2336b4fc777b3901f1ae4bd8ffff1",
+ "languages": [
+ "en",
+ "de",
+ "es",
+ "zh",
+ "ja",
+ "ru",
+ "fr",
+ "hi",
+ "ar",
+ "bn",
+ "pt"
+ ],
+ "entry_count": 294,
+ "entries": [
+ {
+ "key": "gui.chat.confidence",
+ "canonical_source_value": " (Konfidenz: {percent:.0%})",
+ "translations": {
+ "de": " (Konfidenz: {percent:.0%})",
+ "ru": " (Конфиденциальность: {percent:.0%})",
+ "zh": " (置信度: {percent:.0%})",
+ "pt": " (Confiança: {percent:.0%})",
+ "bn": " (আস্থার মাত্রা: {percent:.0%})",
+ "es": " (Confianza: {percent:.0%})",
+ "ar": " (الثقة: {percent:.0%})",
+ "fr": " (Confiance : {percent:.0%})",
+ "hi": " (विश्वसनीयता: {percent:.0%})",
+ "en": " (Confidence: {percent:.0%})",
+ "ja": " (信頼度: {percent:.0%})"
+ }
+ },
+ {
+ "key": "gui.chat.context_status",
+ "canonical_source_value": "Kontext: ~{count} Zeichen",
+ "translations": {
+ "de": "Kontext: ~{count} Zeichen",
+ "es": "Contexto: ~{count} caracteres",
+ "ja": "文脈: ~{count} 文字",
+ "pt": "Contexto: ~{count} caracteres",
+ "bn": "প্রসঙ্গ: ~{count} অক্ষর",
+ "fr": "Contexte : ~{count} caractères",
+ "ru": "Контекст: ~{count} символов",
+ "hi": "संदर्भ: ~{count} अक्षर",
+ "ar": "السياق: ~{count} حرفًا",
+ "zh": "上下文:~{count} 个字符",
+ "en": "Context: ~{count} characters"
+ }
+ },
+ {
+ "key": "gui.chat.doc_status",
+ "canonical_source_value": "Docs: {indexed}/{selected} indexiert",
+ "translations": {
+ "de": "Docs: {indexed}/{selected} indexiert",
+ "bn": "নথি: {indexed}/{selected} সূচিবদ্ধ",
+ "en": "Docs: {indexed}/{selected} indexed",
+ "hi": "दस्तावेज़: {indexed}/{selected} अनुक्रमित",
+ "ru": "Проиндексировано документов: {indexed}/{selected}",
+ "fr": "Documents : {indexed}/{selected} indexés",
+ "es": "Documentos: {indexed}/{selected} indexados",
+ "ar": "الوثائق: {indexed}/{selected} مفهرسة",
+ "zh": "文档:{indexed}/{selected} 已建立索引",
+ "ja": "ドキュメント: {indexed}/{selected} にインデックス済み",
+ "pt": "Documentos: {indexed}/{selected} indexados"
+ }
+ },
+ {
+ "key": "gui.chat.error_prefix",
+ "canonical_source_value": "Fehler: {message}",
+ "translations": {
+ "de": "Fehler: {message}",
+ "pt": "Erro: {message}",
+ "zh": "错误:{message}",
+ "en": "Error: {message}",
+ "es": "Error: {message}",
+ "ja": "エラー: {message}",
+ "bn": "ত্রুটি: {message}",
+ "hi": "त्रुटि: {message}",
+ "fr": "Erreur : {message}",
+ "ar": "خطأ: {message}",
+ "ru": "Ошибка: {message}"
+ }
+ },
+ {
+ "key": "gui.chat.history_cleared",
+ "canonical_source_value": "Verlauf gelöscht. Stelle eine neue Frage.",
+ "translations": {
+ "de": "Verlauf gelöscht. Stelle eine neue Frage.",
+ "fr": "Historique supprimé. Pose une nouvelle question.",
+ "es": "El historial se ha eliminado. Haz una nueva pregunta.",
+ "ja": "履歴が削除されました。新しい質問をしてください。",
+ "ru": "История очищена. Задайте новый вопрос.",
+ "pt": "O histórico foi excluído. Faça uma nova pergunta.",
+ "ar": "تم حذف السجل. اطرح سؤالاً جديداً.",
+ "bn": "ইতিহাস মুছে ফেলা হয়েছে। একটি নতুন প্রশ্ন করুন।",
+ "zh": "历史记录已删除。请提出新问题。",
+ "hi": "इतिहास मिटा दिया गया है। कृपया एक नया प्रश्न पूछें।",
+ "en": "History deleted. Ask a new question."
+ }
+ },
+ {
+ "key": "gui.chat.no_backend",
+ "canonical_source_value": "Kein LLM-Client oder RAG-Engine konfiguriert. Bitte Einstellungen prüfen.",
+ "translations": {
+ "de": "Kein LLM-Client oder RAG-Engine konfiguriert. Bitte Einstellungen prüfen.",
+ "hi": "LLM क्लाइंट या RAG इंजन कॉन्फ़िगर नहीं है। कृपया सेटिंग्स जांचें।",
+ "zh": "未配置 LLM 客户端或 RAG 引擎。请检查设置。",
+ "en": "No LLM client or RAG engine is configured. Please check the settings.",
+ "es": "No hay ningún cliente LLM ni motor RAG configurado. Revise la configuración.",
+ "ru": "Клиент LLM или движок RAG не настроен. Пожалуйста, проверьте настройки.",
+ "bn": "LLM-ক্লায়েন্ট বা RAG-ইঞ্জিন কনফিগার করা নেই। অনুগ্রহ করে সেটিংস পরীক্ষা করুন।",
+ "ar": "لم يتم إعداد عميل LLM أو محرك RAG. يرجى مراجعة الإعدادات.",
+ "pt": "Nenhum cliente LLM ou mecanismo RAG está configurado. Verifique as configurações.",
+ "ja": "LLM クライアントまたは RAG エンジンが設定されていません。設定を確認してください。",
+ "fr": "Aucun client LLM ni moteur RAG n'est configuré. Veuillez vérifier les paramètres."
+ }
+ },
+ {
+ "key": "gui.chat.no_indexed_documents",
+ "canonical_source_value": "⚠️ Keine indexierten Dokumente gefunden. Bitte Dokumente laden und indexieren (automatisch bei Text-Extraktion).",
+ "translations": {
+ "de": "⚠️ Keine indexierten Dokumente gefunden. Bitte Dokumente laden und indexieren (automatisch bei Text-Extraktion).",
+ "hi": "⚠️ कोई अनुक्रमित दस्तावेज़ नहीं मिले। कृपया दस्तावेज़ लोड करें और उन्हें अनुक्रमित करें (पाठ निष्कर्षण के समय स्वचालित रूप से)।",
+ "pt": "⚠️ Nenhum documento indexado encontrado. Carregue e indexe os documentos (automaticamente durante a extração de texto).",
+ "es": "⚠️ No se encontraron documentos indexados. Por favor, carga e indexa los documentos (automáticamente durante la extracción de texto).",
+ "fr": "⚠️ Aucun document indexé trouvé. Veuillez charger et indexer les documents (automatiquement lors de l'extraction du texte).",
+ "ja": "⚠️ インデックス済みのドキュメントが見つかりません。ドキュメントを読み込んでインデックスを作成してください(テキスト抽出時に自動で実行されます)。",
+ "ar": "⚠️ لم يتم العثور على مستندات مفهرسة. يرجى تحميل المستندات وفهرستها (تلقائيًا عند استخراج النص).",
+ "ru": "⚠️ Индексированные документы не найдены. Пожалуйста, загрузите и проиндексируйте документы (автоматически при извлечении текста).",
+ "bn": "⚠️ কোনো ইন্ডেক্সকৃত নথি পাওয়া যায়নি। অনুগ্রহ করে নথি লোড করুন এবং ইন্ডেক্স করুন (টেক্সট এক্সট্র্যাকশনের সময় স্বয়ংক্রিয়ভাবে)।",
+ "en": "⚠️ No indexed documents found. Please load and index documents (automatically during text extraction).",
+ "zh": "⚠️ 未找到已索引的文档。请加载并索引文档(文本提取时会自动索引)。"
+ }
+ },
+ {
+ "key": "gui.chat.rag_error_prefix",
+ "canonical_source_value": "❌ RAG-Fehler: {message}",
+ "translations": {
+ "de": "❌ RAG-Fehler: {message}",
+ "bn": "❌ RAG-ত্রুটি: {message}",
+ "hi": "❌ RAG-त्रुटि: {message}",
+ "pt": "❌ RAG-Erro: {message}",
+ "zh": "❌ RAG-错误:{message}",
+ "ja": "❌ RAG-エラー: {message}",
+ "ar": "❌ خطأ RAG: {message}",
+ "ru": "❌ Ошибка RAG: {message}",
+ "fr": "❌ Erreur RAG : {message}",
+ "es": "❌ Error de RAG: {message}",
+ "en": "❌ RAG error: {message}"
+ }
+ },
+ {
+ "key": "gui.chat.rag_status",
+ "canonical_source_value": "RAG: {count} Chunks",
+ "translations": {
+ "de": "RAG: {count} Chunks",
+ "fr": "RAG : {count} Fragments",
+ "bn": "RAG: {count}টি খণ্ড",
+ "es": "RAG: {count} Fragmentos",
+ "zh": "RAG: {count} 分块",
+ "pt": "RAG: {count} fragmentos",
+ "ja": "RAG: {count} チャンク",
+ "hi": "RAG: {count} के खंड",
+ "ar": "RAG: {count} مقاطع",
+ "ru": "RAG: {count} фрагментов",
+ "en": "RAG: {count} chunks"
+ }
+ },
+ {
+ "key": "gui.chat.rag_worker_error",
+ "canonical_source_value": "RAG request failed ({error_type}).",
+ "translations": {
+ "de": "RAG request failed ({error_type}).",
+ "ja": "RAG の要求が失敗しました ({error_type})。",
+ "zh": "RAG 请求失败({error_type})。",
+ "fr": "Échec de la requête RAG ({error_type}).",
+ "hi": "RAG अनुरोध विफल रहा ({error_type})।",
+ "ru": "Не удалось выполнить запрос RAG ({error_type}).",
+ "es": "Falló la solicitud de RAG ({error_type}).",
+ "pt": "Falha na solicitação RAG ({error_type}).",
+ "en": "RAG request failed ({error_type}).",
+ "ar": "فشل طلب RAG ({error_type}).",
+ "bn": "RAG অনুরোধ ব্যর্থ হয়েছে ({error_type})।"
+ }
+ },
+ {
+ "key": "gui.chat.role_assistant",
+ "canonical_source_value": "Assistent",
+ "translations": {
+ "de": "Assistent",
+ "ru": "Ассистент",
+ "fr": "Assistant",
+ "zh": "助手",
+ "en": "Assistant",
+ "pt": "Assistente",
+ "es": "Asistente",
+ "bn": "সহকারী",
+ "hi": "सहायक",
+ "ar": "المساعد",
+ "ja": "アシスタント"
+ }
+ },
+ {
+ "key": "gui.chat.role_system",
+ "canonical_source_value": "System",
+ "translations": {
+ "de": "System",
+ "ru": "Система",
+ "en": "System",
+ "ja": "システム",
+ "bn": "সিস্টেম",
+ "ar": "النظام",
+ "hi": "प्रणाली",
+ "pt": "Sistema",
+ "zh": "系统",
+ "es": "Sistema",
+ "fr": "Système"
+ }
+ },
+ {
+ "key": "gui.chat.role_user",
+ "canonical_source_value": "Du",
+ "translations": {
+ "de": "Du",
+ "ja": "あなた",
+ "bn": "আপনি",
+ "es": "Tú",
+ "fr": "Vous",
+ "pt": "Você",
+ "ru": "Вы",
+ "zh": "你",
+ "ar": "أنت",
+ "en": "You",
+ "hi": "आप"
+ }
+ },
+ {
+ "key": "gui.chat.sources",
+ "canonical_source_value": "Quellen: ",
+ "translations": {
+ "de": "Quellen: ",
+ "zh": "来源: ",
+ "en": "Sources: ",
+ "ru": "Источники: ",
+ "hi": "स्रोत: ",
+ "ja": "出典: ",
+ "pt": "Fontes: ",
+ "fr": "Sources : ",
+ "es": "Fuentes: ",
+ "bn": "উৎস: ",
+ "ar": "المصادر: "
+ }
+ },
+ {
+ "key": "gui.chat.welcome",
+ "canonical_source_value": "Willkommen! Ich kann Fragen zu den geladenen Dokumenten beantworten. Wähle Dokumente aus und stelle Fragen zum Inhalt.",
+ "translations": {
+ "de": "Willkommen! Ich kann Fragen zu den geladenen Dokumenten beantworten. Wähle Dokumente aus und stelle Fragen zum Inhalt.",
+ "bn": "স্বাগতম! আমি লোড করা নথিগুলো সম্পর্কে প্রশ্নের উত্তর দিতে পারি। কয়েকটি নথি নির্বাচন করুন এবং তাদের বিষয়বস্তু সম্পর্কে প্রশ্ন করুন।",
+ "zh": "欢迎!我可以回答有关已加载文档的问题。请选择文档并就其内容提问。",
+ "ja": "ようこそ!読み込まれたドキュメントに関するご質問にお答えできます。ドキュメントを選択して、その内容について質問してください。",
+ "es": "¡Bienvenido! Puedo responder preguntas sobre los documentos cargados. Selecciona documentos y haz preguntas sobre su contenido.",
+ "fr": "Bienvenue ! Je peux répondre à vos questions sur les documents chargés. Sélectionnez des documents et posez vos questions sur leur contenu.",
+ "ar": "أهلاً بك! يمكنني الإجابة عن الأسئلة المتعلقة بالمستندات المحمّلة. اختر المستندات واطرح أسئلتك حول محتواها.",
+ "en": "Welcome! I can answer questions about the loaded documents. Select documents and ask questions about their content.",
+ "hi": "नमस्ते! मैं लोड किए गए दस्तावेज़ों के बारे में प्रश्नों का उत्तर दे सकता हूँ। दस्तावेज़ चुनें और उनकी सामग्री के बारे में प्रश्न पूछें।",
+ "ru": "Добро пожаловать! Я могу отвечать на вопросы о загруженных документах. Выберите документы и задайте вопросы об их содержимом.",
+ "pt": "Bem-vindo! Posso responder a perguntas sobre os documentos carregados. Selecione documentos e faça perguntas sobre o seu conteúdo."
+ }
+ },
+ {
+ "key": "gui.chat.worker_error",
+ "canonical_source_value": "LLM-Anfrage fehlgeschlagen ({error_type}).",
+ "translations": {
+ "de": "LLM-Anfrage fehlgeschlagen ({error_type}).",
+ "es": "LLM: solicitud fallida ({error_type}).",
+ "ar": "فشل طلب LLM ({error_type}).",
+ "zh": "LLM 请求失败({error_type})。",
+ "ru": "LLM-запрос не выполнен ({error_type}).",
+ "pt": "Falha na solicitação LLM ({error_type}).",
+ "en": "LLM request failed ({error_type}).",
+ "fr": "Échec de la requête LLM ({error_type}).",
+ "hi": "LLM-अनुरोध विफल रहा ({error_type})।",
+ "ja": "LLMの要求が失敗しました({error_type})。",
+ "bn": "LLM-এর অনুরোধ ব্যর্থ হয়েছে ({error_type})।"
+ }
+ },
+ {
+ "key": "gui.chat_panel.bereit",
+ "canonical_source_value": "Bereit",
+ "translations": {
+ "de": "Bereit",
+ "ar": "جاهز",
+ "fr": "Prêt",
+ "en": "Ready",
+ "ru": "Готово",
+ "bn": "প্রস্তুত",
+ "es": "Listo",
+ "pt": "Pronto",
+ "zh": "就绪",
+ "hi": "तैयार",
+ "ja": "準備完了"
+ }
+ },
+ {
+ "key": "gui.chat_panel.chromadb_value_chunks_indexiert_embedding_value",
+ "canonical_source_value": "ChromaDB: {value1} Chunks indexiert\nEmbedding: {value2}",
+ "translations": {
+ "de": "ChromaDB: {value1} Chunks indexiert\nEmbedding: {value2}",
+ "ar": "ChromaDB: تمت فهرسة {value1} من المقاطع\nالتضمين: {value2}",
+ "es": "ChromaDB: {value1} fragmentos indexados\nEmbedding: {value2}",
+ "en": "ChromaDB: {value1} chunks indexed\nEmbedding: {value2}",
+ "pt": "ChromaDB: {value1} fragmentos indexados\nEmbedding: {value2}",
+ "fr": "ChromaDB : {value1} fragments indexés\nEmbedding : {value2}",
+ "hi": "ChromaDB: {value1} टेक्स्ट खंड अनुक्रमित\nएम्बेडिंग: {value2}",
+ "ru": "ChromaDB: проиндексировано фрагментов: {value1}\nEmbedding: {value2}",
+ "zh": "ChromaDB:已索引 {value1} 个块\n嵌入:{value2}",
+ "ja": "ChromaDB: {value1} 件のチャンクをインデックス化\nEmbedding: {value2}",
+ "bn": "ChromaDB: {value1}টি টেক্সট খণ্ড সূচিবদ্ধ\nEmbedding: {value2}"
+ }
+ },
+ {
+ "key": "gui.chat_panel.dokumente_value",
+ "canonical_source_value": "Dokumente: {value1}",
+ "translations": {
+ "de": "Dokumente: {value1}",
+ "fr": "Documents : {value1}",
+ "ru": "Документы: {value1}",
+ "hi": "दस्तावेज़: {value1}",
+ "ja": "ドキュメント: {value1}",
+ "pt": "Documentos: {value1}",
+ "ar": "المستندات: {value1}",
+ "bn": "দলিলসমূহ: {value1}",
+ "en": "Documents: {value1}",
+ "zh": "文档:{value1}",
+ "es": "Documentos: {value1}"
+ }
+ },
+ {
+ "key": "gui.chat_panel.dokumenten_chat",
+ "canonical_source_value": "Dokumenten-Chat",
+ "translations": {
+ "de": "Dokumenten-Chat",
+ "hi": "Dokumenten-Chat को हिंदी में अनुवाद करते हुए:",
+ "en": "Document chat",
+ "zh": "文档聊天",
+ "es": "Chat de documentos",
+ "ru": "Чат по документам",
+ "fr": "Chat de documents",
+ "pt": "Chat de documentos",
+ "ja": "ドキュメントチャット",
+ "bn": "Dokumenten-Chat",
+ "ar": "محادثة المستندات"
+ }
+ },
+ {
+ "key": "gui.chat_panel.fehler",
+ "canonical_source_value": "Fehler",
+ "translations": {
+ "de": "Fehler",
+ "zh": "错误",
+ "bn": "ত্রুটি",
+ "fr": "Erreur",
+ "en": "Error",
+ "pt": "Erro",
+ "ja": "エラー",
+ "ar": "خطأ",
+ "ru": "Ошибка",
+ "es": "Error",
+ "hi": "त्रुटि"
+ }
+ },
+ {
+ "key": "gui.chat_panel.frage_zu_den_dokumenten_stellen",
+ "canonical_source_value": "Frage zu den Dokumenten stellen...",
+ "translations": {
+ "de": "Frage zu den Dokumenten stellen...",
+ "es": "Hacer una pregunta sobre los documentos...",
+ "pt": "Fazer uma pergunta sobre os documentos...",
+ "ru": "Задайте вопрос о документах...",
+ "ja": "ドキュメントについて質問する...",
+ "en": "Ask a question about the documents...",
+ "zh": "在文档中提问...",
+ "fr": "Poser une question sur les documents...",
+ "bn": "Frage zu den Dokumenten stellen...",
+ "hi": "दस्तावेज़ों के बारे में प्रश्न पूछें...",
+ "ar": "اطرح سؤالًا حول المستندات..."
+ }
+ },
+ {
+ "key": "gui.chat_panel.generiere_antwort",
+ "canonical_source_value": "Generiere Antwort...",
+ "translations": {
+ "de": "Generiere Antwort...",
+ "ru": "Генерирую ответ...",
+ "pt": "Gerando resposta...",
+ "hi": "उत्तर उत्पन्न हो रहा है...",
+ "en": "Generating response...",
+ "fr": "Génération de la réponse...",
+ "zh": "生成回答中...",
+ "bn": "উত্তর তৈরি করা হচ্ছে...",
+ "es": "Generando respuesta...",
+ "ar": "جارٍ إنشاء الرد...",
+ "ja": "回答を生成中..."
+ }
+ },
+ {
+ "key": "gui.chat_panel.rag_an",
+ "canonical_source_value": "RAG: AN",
+ "translations": {
+ "de": "RAG: AN",
+ "hi": "RAG: चालू",
+ "bn": "RAG: চালু",
+ "ru": "RAG: ВКЛ.",
+ "zh": "RAG:开启",
+ "fr": "RAG : ACTIVÉ",
+ "pt": "RAG: LIGADO",
+ "ja": "RAG: オン",
+ "en": "RAG: ON",
+ "ar": "RAG: تشغيل",
+ "es": "RAG: ACTIVADO"
+ }
+ },
+ {
+ "key": "gui.chat_panel.rag_engine_nicht_verfugbar",
+ "canonical_source_value": "RAG-Engine nicht verfügbar",
+ "translations": {
+ "de": "RAG-Engine nicht verfügbar",
+ "ru": "Движок RAG недоступен",
+ "fr": "Le moteur RAG n'est pas disponible",
+ "ar": "RAG-محرك غير متاح",
+ "pt": "O mecanismo RAG não está disponível",
+ "es": "El motor RAG no está disponible",
+ "bn": "RAG-ইঞ্জিন উপলব্ধ নয়",
+ "hi": "RAG-इंजन उपलब्ध नहीं है",
+ "ja": "RAG-エンジンが利用できません",
+ "en": "RAG-Engine not available",
+ "zh": "RAG-引擎不可用"
+ }
+ },
+ {
+ "key": "gui.chat_panel.rag_suche",
+ "canonical_source_value": "🔍 RAG-Suche...",
+ "translations": {
+ "de": "🔍 RAG-Suche...",
+ "ja": "🔍 RAG検索...",
+ "ru": "🔍 RAG-Поиск...",
+ "ar": "🔍 RAG-بحث...",
+ "bn": "🔍 RAG-অনুসন্ধান...",
+ "pt": "🔍 Pesquisar RAG...",
+ "fr": "🔍 Recherche RAG...",
+ "zh": "🔍 RAG-搜索...",
+ "hi": "🔍 RAG-खोजें...",
+ "es": "🔍 Búsqueda RAG...",
+ "en": "🔍 RAG-Search..."
+ }
+ },
+ {
+ "key": "gui.chat_panel.senden",
+ "canonical_source_value": "Senden",
+ "translations": {
+ "de": "Senden",
+ "ja": "送信",
+ "fr": "Envoyer",
+ "en": "Send",
+ "hi": "भेजें",
+ "bn": "পাঠান",
+ "ru": "Отправить",
+ "es": "Enviar",
+ "pt": "Enviar",
+ "zh": "发送",
+ "ar": "إرسال"
+ }
+ },
+ {
+ "key": "gui.chat_panel.verlauf_loschen",
+ "canonical_source_value": "Verlauf löschen",
+ "translations": {
+ "de": "Verlauf löschen",
+ "ar": "مسح السجل",
+ "zh": "清除历史记录",
+ "en": "Clear history",
+ "fr": "Effacer l'historique",
+ "ru": "Очистить историю",
+ "pt": "Apagar o histórico",
+ "ja": "履歴を消去",
+ "bn": "ইতিহাস মুছুন",
+ "es": "Borrar historial",
+ "hi": "इतिहास मिटाएँ"
+ }
+ },
+ {
+ "key": "gui.common.cancel",
+ "canonical_source_value": "Abbrechen",
+ "translations": {
+ "de": "Abbrechen",
+ "zh": "取消",
+ "es": "Cancelar",
+ "pt": "Cancelar",
+ "fr": "Annuler",
+ "ja": "キャンセル",
+ "en": "Cancel",
+ "ru": "Отмена",
+ "ar": "إلغاء",
+ "bn": "বাতিল করুন",
+ "hi": "रद्द करें"
+ }
+ },
+ {
+ "key": "gui.common.no",
+ "canonical_source_value": "Nein",
+ "translations": {
+ "de": "Nein",
+ "ar": "لا",
+ "bn": "না",
+ "en": "No",
+ "ru": "Нет",
+ "es": "No",
+ "ja": "いいえ",
+ "fr": "Non",
+ "pt": "Não",
+ "zh": "否",
+ "hi": "नहीं"
+ }
+ },
+ {
+ "key": "gui.common.ok",
+ "canonical_source_value": "OK",
+ "translations": {
+ "de": "OK",
+ "ar": "حسنًا",
+ "ja": "OK",
+ "ru": "ОК",
+ "bn": "ঠিক আছে",
+ "en": "OK",
+ "pt": "OK",
+ "hi": "ठीक है",
+ "zh": "好的",
+ "es": "OK",
+ "fr": "OK"
+ }
+ },
+ {
+ "key": "gui.common.yes",
+ "canonical_source_value": "Ja",
+ "translations": {
+ "de": "Ja",
+ "fr": "Oui",
+ "ru": "Да",
+ "ja": "はい",
+ "es": "Sí",
+ "pt": "Sim",
+ "bn": "হ্যাঁ",
+ "zh": "是",
+ "en": "Yes",
+ "ar": "نعم",
+ "hi": "हाँ"
+ }
+ },
+ {
+ "key": "gui.document.custom_prompt_analyze",
+ "canonical_source_value": "Was soll analysiert werden?",
+ "translations": {
+ "de": "Was soll analysiert werden?",
+ "ar": "ما الذي يجب تحليله؟",
+ "es": "¿Qué debe analizarse?",
+ "fr": "Que faut-il analyser ?",
+ "pt": "O que deve ser analisado?",
+ "bn": "কী বিশ্লেষণ করতে হবে?",
+ "en": "What should be analyzed?",
+ "ru": "Что нужно проанализировать?",
+ "zh": "要分析什么?",
+ "ja": "何を分析しますか?",
+ "hi": "क्या विश्लेषण करना है?"
+ }
+ },
+ {
+ "key": "gui.document.custom_prompt_extract",
+ "canonical_source_value": "Welche Informationen extrahieren?",
+ "translations": {
+ "de": "Welche Informationen extrahieren?",
+ "es": "¿Qué información hay que extraer?",
+ "fr": "Quelles informations faut-il extraire ?",
+ "ar": "ما المعلومات التي يجب استخراجها؟",
+ "ru": "Какую информацию извлечь?",
+ "en": "Which information should be extracted?",
+ "pt": "Quais informações devem ser extraídas?",
+ "hi": "कौन-सी जानकारी निकालनी है?",
+ "zh": "要提取哪些信息?",
+ "bn": "কোন তথ্য বের করতে হবে?",
+ "ja": "抽出する情報は何ですか?"
+ }
+ },
+ {
+ "key": "gui.document.custom_prompt_generic",
+ "canonical_source_value": "Eingabe:",
+ "translations": {
+ "de": "Eingabe:",
+ "en": "Input:",
+ "hi": "इनपुट:",
+ "pt": "Entrada:",
+ "es": "Entrada:",
+ "zh": "输入:",
+ "bn": "ইনপুট:",
+ "fr": "Entrée :",
+ "ru": "Ввод:",
+ "ja": "入力:",
+ "ar": "المدخل:"
+ }
+ },
+ {
+ "key": "gui.document.custom_prompt_question",
+ "canonical_source_value": "Welche Frage stellen?",
+ "translations": {
+ "de": "Welche Frage stellen?",
+ "zh": "你要问什么问题?",
+ "en": "What question should we ask?",
+ "es": "¿Qué pregunta hacer?",
+ "ar": "ما السؤال الذي تريد طرحه؟",
+ "ru": "Какой вопрос задать?",
+ "pt": "Que pergunta devo fazer?",
+ "ja": "どのような質問をしますか?",
+ "bn": "কোন প্রশ্ন করবেন?",
+ "fr": "Quelle question poser ?",
+ "hi": "कौन-सा प्रश्न पूछना है?"
+ }
+ },
+ {
+ "key": "gui.document.status_analyzing",
+ "canonical_source_value": "Analysiere...",
+ "translations": {
+ "de": "Analysiere...",
+ "ru": "Идёт анализ...",
+ "pt": "Analisando...",
+ "ar": "تحليل...",
+ "fr": "Analyse en cours...",
+ "bn": "বিশ্লেষণ চলছে...",
+ "en": "Analyzing...",
+ "hi": "विश्लेषण हो रहा है...",
+ "ja": "分析しています...",
+ "zh": "正在分析...",
+ "es": "Analizando..."
+ }
+ },
+ {
+ "key": "gui.document.status_completed",
+ "canonical_source_value": "Fertig",
+ "translations": {
+ "de": "Fertig",
+ "ja": "完了",
+ "pt": "Pronto",
+ "zh": "完成",
+ "fr": "Terminé",
+ "bn": "সম্পন্ন",
+ "ru": "Готово",
+ "en": "Done",
+ "ar": "تم",
+ "es": "Listo",
+ "hi": "हो गया"
+ }
+ },
+ {
+ "key": "gui.document.status_extracting",
+ "canonical_source_value": "Extrahiere...",
+ "translations": {
+ "de": "Extrahiere...",
+ "ar": "جارٍ الاستخراج...",
+ "bn": "Extrahiere...",
+ "ru": "Извлеките...",
+ "pt": "Extraindo...",
+ "zh": "正在提取...",
+ "ja": "Extrahiere...",
+ "en": "Extracting...",
+ "fr": "Extraction en cours...",
+ "es": "Extrayendo...",
+ "hi": "निकाला जा रहा है..."
+ }
+ },
+ {
+ "key": "gui.document.status_pending",
+ "canonical_source_value": "Ausstehend",
+ "translations": {
+ "de": "Ausstehend",
+ "fr": "En attente",
+ "en": "Pending",
+ "es": "Pendiente",
+ "pt": "Pendente",
+ "zh": "待处理",
+ "ja": "保留中",
+ "ru": "В ожидании",
+ "hi": "लंबित",
+ "bn": "অপেক্ষমাণ",
+ "ar": "قيد الانتظار"
+ }
+ },
+ {
+ "key": "gui.document_panel.alle",
+ "canonical_source_value": "Alle",
+ "translations": {
+ "de": "Alle",
+ "bn": "সব",
+ "en": "All",
+ "hi": "सभी",
+ "pt": "Todos",
+ "zh": "全部",
+ "es": "Todos",
+ "ru": "Все",
+ "ar": "الكل",
+ "fr": "Tous",
+ "ja": "すべて"
+ }
+ },
+ {
+ "key": "gui.document_panel.analysieren",
+ "canonical_source_value": "Analysieren...",
+ "translations": {
+ "de": "Analysieren...",
+ "pt": "Analisando...",
+ "zh": "分析中……",
+ "en": "Analyzing...",
+ "ru": "Идёт анализ...",
+ "fr": "Analyse en cours...",
+ "ja": "分析中...",
+ "es": "Analizando...",
+ "ar": "تحليل...",
+ "bn": "বিশ্লেষণ করুন...",
+ "hi": "विश्लेषण करें..."
+ }
+ },
+ {
+ "key": "gui.document_panel.dateien",
+ "canonical_source_value": "+ Dateien",
+ "translations": {
+ "de": "+ Dateien",
+ "ja": "+ ファイル",
+ "en": "+ Files",
+ "hi": "+ फ़ाइलें",
+ "ru": "+ Файлы",
+ "ar": "+ الملفات",
+ "zh": "+ 文件",
+ "bn": "+ ফাইল",
+ "fr": "+ Fichiers",
+ "pt": "+ Arquivos",
+ "es": "+ Archivos"
+ }
+ },
+ {
+ "key": "gui.document_panel.dateien_hinzufugen",
+ "canonical_source_value": "Dateien hinzufügen",
+ "translations": {
+ "de": "Dateien hinzufügen",
+ "ja": "ファイルを追加",
+ "fr": "Ajouter des fichiers",
+ "en": "Add files",
+ "zh": "添加文件",
+ "es": "Agregar archivos",
+ "hi": "फ़ाइलें जोड़ें",
+ "ar": "إضافة الملفات",
+ "pt": "Adicionar arquivos",
+ "ru": "Добавить файлы",
+ "bn": "ফাইল যোগ করুন"
+ }
+ },
+ {
+ "key": "gui.document_panel.detailrecherche",
+ "canonical_source_value": "Detailrecherche",
+ "translations": {
+ "de": "Detailrecherche",
+ "ru": "Подробное исследование",
+ "hi": "विस्तृत शोध",
+ "en": "Detailed research",
+ "pt": "Pesquisa detalhada",
+ "es": "Investigación detallada",
+ "zh": "详细研究",
+ "ja": "詳細調査",
+ "fr": "Recherche détaillée",
+ "bn": "বিস্তারিত গবেষণা",
+ "ar": "بحث تفصيلي"
+ }
+ },
+ {
+ "key": "gui.document_panel.dokument",
+ "canonical_source_value": "Dokument",
+ "translations": {
+ "de": "Dokument",
+ "hi": "दस्तावेज़",
+ "fr": "Document",
+ "en": "Document",
+ "pt": "Documento",
+ "bn": "নথি",
+ "ru": "Документ",
+ "es": "Documento",
+ "zh": "文档",
+ "ja": "ドキュメント",
+ "ar": "مستند"
+ }
+ },
+ {
+ "key": "gui.document_panel.entfernen",
+ "canonical_source_value": "Entfernen",
+ "translations": {
+ "de": "Entfernen",
+ "hi": "हटाएँ",
+ "zh": "移除",
+ "es": "Eliminar",
+ "ja": "削除",
+ "pt": "Remover",
+ "en": "Remove",
+ "ru": "Удалить",
+ "fr": "Supprimer",
+ "ar": "إزالة",
+ "bn": "সরান"
+ }
+ },
+ {
+ "key": "gui.document_panel.frage_stellen",
+ "canonical_source_value": "Frage stellen...",
+ "translations": {
+ "de": "Frage stellen...",
+ "zh": "问个问题...",
+ "ja": "質問する...",
+ "ru": "Задать вопрос...",
+ "fr": "Poser une question...",
+ "en": "Ask a question...",
+ "ar": "اطرح سؤالاً...",
+ "pt": "Fazer uma pergunta...",
+ "es": "Hacer una pregunta...",
+ "bn": "প্রশ্ন করুন...",
+ "hi": "प्रश्न पूछें..."
+ }
+ },
+ {
+ "key": "gui.document_panel.groe",
+ "canonical_source_value": "Größe",
+ "translations": {
+ "de": "Größe",
+ "en": "Size",
+ "hi": "आकार",
+ "ru": "Размер",
+ "ja": "サイズ",
+ "pt": "Tamanho",
+ "bn": "আকার",
+ "es": "Tamaño",
+ "zh": "大小",
+ "fr": "Taille",
+ "ar": "الحجم"
+ }
+ },
+ {
+ "key": "gui.document_panel.informationen_extrahieren",
+ "canonical_source_value": "Informationen extrahieren...",
+ "translations": {
+ "de": "Informationen extrahieren...",
+ "bn": "তথ্য নিষ্কাশন করা হচ্ছে...",
+ "ja": "情報を抽出しています...",
+ "zh": "正在提取信息...",
+ "hi": "जानकारी निकाली जा रही है...",
+ "pt": "Extraindo informações...",
+ "es": "Extrayendo información...",
+ "en": "Extracting information...",
+ "ru": "Извлечение информации...",
+ "ar": "جارٍ استخراج المعلومات...",
+ "fr": "Extraction des informations..."
+ }
+ },
+ {
+ "key": "gui.document_panel.keine",
+ "canonical_source_value": "Keine",
+ "translations": {
+ "de": "Keine",
+ "pt": "Keine",
+ "fr": "Aucun",
+ "ja": "なし",
+ "bn": "কোনোটি নয়",
+ "es": "Ninguna",
+ "ru": "Нет",
+ "en": "None",
+ "zh": "无",
+ "hi": "कोई नहीं",
+ "ar": "لا شيء"
+ }
+ },
+ {
+ "key": "gui.document_panel.keine_dokumente",
+ "canonical_source_value": "Keine Dokumente",
+ "translations": {
+ "de": "Keine Dokumente",
+ "ja": "ドキュメントはありません",
+ "ar": "لا توجد مستندات",
+ "es": "Sin documentos",
+ "bn": "কোনো নথি নেই",
+ "zh": "没有文档",
+ "hi": "कोई दस्तावेज़ नहीं",
+ "fr": "Aucun document",
+ "pt": "Nenhum documento",
+ "ru": "Нет документов",
+ "en": "No documents"
+ }
+ },
+ {
+ "key": "gui.document_panel.kernaussagen",
+ "canonical_source_value": "Kernaussagen",
+ "translations": {
+ "de": "Kernaussagen",
+ "zh": "核心要点",
+ "fr": "Points clés",
+ "bn": "মূল বক্তব্য",
+ "pt": "Pontos principais",
+ "es": "Ideas principales",
+ "ru": "Ключевые положения",
+ "en": "Key points",
+ "ar": "النقاط الرئيسية",
+ "hi": "मुख्य बिंदु",
+ "ja": "要点"
+ }
+ },
+ {
+ "key": "gui.document_panel.ordner",
+ "canonical_source_value": "+ Ordner",
+ "translations": {
+ "de": "+ Ordner",
+ "ru": "+ Папка",
+ "ja": "+ フォルダ",
+ "zh": "+ 文件夹",
+ "pt": "+ Pasta",
+ "bn": "+ ফোল্ডার",
+ "en": "+ Folder",
+ "es": "+ Carpeta",
+ "fr": "+ Dossier",
+ "ar": "+ مجلد",
+ "hi": "+ फ़ोल्डर"
+ }
+ },
+ {
+ "key": "gui.document_panel.ordner_hinzufugen",
+ "canonical_source_value": "Ordner hinzufügen",
+ "translations": {
+ "de": "Ordner hinzufügen",
+ "bn": "ফোল্ডার যোগ করুন",
+ "en": "Add folder",
+ "es": "Agregar carpeta",
+ "ru": "Добавить папку",
+ "zh": "添加文件夹",
+ "ar": "إضافة مجلد",
+ "pt": "Adicionar pasta",
+ "fr": "Ajouter un dossier",
+ "ja": "フォルダを追加",
+ "hi": "फ़ोल्डर जोड़ें"
+ }
+ },
+ {
+ "key": "gui.document_panel.personen_orte",
+ "canonical_source_value": "Personen/Orte",
+ "translations": {
+ "de": "Personen/Orte",
+ "fr": "Personnes/Lieux",
+ "zh": "人物/地点",
+ "hi": "लोग/स्थान",
+ "ja": "人物/場所",
+ "pt": "Pessoas/Lugares",
+ "ar": "الأشخاص/الأماكن",
+ "en": "People/Places",
+ "es": "Personas/Lugares",
+ "bn": "ব্যক্তি/স্থান",
+ "ru": "Персоны/Места"
+ }
+ },
+ {
+ "key": "gui.document_panel.schnellanalyse",
+ "canonical_source_value": "Schnellanalyse",
+ "translations": {
+ "de": "Schnellanalyse",
+ "ar": "تحليل سريع",
+ "bn": "দ্রুত বিশ্লেষণ",
+ "ru": "Быстрый анализ",
+ "zh": "快速分析",
+ "fr": "Analyse rapide",
+ "pt": "Análise rápida",
+ "ja": "クイック分析",
+ "es": "Análisis rápido",
+ "en": "Quick analysis",
+ "hi": "त्वरित विश्लेषण"
+ }
+ },
+ {
+ "key": "gui.document_panel.status",
+ "canonical_source_value": "Status",
+ "translations": {
+ "de": "Status",
+ "zh": "状态",
+ "bn": "অবস্থা",
+ "ru": "Статус",
+ "hi": "स्थिति",
+ "ja": "状態",
+ "pt": "Status",
+ "en": "Status",
+ "fr": "Statut",
+ "es": "Estado",
+ "ar": "الحالة"
+ }
+ },
+ {
+ "key": "gui.document_panel.value_von_value_ausgewahlt",
+ "canonical_source_value": "{value1} von {value2} ausgewählt",
+ "translations": {
+ "de": "{value1} von {value2} ausgewählt",
+ "hi": "{value2} में से {value1} चयनित",
+ "pt": "Selecionados: {value1} de {value2}",
+ "ar": "تم تحديد {value1} من أصل {value2}",
+ "zh": "{value2} 个中已选 {value1} 个",
+ "ja": "{value2} 個中 {value1} 個を選択済み",
+ "es": "Seleccionados: {value1} de {value2}",
+ "en": "Selected {value1} of {value2}",
+ "fr": "Sélection : {value1} sur {value2}",
+ "ru": "Выбрано {value1} из {value2}",
+ "bn": "{value1}টি {value2}টির মধ্যে নির্বাচিত"
+ }
+ },
+ {
+ "key": "gui.document_panel.zeitachse",
+ "canonical_source_value": "Zeitachse",
+ "translations": {
+ "de": "Zeitachse",
+ "en": "Timeline",
+ "pt": "Linha do tempo",
+ "zh": "时间轴",
+ "fr": "Chronologie",
+ "bn": "সময়রেখা",
+ "ar": "الجدول الزمني",
+ "ru": "Временная шкала",
+ "es": "Línea de tiempo",
+ "hi": "समयरेखा",
+ "ja": "タイムライン"
+ }
+ },
+ {
+ "key": "gui.document_panel.zusammenfassung_erstellen",
+ "canonical_source_value": "Zusammenfassung erstellen",
+ "translations": {
+ "de": "Zusammenfassung erstellen",
+ "ar": "إنشاء ملخص",
+ "ru": "Создать сводку",
+ "fr": "Créer un résumé",
+ "en": "Create summary",
+ "pt": "Criar resumo",
+ "hi": "सारांश बनाएँ",
+ "ja": "要約を作成",
+ "es": "Crear resumen",
+ "zh": "创建摘要",
+ "bn": "সারসংক্ষেপ তৈরি করুন"
+ }
+ },
+ {
+ "key": "gui.files.label.all_files",
+ "canonical_source_value": "Alle Dateien",
+ "translations": {
+ "de": "Alle Dateien",
+ "pt": "Todos os arquivos",
+ "hi": "सभी फ़ाइलें",
+ "ru": "Все файлы",
+ "bn": "সমস্ত ফাইল",
+ "es": "Todos los archivos",
+ "en": "All files",
+ "ar": "جميع الملفات",
+ "ja": "すべてのファイル",
+ "zh": "所有文件",
+ "fr": "Tous les fichiers"
+ }
+ },
+ {
+ "key": "gui.files.label.all_supported",
+ "canonical_source_value": "Alle unterstützten",
+ "translations": {
+ "de": "Alle unterstützten",
+ "hi": "सभी समर्थित प्रारूप",
+ "zh": "所有支持的格式",
+ "ru": "Все поддерживаемые форматы",
+ "es": "Todos los formatos compatibles",
+ "pt": "Todos os formatos compatíveis",
+ "fr": "Tous les formats pris en charge",
+ "en": "All supported",
+ "bn": "সমর্থিত সব ফরম্যাট",
+ "ar": "جميع الصيغ المدعومة",
+ "ja": "すべての対応形式"
+ }
+ },
+ {
+ "key": "gui.files.label.code",
+ "canonical_source_value": "Code",
+ "translations": {
+ "de": "Code",
+ "ar": "الشيفرة",
+ "en": "Code",
+ "ru": "Код",
+ "es": "Código",
+ "fr": "Code",
+ "ja": "コード",
+ "bn": "কোড",
+ "hi": "कोड",
+ "zh": "代码",
+ "pt": "Código"
+ }
+ },
+ {
+ "key": "gui.files.label.documents",
+ "canonical_source_value": "Dokumente",
+ "translations": {
+ "de": "Dokumente",
+ "bn": "ডকুমেন্টসমূহ",
+ "en": "Documents",
+ "pt": "Documentos",
+ "hi": "दस्तावेज़",
+ "zh": "文档",
+ "ja": "ドキュメント",
+ "ru": "Документы",
+ "es": "Documentos",
+ "fr": "Documents",
+ "ar": "المستندات"
+ }
+ },
+ {
+ "key": "gui.files.label.html",
+ "canonical_source_value": "HTML",
+ "translations": {
+ "de": "HTML",
+ "hi": "HTML",
+ "ru": "HTML",
+ "es": "HTML",
+ "pt": "HTML",
+ "zh": "HTML",
+ "fr": "HTML",
+ "en": "HTML",
+ "bn": "HTML",
+ "ja": "HTML",
+ "ar": "HTML"
+ }
+ },
+ {
+ "key": "gui.files.label.json",
+ "canonical_source_value": "JSON",
+ "translations": {
+ "de": "JSON",
+ "bn": "JSON",
+ "es": "JSON",
+ "fr": "JSON",
+ "zh": "JSON",
+ "ru": "JSON",
+ "pt": "JSON",
+ "hi": "JSON",
+ "en": "JSON",
+ "ar": "JSON",
+ "ja": "JSON"
+ }
+ },
+ {
+ "key": "gui.files.label.markdown",
+ "canonical_source_value": "Markdown",
+ "translations": {
+ "de": "Markdown",
+ "zh": "Markdown",
+ "ru": "Markdown",
+ "en": "Markdown",
+ "ja": "Markdown",
+ "pt": "Markdown",
+ "bn": "Markdown",
+ "fr": "Markdown",
+ "es": "Markdown",
+ "hi": "Markdown",
+ "ar": "Markdown"
+ }
+ },
+ {
+ "key": "gui.files.label.pdf",
+ "canonical_source_value": "PDF",
+ "translations": {
+ "de": "PDF",
+ "ru": "PDF",
+ "bn": "PDF",
+ "pt": "PDF",
+ "es": "PDF",
+ "ja": "PDF",
+ "zh": "PDF",
+ "hi": "PDF",
+ "en": "PDF",
+ "fr": "PDF",
+ "ar": "PDF"
+ }
+ },
+ {
+ "key": "gui.files.label.spreadsheets",
+ "canonical_source_value": "Tabellen",
+ "translations": {
+ "de": "Tabellen",
+ "bn": "স্প্রেডশিট",
+ "pt": "Planilhas",
+ "en": "Spreadsheets",
+ "fr": "Feuilles de calcul",
+ "es": "Hojas de cálculo",
+ "zh": "电子表格",
+ "hi": "स्प्रेडशीट",
+ "ja": "スプレッドシート",
+ "ru": "Электронные таблицы",
+ "ar": "جداول البيانات"
+ }
+ },
+ {
+ "key": "gui.files.label.text",
+ "canonical_source_value": "Text",
+ "translations": {
+ "de": "Text",
+ "ja": "テキスト",
+ "pt": "Texto",
+ "zh": "文本",
+ "bn": "পাঠ্য",
+ "hi": "पाठ",
+ "es": "Texto",
+ "en": "Text",
+ "fr": "Texte",
+ "ru": "Текст",
+ "ar": "نص"
+ }
+ },
+ {
+ "key": "gui.files.label.word",
+ "canonical_source_value": "Word",
+ "translations": {
+ "de": "Word",
+ "pt": "Word",
+ "hi": "Word",
+ "fr": "Word",
+ "zh": "Word",
+ "ja": "Word",
+ "es": "Word",
+ "ar": "Word",
+ "en": "Word",
+ "ru": "Word",
+ "bn": "Word"
+ }
+ },
+ {
+ "key": "gui.main_window.aktuelle_einstellungen",
+ "canonical_source_value": "(Aktuelle Einstellungen)",
+ "translations": {
+ "de": "(Aktuelle Einstellungen)",
+ "ar": "(الإعدادات الحالية)",
+ "ja": "(現在の設定)",
+ "bn": "(বর্তমান সেটিংস)",
+ "hi": "(वर्तमान सेटिंग्स)",
+ "es": "(Configuración actual)",
+ "fr": "(Paramètres actuels)",
+ "en": "(Current settings)",
+ "pt": "(Configurações atuais)",
+ "zh": "(当前设置)",
+ "ru": "(Текущие настройки)"
+ }
+ },
+ {
+ "key": "gui.main_window.aktuelle_pipeline_phase_keine_dokumente_dateien_hinzuf",
+ "canonical_source_value": "Aktuelle Pipeline-Phase:\n📄 Keine Dokumente → Dateien hinzufügen\n🔤 Dokumente geladen → Text extrahieren (Schritt 1)\n✅ Extrahiert → Analysieren (opt.) oder Bericht erstellen (Schritt 3)\n🎯 Bereit → Bericht erstellen",
+ "translations": {
+ "de": "Aktuelle Pipeline-Phase:\n📄 Keine Dokumente → Dateien hinzufügen\n🔤 Dokumente geladen → Text extrahieren (Schritt 1)\n✅ Extrahiert → Analysieren (opt.) oder Bericht erstellen (Schritt 3)\n🎯 Bereit → Bericht erstellen",
+ "ar": "مرحلة خط المعالجة الحالية:\n📄 لا توجد مستندات ← إضافة الملفات\n🔤 تم تحميل المستندات ← استخراج النص (الخطوة 1)\n✅ تم الاستخراج ← التحليل (اختياري) أو إنشاء التقرير (الخطوة 3)\n🎯 جاهز ← إنشاء التقرير",
+ "zh": "当前流水线阶段:\n📄 无文档 → 添加文件\n🔤 已加载文档 → 提取文本(步骤 1)\n✅ 已提取 → 分析(可选)或生成报告(步骤 3)\n🎯 就绪 → 生成报告",
+ "en": "Current pipeline phase:\n📄 No documents → Add files\n🔤 Documents loaded → Extract text (step 1)\n✅ Extracted → Analyze (opt.) or create report (step 3)\n🎯 Ready → Create report",
+ "es": "Fase actual del pipeline:\n📄 Sin documentos → Añadir archivos\n🔤 Documentos cargados → Extraer texto (paso 1)\n✅ Extraído → Analizar (opc.) o generar informe (paso 3)\n🎯 Listo → Generar informe",
+ "ru": "Текущая фаза конвейера:\n📄 Нет документов → добавить файлы\n🔤 Документы загружены → извлечь текст (шаг 1)\n✅ Извлечено → анализировать (опц.) или создать отчёт (шаг 3)\n🎯 Готово → создать отчёт",
+ "bn": "বর্তমান পাইপলাইন পর্যায়:\n📄 কোনো নথি নেই → ফাইল যোগ করুন\n🔤 নথি লোড হয়েছে → টেক্সট নিষ্কাশন করুন (ধাপ 1)\n✅ নিষ্কাশিত → বিশ্লেষণ করুন (ঐচ্ছিক) বা প্রতিবেদন তৈরি করুন (ধাপ 3)\n🎯 প্রস্তুত → প্রতিবেদন তৈরি করুন",
+ "fr": "Phase actuelle du pipeline :\n📄 Aucun document → Ajouter des fichiers\n🔤 Documents chargés → Extraire le texte (étape 1)\n✅ Extrait → Analyser (facultatif) ou générer le rapport (étape 3)\n🎯 Prêt → Générer le rapport",
+ "pt": "Fase atual do pipeline:\n📄 Nenhum documento → Adicionar arquivos\n🔤 Documentos carregados → Extrair texto (Passo 1)\n✅ Extraído → Analisar (opc.) ou gerar relatório (Passo 3)\n🎯 Pronto → Gerar relatório",
+ "hi": "वर्तमान पाइपलाइन चरण:\n📄 कोई दस्तावेज़ नहीं → फ़ाइलें जोड़ें\n🔤 दस्तावेज़ लोड हुए → टेक्स्ट निकालें (चरण 1)\n✅ निकाला गया → विश्लेषण करें (वैकल्पिक) या रिपोर्ट बनाएँ (चरण 3)\n🎯 तैयार → रिपोर्ट बनाएँ",
+ "ja": "現在のパイプラインフェーズ:\n📄 ドキュメントなし → ファイルを追加\n🔤 ドキュメント読み込み済み → テキストを抽出(ステップ1)\n✅ 抽出済み → 分析(任意)またはレポートを作成(ステップ3)\n🎯 準備完了 → レポートを作成"
+ }
+ },
+ {
+ "key": "gui.main_window.alle_abwahlen",
+ "canonical_source_value": "Alle abwählen",
+ "translations": {
+ "de": "Alle abwählen",
+ "ja": "すべて選択解除",
+ "fr": "Tout désélectionner",
+ "es": "Desmarcar todo",
+ "en": "Deselect all",
+ "pt": "Desmarcar tudo",
+ "zh": "全部取消选择",
+ "ru": "Снять все",
+ "bn": "সব নির্বাচন বাতিল করুন",
+ "ar": "إلغاء تحديد الكل",
+ "hi": "सभी का चयन हटाएँ"
+ }
+ },
+ {
+ "key": "gui.main_window.alle_auswahlen",
+ "canonical_source_value": "Alle auswählen",
+ "translations": {
+ "de": "Alle auswählen",
+ "pt": "Selecionar tudo",
+ "ja": "すべて選択",
+ "en": "Select all",
+ "zh": "全部选择",
+ "ru": "Выбрать все",
+ "ar": "اختر الكل",
+ "es": "Seleccionar todo",
+ "fr": "Tout sélectionner",
+ "hi": "सभी चुनें",
+ "bn": "সব নির্বাচন করুন"
+ }
+ },
+ {
+ "key": "gui.main_window.alle_dokumente_indexieren",
+ "canonical_source_value": "Alle Dokumente indexieren",
+ "translations": {
+ "de": "Alle Dokumente indexieren",
+ "bn": "Alle Dokumente indexieren → সমস্ত নথি সূচীভুক্ত করুন",
+ "hi": "सभी दस्तावेज़ अनुक्रमित करें",
+ "zh": "索引所有文档",
+ "es": "Indexar todos los documentos",
+ "ru": "Индексировать все документы",
+ "fr": "Indexer tous les documents",
+ "ar": "فهرسة جميع المستندات",
+ "pt": "Indexar todos os documentos",
+ "en": "Index all documents",
+ "ja": "すべての文書をインデックス化"
+ }
+ },
+ {
+ "key": "gui.main_window.alte_klartext_schlussel_migrieren",
+ "canonical_source_value": "Alte Klartext-Schlüssel migrieren…",
+ "translations": {
+ "de": "Alte Klartext-Schlüssel migrieren…",
+ "pt": "Migrar chaves de texto simples antigas…",
+ "bn": "পুরোনো প্লেইন-টেক্সট কী-গুলো স্থানান্তর করা হচ্ছে…",
+ "en": "Migrating old plain-text keys…",
+ "ar": "ترحيل مفاتيح النص الواضح القديمة…",
+ "fr": "Migration des anciennes clés en texte clair…",
+ "ru": "Миграция старых ключей в открытом тексте…",
+ "zh": "将旧版明文密钥迁移…",
+ "hi": "सादी-पाठ वाली पुरानी कुंजियाँ माइग्रेट करें…",
+ "es": "Migrar las claves de texto plano antiguas…",
+ "ja": "古い平文キーを移行…"
+ }
+ },
+ {
+ "key": "gui.main_window.alte_schlussel_migrieren",
+ "canonical_source_value": "Alte Schlüssel migrieren",
+ "translations": {
+ "de": "Alte Schlüssel migrieren",
+ "pt": "Migrar chaves antigas",
+ "ja": "古い鍵を移行する",
+ "ru": "Перенести старые ключи",
+ "ar": "نقل المفاتيح القديمة",
+ "fr": "Migrer les anciennes clés",
+ "es": "Migrar claves antiguas",
+ "hi": "पुरानी कुंजियों को स्थानांतरित करें",
+ "zh": "迁移旧密钥",
+ "en": "Migrate old keys",
+ "bn": "পুরোনো কী স্থানান্তর করুন"
+ }
+ },
+ {
+ "key": "gui.main_window.analyse",
+ "canonical_source_value": "Analyse",
+ "translations": {
+ "de": "Analyse",
+ "fr": "Analyse",
+ "hi": "विश्लेषण",
+ "en": "Analyse",
+ "bn": "বিশ্লেষণ",
+ "pt": "Análise",
+ "ru": "Анализ",
+ "ar": "تحليل",
+ "zh": "分析",
+ "es": "Analizar",
+ "ja": "分析"
+ }
+ },
+ {
+ "key": "gui.main_window.analyse_lauft_bereits",
+ "canonical_source_value": "Analyse läuft bereits...",
+ "translations": {
+ "de": "Analyse läuft bereits...",
+ "ja": "分析はすでに実行中です...",
+ "ar": "التحليل قيد التشغيل بالفعل...",
+ "pt": "A análise já está em andamento...",
+ "en": "Analysis is already running...",
+ "zh": "分析已在运行中...",
+ "fr": "L'analyse est déjà en cours...",
+ "es": "Análisis ya en curso...",
+ "bn": "বিশ্লেষণ ইতিমধ্যে চলছে...",
+ "ru": "Анализ уже выполняется...",
+ "hi": "विश्लेषण पहले से चल रहा है..."
+ }
+ },
+ {
+ "key": "gui.main_window.analyse_pipeline",
+ "canonical_source_value": "Analyse-Pipeline",
+ "translations": {
+ "de": "Analyse-Pipeline",
+ "fr": "Pipeline d'analyse",
+ "pt": "Pipeline de análise",
+ "zh": "分析流水线",
+ "ja": "分析パイプライン",
+ "en": "Analysis pipeline",
+ "es": "Pipeline de análisis",
+ "ru": "Конвейер анализа",
+ "ar": "خط أنابيب التحليل",
+ "bn": "বিশ্লেষণ পাইপলাইন",
+ "hi": "विश्लेषण पाइपलाइन"
+ }
+ },
+ {
+ "key": "gui.main_window.analysen_abgeschlossen_value",
+ "canonical_source_value": "Analysen abgeschlossen: {value1}",
+ "translations": {
+ "de": "Analysen abgeschlossen: {value1}",
+ "en": "Analyses completed: {value1}",
+ "ru": "Анализы завершены: {value1}",
+ "bn": "বিশ্লেষণ সম্পন্ন হয়েছে: {value1}",
+ "ar": "تم الانتهاء من التحليلات: {value1}",
+ "es": "Análisis completado: {value1}",
+ "hi": "विश्लेषण पूर्ण हुए: {value1}",
+ "ja": "分析完了: {value1}",
+ "pt": "Análises concluídas: {value1}",
+ "fr": "Analyses terminées : {value1}",
+ "zh": "分析已完成:{value1}"
+ }
+ },
+ {
+ "key": "gui.main_window.analysieren",
+ "canonical_source_value": "Analysieren",
+ "translations": {
+ "de": "Analysieren",
+ "zh": "分析",
+ "pt": "Analisar",
+ "ja": "分析する",
+ "en": "Analyze",
+ "fr": "Analyser",
+ "es": "Analizar",
+ "bn": "বিশ্লেষণ করুন",
+ "ru": "Анализировать",
+ "ar": "حلّل",
+ "hi": "विश्लेषण करें"
+ }
+ },
+ {
+ "key": "gui.main_window.api_hintergrund_ergebnis_zuruck_in_app_chat_konsole_of",
+ "canonical_source_value": "api = Hintergrund (Ergebnis zurück in App)\nchat = Konsole öffnet sich zum Weiter-Chatten",
+ "translations": {
+ "de": "api = Hintergrund (Ergebnis zurück in App)\nchat = Konsole öffnet sich zum Weiter-Chatten",
+ "pt": "api = Segundo plano (resultado devolvido ao aplicativo)\nchat = O console abre para continuar a conversa",
+ "ja": "api = バックグラウンド(結果をアプリに返す)\nchat = チャットを続けるためにコンソールが開く",
+ "bn": "api = পটভূমি (ফলাফল অ্যাপে ফিরে যায়)\nchat = কথোপকথন চালিয়ে যাওয়ার জন্য কনসোল খোলে",
+ "hi": "api = पृष्ठभूमि (परिणाम ऐप में वापस)\nchat = चैट जारी रखने के लिए कंसोल खुलता है",
+ "ru": "api = Фон (результат возвращается в приложение)\nchat = Консоль открывается для продолжения чата",
+ "fr": "api = Arrière-plan (résultat renvoyé dans l'application)\nchat = La console s'ouvre pour poursuivre la conversation",
+ "ar": "api = الخلفية (النتيجة تعود إلى التطبيق)\nchat = تفتح وحدة التحكم للمتابعة في الدردشة",
+ "zh": "api = 后台(结果返回应用)\nchat = 控制台打开以继续聊天",
+ "es": "api = en segundo plano (el resultado vuelve a la aplicación)\nchat = la consola se abre para continuar la conversación",
+ "en": "api = background (result returned to app)\nchat = console opens for continued conversation"
+ }
+ },
+ {
+ "key": "gui.main_window.api_key",
+ "canonical_source_value": "API-Key:",
+ "translations": {
+ "de": "API-Key:",
+ "ja": "API キー:",
+ "fr": "Clé API :",
+ "bn": "API কী:",
+ "es": "Clave de API:",
+ "ru": "Ключ API:",
+ "hi": "API कुंजी:",
+ "pt": "Chave de API:",
+ "ar": "مفتاح API:",
+ "zh": "API 密钥:",
+ "en": "API-Key:"
+ }
+ },
+ {
+ "key": "gui.main_window.ausgewahlte_indexieren",
+ "canonical_source_value": "Ausgewählte indexieren",
+ "translations": {
+ "de": "Ausgewählte indexieren",
+ "ru": "Индексировать выбранное",
+ "en": "Index selected",
+ "ar": "فهرسة المحدد",
+ "fr": "Indexer les éléments sélectionnés",
+ "es": "Indexar seleccionados",
+ "pt": "Indexar selecionados",
+ "zh": "为所选项目建立索引",
+ "bn": "নির্বাচিত আইটেম সূচিবদ্ধ করুন",
+ "ja": "選択した項目にインデックスを付ける",
+ "hi": "चयनित वस्तुओं को अनुक्रमित करें"
+ }
+ },
+ {
+ "key": "gui.main_window.basis_dateiname_ohne_erweiterung",
+ "canonical_source_value": "Basis-Dateiname (ohne Erweiterung):",
+ "translations": {
+ "de": "Basis-Dateiname (ohne Erweiterung):",
+ "fr": "Nom de fichier de base (sans extension) :",
+ "es": "Nombre de archivo base (sin extensión):",
+ "zh": "基础文件名(不含扩展名):",
+ "ru": "Базовое имя файла (без расширения):",
+ "en": "Base file name (without extension):",
+ "pt": "Nome base do arquivo (sem extensão):",
+ "hi": "बेस फ़ाइल नाम (एक्सटेंशन के बिना):",
+ "bn": "মূল ফাইলের নাম (এক্সটেনশন ছাড়া):",
+ "ar": "اسم الملف الأساسي (بدون امتداد):",
+ "ja": "ベースファイル名(拡張子なし):"
+ }
+ },
+ {
+ "key": "gui.main_window.bearbeiten",
+ "canonical_source_value": "&Bearbeiten",
+ "translations": {
+ "de": "&Bearbeiten",
+ "en": "&Edit",
+ "es": "&Editar",
+ "hi": "&संपादित करें",
+ "fr": "&Modifier",
+ "pt": "&Editar",
+ "ru": "&Редактировать",
+ "zh": "&编辑",
+ "bn": "&সম্পাদনা",
+ "ar": "&تحرير",
+ "ja": "&編集"
+ }
+ },
+ {
+ "key": "gui.main_window.beenden",
+ "canonical_source_value": "&Beenden",
+ "translations": {
+ "de": "&Beenden",
+ "ru": "&Выход",
+ "en": "&Exit",
+ "pt": "&Sair",
+ "ar": "&إنهاء",
+ "ja": "&終了",
+ "fr": "&Quitter",
+ "zh": "&退出",
+ "es": "&Salir",
+ "bn": "&বন্ধ করুন",
+ "hi": "&बंद करें"
+ }
+ },
+ {
+ "key": "gui.main_window.bereit_rag_nicht_aktiv_kein_ollama_provider",
+ "canonical_source_value": "Bereit (RAG nicht aktiv — kein Ollama-Provider)",
+ "translations": {
+ "de": "Bereit (RAG nicht aktiv — kein Ollama-Provider)",
+ "zh": "就绪(RAG 未激活 — 没有 Ollama 提供方)",
+ "hi": "तैयार (RAG सक्रिय नहीं — कोई Ollama प्रदाता नहीं)",
+ "pt": "Pronto (RAG não está ativo — nenhum provedor Ollama)",
+ "fr": "Prêt (RAG non actif — aucun fournisseur Ollama)",
+ "ar": "جاهز (RAG غير نشط — لا يوجد موفر Ollama)",
+ "ru": "Готов (RAG не активен — нет провайдера Ollama)",
+ "en": "Ready (RAG not active — no Ollama provider)",
+ "ja": "準備完了(RAG は有効ではありません — Ollama プロバイダーがありません)",
+ "bn": "তৈরি (RAG সক্রিয় নয় — কোনো Ollama প্রদানকারী নেই)",
+ "es": "Listo (RAG no activo — sin proveedor de Ollama)"
+ }
+ },
+ {
+ "key": "gui.main_window.bericht",
+ "canonical_source_value": "Bericht",
+ "translations": {
+ "de": "Bericht",
+ "hi": "रिपोर्ट",
+ "en": "Report",
+ "ja": "レポート",
+ "pt": "Relatório",
+ "fr": "Rapport",
+ "ar": "تقرير",
+ "zh": "报告",
+ "es": "Informe",
+ "ru": "Отчёт",
+ "bn": "প্রতিবেদন"
+ }
+ },
+ {
+ "key": "gui.main_window.bericht_erfolgreich_erstellt",
+ "canonical_source_value": "Bericht erfolgreich erstellt",
+ "translations": {
+ "de": "Bericht erfolgreich erstellt",
+ "en": "Report created successfully",
+ "es": "Informe creado correctamente",
+ "pt": "Relatório criado com sucesso",
+ "ja": "レポートが正常に作成されました",
+ "zh": "报告已成功生成",
+ "ar": "تم إنشاء التقرير بنجاح",
+ "hi": "रिपोर्ट सफलतापूर्वक बनाई गई",
+ "fr": "Rapport créé avec succès",
+ "ru": "Отчёт успешно создан",
+ "bn": "রিপোর্ট সফলভাবে তৈরি হয়েছে"
+ }
+ },
+ {
+ "key": "gui.main_window.bericht_erstellen",
+ "canonical_source_value": "Bericht erstellen",
+ "translations": {
+ "de": "Bericht erstellen",
+ "en": "Create report",
+ "es": "Crear informe",
+ "pt": "Criar relatório",
+ "ja": "レポートを作成",
+ "fr": "Créer un rapport",
+ "ar": "إنشاء تقرير",
+ "ru": "Создать отчёт",
+ "bn": "প্রতিবেদন তৈরি করুন",
+ "hi": "रिपोर्ट बनाएँ",
+ "zh": "生成报告"
+ }
+ },
+ {
+ "key": "gui.main_window.bericht_exportieren",
+ "canonical_source_value": "Bericht exportieren",
+ "translations": {
+ "de": "Bericht exportieren",
+ "ja": "エクスポートレポート",
+ "en": "Export report",
+ "es": "Exportar informe",
+ "ar": "تصدير التقرير",
+ "pt": "Exportar relatório",
+ "ru": "Экспорт отчёта",
+ "fr": "Exporter le rapport",
+ "zh": "导出报告",
+ "hi": "रिपोर्ट निर्यात करें",
+ "bn": "প্রতিবেদন রপ্তানি করুন"
+ }
+ },
+ {
+ "key": "gui.main_window.bericht_wird_vorbereitet",
+ "canonical_source_value": "Bericht wird vorbereitet...",
+ "translations": {
+ "de": "Bericht wird vorbereitet...",
+ "bn": "রিপোর্ট প্রস্তুত করা হচ্ছে...",
+ "ru": "Отчёт готовится...",
+ "hi": "रिपोर्ट तैयार की जा रही है...",
+ "pt": "Relatório em preparação...",
+ "ar": "جارٍ إعداد التقرير...",
+ "ja": "レポートを作成中です...",
+ "es": "El informe se está preparando...",
+ "fr": "Le rapport est en cours de préparation...",
+ "zh": "报告正在准备中...",
+ "en": "Report is being prepared..."
+ }
+ },
+ {
+ "key": "gui.main_window.claude_code_cli_gefunden",
+ "canonical_source_value": "Claude Code CLI gefunden",
+ "translations": {
+ "de": "Claude Code CLI gefunden",
+ "zh": "Claude Code CLI 已找到",
+ "es": "Claude Code CLI encontrado",
+ "bn": "Claude Code CLI খুঁজে পাওয়া গেছে",
+ "fr": "Claude Code CLI trouvé",
+ "pt": "Claude Code CLI encontrado",
+ "ja": "Claude Code CLI が見つかりました",
+ "en": "Claude Code CLI found",
+ "hi": "Claude Code CLI मिल गया",
+ "ru": "Claude Code CLI найден",
+ "ar": "تم العثور على Claude Code CLI"
+ }
+ },
+ {
+ "key": "gui.main_window.claude_code_cli_nicht_gefunden",
+ "canonical_source_value": "Claude Code CLI NICHT gefunden!",
+ "translations": {
+ "de": "Claude Code CLI NICHT gefunden!",
+ "ja": "Claude Code CLI が見つかりません!",
+ "fr": "Claude Code CLI introuvable !",
+ "en": "Claude Code CLI was not found!",
+ "ru": "Claude Code CLI не найден!",
+ "es": "¡No se encontró Claude Code CLI!",
+ "pt": "Claude Code CLI não foi encontrado!",
+ "bn": "Claude Code CLI পাওয়া যায়নি!",
+ "zh": "未找到 Claude Code CLI!",
+ "ar": "لم يتم العثور على Claude Code CLI!",
+ "hi": "Claude Code CLI नहीं मिला!"
+ }
+ },
+ {
+ "key": "gui.main_window.claude_code_modus",
+ "canonical_source_value": "Claude Code Modus:",
+ "translations": {
+ "de": "Claude Code Modus:",
+ "ar": "وضع Claude Code:",
+ "bn": "Claude Code মোড:",
+ "pt": "Modo Claude Code:",
+ "es": "Modo de Claude Code:",
+ "fr": "Mode Claude Code :",
+ "ru": "Режим Claude Code:",
+ "en": "Claude Code mode:",
+ "zh": "Claude Code 模式:",
+ "ja": "Claude Code モード:",
+ "hi": "Claude Code मोड:"
+ }
+ },
+ {
+ "key": "gui.main_window.datei",
+ "canonical_source_value": "&Datei",
+ "translations": {
+ "de": "&Datei",
+ "es": "&Archivo",
+ "pt": "&Arquivo",
+ "en": "&File",
+ "hi": "&फ़ाइल",
+ "fr": "&Fichier",
+ "ar": "&ملف",
+ "zh": "&文件",
+ "ru": "&Файл",
+ "bn": "&ফাইল",
+ "ja": "&ファイル"
+ }
+ },
+ {
+ "key": "gui.main_window.dateien_hinzufugen",
+ "canonical_source_value": "Dateien hinzufügen...",
+ "translations": {
+ "de": "Dateien hinzufügen...",
+ "ar": "إضافة الملفات...",
+ "en": "Add files...",
+ "fr": "Ajouter des fichiers...",
+ "ja": "ファイルを追加...",
+ "ru": "Добавить файлы...",
+ "es": "Agregar archivos...",
+ "zh": "添加文件...",
+ "pt": "Adicionar arquivos...",
+ "hi": "फ़ाइलें जोड़ें...",
+ "bn": "ফাইল যোগ করুন..."
+ }
+ },
+ {
+ "key": "gui.main_window.detailrecherche_hinzugefugt_value",
+ "canonical_source_value": "Detailrecherche hinzugefügt: {value1}...",
+ "translations": {
+ "de": "Detailrecherche hinzugefügt: {value1}...",
+ "ar": "تمت إضافة بحث تفصيلي: {value1}...",
+ "pt": "Detalhe da pesquisa adicionado: {value1}...",
+ "hi": "विस्तृत शोध जोड़ा गया: {value1}...",
+ "ja": "詳細調査を追加しました: {value1}...",
+ "en": "Detailed research added: {value1}...",
+ "bn": "বিস্তারিত গবেষণা যোগ হয়েছে: {value1}...",
+ "es": "Investigación detallada añadida: {value1}...",
+ "zh": "已添加详细调研:{value1}...",
+ "fr": "Recherche détaillée ajoutée : {value1}...",
+ "ru": "Детализация исследования добавлена: {value1}..."
+ }
+ },
+ {
+ "key": "gui.main_window.die_bisherige_konfiguration_wurde_beibehalten_value",
+ "canonical_source_value": "Die bisherige Konfiguration wurde beibehalten. {value1}",
+ "translations": {
+ "de": "Die bisherige Konfiguration wurde beibehalten. {value1}",
+ "ar": "تم الاحتفاظ بالإعداد السابق. {value1}",
+ "pt": "A configuração anterior foi mantida. {value1}",
+ "ja": "以前の設定は維持されました。 {value1}",
+ "fr": "La configuration précédente a été conservée. {value1}",
+ "bn": "পূর্ববর্তী কনফিগারেশন অক্ষুণ্ণ রাখা হয়েছে। {value1}",
+ "ru": "Прежняя конфигурация была сохранена. {value1}",
+ "es": "La configuración anterior se ha conservado. {value1}",
+ "hi": "पिछली कॉन्फ़िगरेशन बनाए रखी गई है। {value1}",
+ "en": "The previous configuration was retained. {value1}",
+ "zh": "先前的配置已被保留。{value1}"
+ }
+ },
+ {
+ "key": "gui.main_window.die_konfigurationsdatei_wurde_nicht_geandert_value",
+ "canonical_source_value": "Die Konfigurationsdatei wurde nicht geändert. {value1}",
+ "translations": {
+ "de": "Die Konfigurationsdatei wurde nicht geändert. {value1}",
+ "zh": "配置文件未被修改。{value1}",
+ "en": "The configuration file has not been changed. {value1}",
+ "es": "El archivo de configuración no se ha modificado. {value1}",
+ "ru": "Конфигурационный файл не был изменён. {value1}",
+ "pt": "O arquivo de configuração não foi alterado. {value1}",
+ "hi": "कॉन्फ़िगरेशन फ़ाइल नहीं बदली गई। {value1}",
+ "bn": "কনফিগারেশন ফাইলটি পরিবর্তন করা হয়নি। {value1}",
+ "ar": "لم يتم تغيير ملف الإعدادات. {value1}",
+ "ja": "構成ファイルは変更されていません。{value1}",
+ "fr": "Le fichier de configuration n'a pas été modifié. {value1}"
+ }
+ },
+ {
+ "key": "gui.main_window.dokumente_hinzufugen_unterstutzte_formate_pdf_docx_txt",
+ "canonical_source_value": "Dokumente hinzufügen\nUnterstützte Formate: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "translations": {
+ "de": "Dokumente hinzufügen\nUnterstützte Formate: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "en": "Add documents\nSupported formats: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "hi": "दस्तावेज़ जोड़ें\nसमर्थित प्रारूप: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "es": "Añadir documentos\nFormatos compatibles: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "zh": "添加文档\n支持的格式:PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "fr": "Ajouter des documents\nFormats pris en charge : PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "pt": "Adicionar documentos\nFormatos suportados: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "ru": "Добавить документы\nПоддерживаемые форматы: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "ja": "文書を追加\n対応形式: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "bn": "নথি যোগ করুন\nসমর্থিত ফরম্যাট: PDF, DOCX, TXT, MD, XLSX, PPTX, ...",
+ "ar": "إضافة المستندات\nالصيغ المدعومة: PDF, DOCX, TXT, MD, XLSX, PPTX, ..."
+ }
+ },
+ {
+ "key": "gui.main_window.ein_bericht_wird_bereits_erstellt_bitte_warten",
+ "canonical_source_value": "Ein Bericht wird bereits erstellt. Bitte warten.",
+ "translations": {
+ "de": "Ein Bericht wird bereits erstellt. Bitte warten.",
+ "fr": "Un rapport est déjà en cours de génération. Veuillez patienter.",
+ "ar": "يجري بالفعل إنشاء تقرير. يُرجى الانتظار.",
+ "hi": "एक रिपोर्ट पहले से तैयार की जा रही है। कृपया प्रतीक्षा करें।",
+ "ja": "レポートはすでに作成中です。お待ちください。",
+ "en": "A report is already being generated. Please wait.",
+ "es": "Ya se está generando un informe. Por favor, espere.",
+ "zh": "正在生成报告,请稍候。",
+ "ru": "Отчёт уже создаётся. Пожалуйста, подождите.",
+ "pt": "Um relatório já está sendo gerado. Por favor, aguarde.",
+ "bn": "একটি প্রতিবেদন ইতিমধ্যে তৈরি হচ্ছে। অনুগ্রহ করে অপেক্ষা করুন।"
+ }
+ },
+ {
+ "key": "gui.main_window.embedding_modell",
+ "canonical_source_value": "Embedding-Modell:",
+ "translations": {
+ "de": "Embedding-Modell:",
+ "bn": "এম্বেডিং মডেল:",
+ "zh": "嵌入模型:",
+ "en": "Embedding model:",
+ "ja": "埋め込みモデル:",
+ "pt": "Modelo de embedding:",
+ "ar": "نموذج التضمين:",
+ "ru": "Модель для встраивания:",
+ "hi": "एम्बेडिंग मॉडल:",
+ "fr": "Modèle d'embedding :",
+ "es": "Modelo de embeddings:"
+ }
+ },
+ {
+ "key": "gui.main_window.erfolgreich_exportiert_value_enthalt_metadaten_zu_valu",
+ "canonical_source_value": "Erfolgreich exportiert:\n{value1}\n\nEnthält: Metadaten zu {value2} Dokument(en), Bericht, Chat und ausgewählte Auszüge.\nDer Export ist nicht anonymisiert. Gespeicherte API-Schlüsselfelder und vollständige Rohdokumente wurden nicht exportiert. Prüfe Bericht und Chat vor dem Weitergeben auf Geheimnisse.",
+ "translations": {
+ "de": "Erfolgreich exportiert:\n{value1}\n\nEnthält: Metadaten zu {value2} Dokument(en), Bericht, Chat und ausgewählte Auszüge.\nDer Export ist nicht anonymisiert. Gespeicherte API-Schlüsselfelder und vollständige Rohdokumente wurden nicht exportiert. Prüfe Bericht und Chat vor dem Weitergeben auf Geheimnisse.",
+ "bn": "সফলভাবে রপ্তানি করা হয়েছে:\n{value1}\n\nএতে রয়েছে: {value2}টি নথি, প্রতিবেদন, চ্যাট এবং নির্বাচিত উদ্ধৃতাংশের মেটাডেটা।\nরপ্তানিটি বেনামী করা হয়নি। সংরক্ষিত API কী-সংক্রান্ত ক্ষেত্র এবং সম্পূর্ণ মূল নথি রপ্তানি করা হয়নি। শেয়ার করার আগে প্রতিবেদন ও চ্যাটে গোপন তথ্য আছে কি না তা দেখে নিন。",
+ "es": "Exportado correctamente:\n{value1}\n\nContiene: metadatos de {value2} documento(s), informe, chat y extractos seleccionados.\nLa exportación no está anonimizada. Los campos clave almacenados de API y los documentos sin procesar completos no se han exportado. Revisa el informe y el chat en busca de secretos antes de compartirlos.",
+ "ar": "تم التصدير بنجاح:\n{value1}\n\nيتضمن: بيانات وصفية عن {value2} مستند (مستندات)، والتقرير والمحادثة والمقتطفات المحددة.\nهذا التصدير غير مجهول الهوية. لم تُصدَّر حقول مفاتيح API المحفوظة ولا المستندات الخام كاملةً. تحقّق من التقرير والمحادثة بحثًا عن أسرار قبل مشاركتهما.",
+ "ru": "Успешно экспортировано:\n{value1}\n\nСодержит: метаданные {value2} документ(ов), отчёт, чат и выбранные фрагменты.\nЭкспорт не анонимизирован. Сохранённые ключевые поля API и полные исходные документы не экспортировались. Перед передачей проверьте отчёт и чат на наличие секретов.",
+ "hi": "सफलतापूर्वक निर्यात किया गया:\n{value1}\n\nइसमें शामिल है: {value2} दस्तावेज़(ों), रिपोर्ट, चैट और चुने हुए अंशों के मेटाडेटा।\nयह निर्यात अनामित नहीं है। सहेजे गए API कुंजी फ़ील्ड और पूरे कच्चे दस्तावेज़ निर्यात नहीं किए गए हैं। साझा करने से पहले रिपोर्ट और चैट की जाँच करें कि उनमें कोई गोपनीय जानकारी तो नहीं है।",
+ "zh": "导出成功:\n{value1}\n\n包含:{value2} 个文档、报告、聊天记录及所选摘录的元数据。\n导出内容未经匿名化处理。已保存的 API 密钥字段及完整原始文档未导出。在分享前,请检查报告和聊天记录中是否包含机密信息。",
+ "fr": "Exporté avec succès :\n{value1}\n\nContient : les métadonnées de {value2} document(s), un rapport, un chat et des extraits sélectionnés.\nL'export n'est pas anonymisé. Les champs de clés API enregistrés et les documents bruts complets n'ont pas été exportés. Vérifiez le rapport et le chat pour détecter tout secret avant de les transmettre.",
+ "en": "Successfully exported:\n{value1}\n\nContains: metadata about {value2} document(s), report, chat, and selected excerpts.\nThe export is not anonymized. Stored API key fields and complete raw documents were not exported. Check the report and chat for secrets before sharing.",
+ "pt": "Exportado com sucesso:\n{value1}\n\nContém: metadados de {value2} documento(s), relatório, conversa e trechos selecionados.\nA exportação não é anonimizada. Os campos de chaves de API armazenados e os documentos brutos completos não foram exportados. Verifique se há segredos no relatório e na conversa antes de compartilhar.",
+ "ja": "エクスポートに成功しました:\n{value1}\n\n内容:{value2} 件のドキュメント、レポート、チャット、および選択した抜粋のメタデータ。\nこのエクスポートは匿名化されていません。保存済みの API キー項目と完全な未加工ドキュメントはエクスポートされていません。共有する前に、レポートとチャットに機密情報が含まれていないか確認してください。"
+ }
+ },
+ {
+ "key": "gui.main_window.erfolgreich_exportiert_value_ordner_value",
+ "canonical_source_value": "Erfolgreich exportiert:\n{value1}\n\nOrdner: {value2}",
+ "translations": {
+ "de": "Erfolgreich exportiert:\n{value1}\n\nOrdner: {value2}",
+ "zh": "导出成功:\n{value1}\n\n文件夹: {value2}",
+ "hi": "सफलतापूर्वक निर्यात किया गया:\n{value1}\n\nफ़ोल्डर: {value2}",
+ "en": "Successfully exported:\n{value1}\n\nFolder: {value2}",
+ "fr": "Exporté avec succès :\n{value1}\n\nDossier : {value2}",
+ "es": "Exportado correctamente:\n{value1}\n\nCarpeta: {value2}",
+ "pt": "Exportado com sucesso:\n{value1}\n\nPasta: {value2}",
+ "ru": "Успешно экспортировано:\n{value1}\n\nПапка: {value2}",
+ "bn": "সফলভাবে রপ্তানি করা হয়েছে:\n{value1}\n\nফোল্ডার: {value2}",
+ "ja": "正常にエクスポートされました:\n{value1}\n\nフォルダー: {value2}",
+ "ar": "تم التصدير بنجاح:\n{value1}\n\nالمجلد: {value2}"
+ }
+ },
+ {
+ "key": "gui.main_window.es_wurden_keine_alten_klartext_schlussel_gefunden",
+ "canonical_source_value": "Es wurden keine alten Klartext-Schlüssel gefunden.",
+ "translations": {
+ "de": "Es wurden keine alten Klartext-Schlüssel gefunden.",
+ "ru": "Старых открытых ключей не найдено.",
+ "pt": "Nenhuma chave antiga em texto simples foi encontrada.",
+ "hi": "कोई पुरानी प्लेनटेक्स्ट कुंजी नहीं मिली।",
+ "bn": "কোনো পুরোনো প্লেইন-টেক্সট কী পাওয়া যায়নি।",
+ "es": "No se encontraron claves antiguas en texto plano.",
+ "en": "No old plaintext keys were found.",
+ "fr": "Aucune ancienne clé en texte clair n'a été trouvée.",
+ "ar": "لم يتم العثور على أي مفاتيح نصية قديمة.",
+ "zh": "没有找到旧的明文密钥。",
+ "ja": "古い平文のキーは見つかりませんでした。"
+ }
+ },
+ {
+ "key": "gui.main_window.export",
+ "canonical_source_value": "Export",
+ "translations": {
+ "de": "Export",
+ "ja": "エクスポート",
+ "ar": "Export",
+ "en": "Export",
+ "zh": "导出",
+ "pt": "Exportar",
+ "fr": "Exporter",
+ "ru": "Экспорт",
+ "es": "Exportar",
+ "bn": "রপ্তানি",
+ "hi": "निर्यात"
+ }
+ },
+ {
+ "key": "gui.main_window.exportdatei_name",
+ "canonical_source_value": "Exportdatei-Name",
+ "translations": {
+ "de": "Exportdatei-Name",
+ "ja": "エクスポートファイル名",
+ "pt": "Nome do arquivo de exportação",
+ "ru": "Имя экспортируемого файла",
+ "zh": "导出文件名",
+ "bn": "রপ্তানি ফাইলের নাম",
+ "fr": "Nom du fichier d'exportation",
+ "en": "Export file name",
+ "hi": "एक्सपोर्ट फ़ाइल-नाम",
+ "es": "Nombre del archivo de exportación",
+ "ar": "اسم ملف التصدير"
+ }
+ },
+ {
+ "key": "gui.main_window.exportieren",
+ "canonical_source_value": "&Exportieren...",
+ "translations": {
+ "de": "&Exportieren...",
+ "pt": "&Exportar...",
+ "hi": "&एक्सपोर्ट करें...",
+ "bn": "&রপ্তানি করুন...",
+ "fr": "&Exporter...",
+ "zh": "&导出...",
+ "ar": "&تصدير...",
+ "en": "&Export...",
+ "ru": "&Экспортировать...",
+ "es": "&Exportar...",
+ "ja": "&エクスポート..."
+ }
+ },
+ {
+ "key": "gui.main_window.exportiert_projekt_dokumente_bericht_und_chat_als_note",
+ "canonical_source_value": "Exportiert Projekt, Dokumente, Bericht und Chat als\nnotespacellm-workspace-v1.json. Der Export ist nicht anonymisiert.",
+ "translations": {
+ "de": "Exportiert Projekt, Dokumente, Bericht und Chat als\nnotespacellm-workspace-v1.json. Der Export ist nicht anonymisiert.",
+ "en": "Exports project, documents, report and chat as\nnotespacellm-workspace-v1.json. The export is not anonymized.",
+ "zh": "将项目、文档、报告和聊天导出为\nnotespacellm-workspace-v1.json。该导出未经匿名化处理。",
+ "fr": "Exporte le projet, les documents, le rapport et le chat sous forme de\nnotespacellm-workspace-v1.json. L'export n'est pas anonymisé.",
+ "pt": "Exporta o projeto, os documentos, o relatório e o chat como\nnotespacellm-workspace-v1.json. A exportação não é anonimizada.",
+ "ru": "Экспортирует проект, документы, отчёт и чат как\nnotespacellm-workspace-v1.json. Экспорт не анонимизирован.",
+ "es": "Exporta el proyecto, los documentos, el informe y el chat como\nnotespacellm-workspace-v1.json. La exportación no está anonimizada.",
+ "ja": "プロジェクト、文書、レポート、チャットを\nnotespacellm-workspace-v1.json としてエクスポートします。エクスポートは匿名化されていません。",
+ "bn": "প্রকল্প, নথি, প্রতিবেদন ও চ্যাট রপ্তানি করা হবে\nnotespacellm-workspace-v1.json হিসেবে। এই রপ্তানি বেনামী করা হয় না।",
+ "hi": "प्रोजेक्ट, दस्तावेज़, रिपोर्ट और चैट को निर्यात किया जाता है\nnotespacellm-workspace-v1.json के रूप में। निर्यात गुमनाम नहीं है।",
+ "ar": "يتم تصدير المشروع والمستندات والتقرير والدردشة في ملف\nnotespacellm-workspace-v1.json. هذا التصدير غير مجهول الهوية."
+ }
+ },
+ {
+ "key": "gui.main_window.extrahiere_text_aus_value_dokumenten",
+ "canonical_source_value": "Extrahiere Text aus {value1} Dokumenten...",
+ "translations": {
+ "de": "Extrahiere Text aus {value1} Dokumenten...",
+ "es": "Extraer texto de {value1} documentos...",
+ "ar": "استخرج النص من مستندات {value1}...",
+ "zh": "从 {value1} 份文档中提取文本...",
+ "ja": "{value1} 件の文書からテキストを抽出...",
+ "pt": "Extraia texto dos documentos {value1}...",
+ "fr": "Extraire le texte de {value1} documents...",
+ "en": "Extract text from {value1} documents...",
+ "ru": "Извлечь текст из {value1} документов...",
+ "bn": "{value1} দস্তাবেজ থেকে পাঠ্য বের করুন...",
+ "hi": "{value1} दस्तावेज़ों से पाठ निकालें..."
+ }
+ },
+ {
+ "key": "gui.main_window.extrahiere_value_value_value",
+ "canonical_source_value": "Extrahiere ({value1}/{value2}): {value3}",
+ "translations": {
+ "de": "Extrahiere ({value1}/{value2}): {value3}",
+ "hi": "निकालें ({value1}/{value2}): {value3}",
+ "fr": "Extraire ({value1}/{value2}) : {value3}",
+ "ru": "Извлечь ({value1}/{value2}): {value3}",
+ "pt": "Extrair ({value1}/{value2}): {value3}",
+ "bn": "নিষ্কাশন চলছে ({value1}/{value2}): {value3}",
+ "es": "Extrayendo ({value1}/{value2}): {value3}",
+ "en": "Extracting ({value1}/{value2}): {value3}",
+ "zh": "正在提取 ({value1}/{value2}): {value3}",
+ "ja": "抽出中 ({value1}/{value2}): {value3}",
+ "ar": "جارٍ الاستخراج ({value1}/{value2}): {value3}"
+ }
+ },
+ {
+ "key": "gui.main_window.extraktion_abgeschlossen_value_dokumente",
+ "canonical_source_value": "Extraktion abgeschlossen: {value1} Dokumente",
+ "translations": {
+ "de": "Extraktion abgeschlossen: {value1} Dokumente",
+ "ja": "抽出完了: {value1} 件のドキュメント",
+ "es": "Extracción completada: {value1} documentos",
+ "fr": "Extraction terminée : {value1} documents",
+ "ar": "اكتمل الاستخراج: {value1} مستندًا.",
+ "zh": "提取已完成:{value1} 份文档",
+ "en": "Extraction completed: {value1} documents",
+ "hi": "निष्कर्षण पूरा हुआ: {value1} दस्तावेज़",
+ "ru": "Извлечение завершено: {value1} документов",
+ "pt": "Extração concluída: {value1} documentos",
+ "bn": "{value1} দস্তাবেজের নিষ্কাশন সম্পন্ন হয়েছে।"
+ }
+ },
+ {
+ "key": "gui.main_window.extraktion_lauft_bereits_anfrage_wird_danach_ausgefuhr",
+ "canonical_source_value": "Extraktion läuft bereits – Anfrage wird danach ausgeführt",
+ "translations": {
+ "de": "Extraktion läuft bereits – Anfrage wird danach ausgeführt",
+ "zh": "提取已在运行——请求将在此之后执行",
+ "pt": "Extração já em andamento – a solicitação será executada em seguida",
+ "bn": "এক্সট্র্যাকশন ইতিমধ্যে চলছে – অনুরোধটি তারপর কার্যকর করা হবে",
+ "en": "Extraction is already running – request will be executed afterwards",
+ "ru": "Извлечение уже выполняется – запрос будет выполнен после него",
+ "es": "Extracción en curso – la solicitud se ejecutará después",
+ "hi": "एक्सट्रैक्शन पहले से चल रहा है – अनुरोध उसके बाद निष्पादित किया जाएगा",
+ "ar": "استخراج قيد التنفيذ بالفعل – سيتم تنفيذ الطلب بعد ذلك",
+ "ja": "抽出は既に実行中です。リクエストはその後に実行されます。",
+ "fr": "L'extraction est déjà en cours – la requête sera exécutée ensuite"
+ }
+ },
+ {
+ "key": "gui.main_window.fehler_bei_berichterstellung_value",
+ "canonical_source_value": "Fehler bei Berichterstellung: {value1}",
+ "translations": {
+ "de": "Fehler bei Berichterstellung: {value1}",
+ "ja": "レポート作成エラー: {value1}",
+ "zh": "报告生成错误:{value1}",
+ "pt": "Erro na geração do relatório: {value1}",
+ "hi": "रिपोर्ट निर्माण में त्रुटि: {value1}",
+ "es": "Error de generación del informe: {value1}",
+ "en": "Report generation error: {value1}",
+ "bn": "রিপোর্ট তৈরিতে ত্রুটি: {value1}",
+ "fr": "Erreur lors de la génération du rapport : {value1}",
+ "ru": "Ошибка при создании отчёта: {value1}",
+ "ar": "خطأ في إنشاء التقرير: {value1}"
+ }
+ },
+ {
+ "key": "gui.main_window.fehler_bei_value_value",
+ "canonical_source_value": "Fehler bei {value1}: {value2}",
+ "translations": {
+ "de": "Fehler bei {value1}: {value2}",
+ "zh": "{value1}中出现错误:{value2}",
+ "bn": "{value1}-এ ত্রুটি: {value2}",
+ "ru": "Ошибка в {value1}: {value2}",
+ "ar": "خطأ في {value1}: {value2}",
+ "es": "Error en {value1}: {value2}",
+ "pt": "Erro em {value1}: {value2}",
+ "fr": "Erreur dans {value1} : {value2}",
+ "hi": "{value1} में त्रुटि: {value2}",
+ "en": "Error in {value1}: {value2}",
+ "ja": "{value1} でエラー: {value2}"
+ }
+ },
+ {
+ "key": "gui.main_window.fehler_beim_leeren_des_index",
+ "canonical_source_value": "Fehler beim Leeren des Index.",
+ "translations": {
+ "de": "Fehler beim Leeren des Index.",
+ "hi": "इंडेक्स खाली करते समय त्रुटि।",
+ "bn": "ইন্ডেক্স খালি করতে ত্রুটি।",
+ "fr": "Erreur lors du vidage de l'index.",
+ "ar": "فشل مسح الفهرس.",
+ "es": "Error al vaciar el índice.",
+ "pt": "Erro ao limpar o índice.",
+ "ja": "インデックスのクリアに失敗しました。",
+ "zh": "清空索引时出错。",
+ "ru": "Ошибка при очистке индекса.",
+ "en": "Error while clearing the index."
+ }
+ },
+ {
+ "key": "gui.main_window.fehler_beim_speichern",
+ "canonical_source_value": "Fehler beim Speichern",
+ "translations": {
+ "de": "Fehler beim Speichern",
+ "en": "Error while saving",
+ "pt": "Erro ao salvar",
+ "fr": "Erreur lors de l'enregistrement",
+ "ar": "خطأ أثناء الحفظ",
+ "ru": "Ошибка при сохранении",
+ "ja": "保存中にエラーが発生しました",
+ "bn": "সংরক্ষণে ত্রুটি",
+ "es": "Error al guardar",
+ "zh": "保存失败",
+ "hi": "सहेजने में त्रुटि"
+ }
+ },
+ {
+ "key": "gui.main_window.fuhre_value_analysen_durch",
+ "canonical_source_value": "Führe {value1} Analysen durch...",
+ "translations": {
+ "de": "Führe {value1} Analysen durch...",
+ "ru": "Запустить анализы, количество: {value1}...",
+ "hi": "{value1} विश्लेषण चलाएँ...",
+ "ar": "قم بإجراء تحليلات {value1}...",
+ "zh": "执行 {value1} 次分析...",
+ "ja": "{value1} 件の分析を実行...",
+ "bn": "{value1} বিশ্লেষণ চালান...",
+ "en": "Run {value1} analyses...",
+ "fr": "Lancer {value1} analyses...",
+ "es": "Ejecutar {value1} análisis...",
+ "pt": "Executar {value1} análises..."
+ }
+ },
+ {
+ "key": "gui.main_window.fur_openai_anthropic_api_key_muss_als_umgebungsvariabl",
+ "canonical_source_value": "Für OpenAI/Anthropic: API-Key muss als Umgebungsvariable gesetzt sein\n(OPENAI_API_KEY bzw. ANTHROPIC_API_KEY)",
+ "translations": {
+ "de": "Für OpenAI/Anthropic: API-Key muss als Umgebungsvariable gesetzt sein\n(OPENAI_API_KEY bzw. ANTHROPIC_API_KEY)",
+ "ja": "OpenAI/Anthropic の場合、API-Key を環境変数として設定しておく必要があります\n(OPENAI_API_KEY または ANTHROPIC_API_KEY)",
+ "en": "For OpenAI/Anthropic: the API key must be set as an environment variable\n(OPENAI_API_KEY or ANTHROPIC_API_KEY)",
+ "zh": "对于 OpenAI/Anthropic:API 密钥必须设置为环境变量\n(OPENAI_API_KEY 或 ANTHROPIC_API_KEY)",
+ "ru": "Для OpenAI/Anthropic: API-Key должен быть задан как переменная окружения\n(OPENAI_API_KEY или ANTHROPIC_API_KEY)",
+ "ar": "لـ OpenAI/Anthropic: يجب ضبط مفتاح API كمتغير بيئة\n(OPENAI_API_KEY أو ANTHROPIC_API_KEY)",
+ "fr": "Pour OpenAI/Anthropic : la clé API doit être définie comme variable d'environnement\n(OPENAI_API_KEY ou ANTHROPIC_API_KEY)",
+ "pt": "Para OpenAI/Anthropic: a chave API-Key deve estar definida como variável de ambiente\n(OPENAI_API_KEY ou ANTHROPIC_API_KEY)",
+ "es": "Para OpenAI/Anthropic: la clave de API debe establecerse como variable de entorno\n(OPENAI_API_KEY o ANTHROPIC_API_KEY)",
+ "bn": "OpenAI/Anthropic-এর জন্য: API কী পরিবেশ ভেরিয়েবল হিসেবে সেট করতে হবে\n(OPENAI_API_KEY অথবা ANTHROPIC_API_KEY)",
+ "hi": "OpenAI/Anthropic के लिए: API कुंजी को पर्यावरण चर के रूप में सेट करना आवश्यक है\n(OPENAI_API_KEY या ANTHROPIC_API_KEY)"
+ }
+ },
+ {
+ "key": "gui.main_window.ganzen_ordner_rekursiv_hinzufugen_alle_unterstutzten_d",
+ "canonical_source_value": "Ganzen Ordner rekursiv hinzufügen (alle unterstützten Dateitypen)",
+ "translations": {
+ "de": "Ganzen Ordner rekursiv hinzufügen (alle unterstützten Dateitypen)",
+ "en": "Add entire folder recursively (all supported file types)",
+ "pt": "Adicionar recursivamente a pasta inteira (todos os tipos de arquivo compatíveis)",
+ "ja": "再帰的にフォルダー全体を追加(サポートされているすべてのファイル形式)",
+ "ar": "إضافة مجلد كامل بشكل متكرر (جميع أنواع الملفات المدعومة)",
+ "fr": "Ajouter récursivement tout un dossier (tous les types de fichiers pris en charge)",
+ "zh": "递归添加整个文件夹(所有支持的文件类型)",
+ "hi": "सभी समर्थित फ़ाइल प्रकारों सहित पूरे फ़ोल्डर को पुनरावर्ती रूप से जोड़ें",
+ "ru": "Рекурсивно добавить всю папку (все поддерживаемые типы файлов)",
+ "es": "Añadir la carpeta completa de forma recursiva (todos los tipos de archivo compatibles)",
+ "bn": "পুরো ফোল্ডারটি পুনরাবৃত্তভাবে যোগ করুন (সমর্থিত সব ফাইলের ধরন)"
+ }
+ },
+ {
+ "key": "gui.main_window.gespeicherten_api_key_entfernen",
+ "canonical_source_value": "Gespeicherten API-Key entfernen",
+ "translations": {
+ "de": "Gespeicherten API-Key entfernen",
+ "ar": "إزالة مفتاح API المحفوظ",
+ "es": "Eliminar la clave API guardada",
+ "ja": "保存済みの API キーを削除",
+ "ru": "Удалить сохранённый ключ API",
+ "pt": "Remover a chave de API salva",
+ "fr": "Supprimer la clé API enregistrée",
+ "en": "Remove stored API key",
+ "hi": "सहेजी गई API कुंजी हटाएँ",
+ "zh": "移除已保存的 API 密钥",
+ "bn": "সংরক্ষিত API কী সরান"
+ }
+ },
+ {
+ "key": "gui.main_window.hauptwerkzeuge",
+ "canonical_source_value": "Hauptwerkzeuge",
+ "translations": {
+ "de": "Hauptwerkzeuge",
+ "en": "Main tools",
+ "hi": "मुख्य उपकरण",
+ "zh": "主要工具",
+ "ja": "主要ツール",
+ "ru": "Основные инструменты",
+ "ar": "الأدوات الرئيسية",
+ "pt": "Ferramentas principais",
+ "fr": "Outils principaux",
+ "es": "Herramientas principales",
+ "bn": "প্রধান সরঞ্জামসমূহ"
+ }
+ },
+ {
+ "key": "gui.main_window.hilfe",
+ "canonical_source_value": "&Hilfe",
+ "translations": {
+ "de": "&Hilfe",
+ "en": "&Help",
+ "hi": "&सहायता",
+ "fr": "&Aide",
+ "zh": "&帮助",
+ "ru": "&Справка",
+ "pt": "&Ajuda",
+ "bn": "&সহায়তা",
+ "es": "&Ayuda",
+ "ja": "&ヘルプ",
+ "ar": "&مساعدة"
+ }
+ },
+ {
+ "key": "gui.main_window.index_erfolgreich_geleert",
+ "canonical_source_value": "Index erfolgreich geleert.",
+ "translations": {
+ "de": "Index erfolgreich geleert.",
+ "ja": "インデックスは正常に消去されました。",
+ "bn": "ইনডেক্স সফলভাবে খালি করা হয়েছে।",
+ "ru": "Индекс успешно очищен.",
+ "es": "Índice vaciado correctamente.",
+ "pt": "O índice foi esvaziado com sucesso.",
+ "zh": "索引已成功清空。",
+ "hi": "इंडेक्स सफलतापूर्वक खाली कर दिया गया।",
+ "ar": "تم تفريغ الفهرس بنجاح.",
+ "en": "Index successfully cleared.",
+ "fr": "L'index a été vidé avec succès."
+ }
+ },
+ {
+ "key": "gui.main_window.index_leeren",
+ "canonical_source_value": "Index leeren",
+ "translations": {
+ "de": "Index leeren",
+ "ja": "インデックスをクリア",
+ "bn": "ইন্ডেক্স খালি করুন",
+ "zh": "清空索引",
+ "es": "Vaciar índice",
+ "hi": "इंडेक्स खाली करें",
+ "ar": "إفراغ الفهرس",
+ "en": "Clear index",
+ "pt": "Limpar índice",
+ "fr": "Vider l'index",
+ "ru": "Очистить индекс"
+ }
+ },
+ {
+ "key": "gui.main_window.indexiere_value_dokumente",
+ "canonical_source_value": "Indexiere {value1} Dokumente...",
+ "translations": {
+ "de": "Indexiere {value1} Dokumente...",
+ "ja": "{value1} ドキュメントをインデックス化しています...",
+ "fr": "Indexation des documents {value1} en cours...",
+ "ar": "تتم فهرسة {value1} من المستندات...",
+ "en": "Indexing {value1} documents...",
+ "ru": "Индексация {value1} документов...",
+ "bn": "{value1} দস্তাবেজগুলো সূচীবদ্ধ করা হচ্ছে...",
+ "hi": "{value1} दस्तावेज़ों को अनुक्रमित किया जा रहा है...",
+ "pt": "Indexando {value1} documentos...",
+ "zh": "正在为 {value1} 份文档建立索引...",
+ "es": "Indexando {value1} documentos..."
+ }
+ },
+ {
+ "key": "gui.main_window.indexiere_value_value_value",
+ "canonical_source_value": "Indexiere ({value1}/{value2}): {value3}",
+ "translations": {
+ "de": "Indexiere ({value1}/{value2}): {value3}",
+ "ar": "جارٍ فهرسة ({value1}/{value2}): {value3}",
+ "ru": "Идёт индексация ({value1}/{value2}): {value3}",
+ "hi": "सूचीकरण जारी है ({value1}/{value2}): {value3}",
+ "en": "Indexing ({value1}/{value2}): {value3}",
+ "fr": "Indexation en cours ({value1}/{value2}) : {value3}",
+ "ja": "インデックス作成中 ({value1}/{value2}): {value3}",
+ "zh": "正在建立索引 ({value1}/{value2}): {value3}",
+ "bn": "সূচিবদ্ধকরণ চলছে ({value1}/{value2}): {value3}",
+ "es": "Indexando ({value1}/{value2}): {value3}",
+ "pt": "Indexando ({value1}/{value2}): {value3}"
+ }
+ },
+ {
+ "key": "gui.main_window.indexierung_lauft_bereits",
+ "canonical_source_value": "Indexierung läuft bereits.",
+ "translations": {
+ "de": "Indexierung läuft bereits.",
+ "en": "Indexing is already running.",
+ "es": "La indexación ya está en curso.",
+ "ar": "جارٍ الفهرسة بالفعل.",
+ "fr": "L'indexation est déjà en cours.",
+ "pt": "A indexação já está em andamento.",
+ "ru": "Индексация уже запущена.",
+ "hi": "इंडेक्सिंग पहले से चल रही है।",
+ "zh": "索引编制已在进行中。",
+ "ja": "インデックス作成はすでに実行中です。",
+ "bn": "ইন্ডেক্সিং ইতিমধ্যে চলছে।"
+ }
+ },
+ {
+ "key": "gui.main_window.indexierung_lauft_bereits_nachindexierung_spater_uber",
+ "canonical_source_value": "Indexierung läuft bereits – Nachindexierung später über das RAG-Menü",
+ "translations": {
+ "de": "Indexierung läuft bereits – Nachindexierung später über das RAG-Menü",
+ "ru": "Индексация уже выполняется – повторная индексация позже через меню RAG",
+ "es": "La indexación ya está en curso; la reindexación se puede realizar más adelante desde el menú RAG.",
+ "fr": "L'indexation est déjà en cours – la réindexation peut être lancée ultérieurement via le menu RAG",
+ "bn": "ইন্ডেক্সিং ইতিমধ্যে চলছে – পরবর্তীতে RAG মেনু থেকে পুনরায় ইন্ডেক্সিং করুন",
+ "en": "Indexing is already running – re-indexing later via the RAG menu",
+ "pt": "A indexação já está em andamento – a reindexação pode ser feita depois pelo menu RAG",
+ "ja": "インデックス作成はすでに実行中です。後で再インデックスを行う場合は、RAG メニューから実行してください。",
+ "zh": "索引编制已在进行中——稍后可通过 RAG 菜单进行重新索引",
+ "ar": "الفهرسة قيد التشغيل بالفعل – إعادة الفهرسة لاحقًا عبر قائمة RAG",
+ "hi": "इंडेक्सिंग पहले से चल रही है – बाद में पुनः इंडेक्सिंग RAG मेनू के माध्यम से की जा सकती है"
+ }
+ },
+ {
+ "key": "gui.main_window.kein_inhalt_zum_exportieren",
+ "canonical_source_value": "Kein Inhalt zum Exportieren.",
+ "translations": {
+ "de": "Kein Inhalt zum Exportieren.",
+ "ja": "エクスポートするコンテンツはありません。",
+ "es": "No hay contenido para exportar.",
+ "bn": "কপি করার মতো কোনো বিষয়বস্তু নেই।",
+ "ru": "Нет содержимого для экспорта.",
+ "zh": "没有可导出的内容。",
+ "en": "No content to export.",
+ "ar": "لا يوجد محتوى للتصدير.",
+ "hi": "कोई निर्यात करने योग्य सामग्री नहीं है।",
+ "fr": "Aucun contenu à exporter.",
+ "pt": "Nenhum conteúdo para exportar."
+ }
+ },
+ {
+ "key": "gui.main_window.kein_llm_client_verfugbar",
+ "canonical_source_value": "Kein LLM-Client verfügbar.",
+ "translations": {
+ "de": "Kein LLM-Client verfügbar.",
+ "zh": "没有可用的 LLM 客户端。",
+ "ar": "لا يتوفر عميل LLM.",
+ "ja": "LLMのクライアントは利用できません。",
+ "hi": "LLM क्लाइंट उपलब्ध नहीं है।",
+ "pt": "Nenhum cliente LLM disponível.",
+ "en": "No LLM client available.",
+ "es": "No hay ningún cliente LLM disponible.",
+ "fr": "Aucun client LLM n'est disponible.",
+ "bn": "LLM-এর কোনো ক্লায়েন্ট উপলব্ধ নেই।",
+ "ru": "Клиент LLM недоступен."
+ }
+ },
+ {
+ "key": "gui.main_window.kein_modell_ausgewahlt",
+ "canonical_source_value": "Kein Modell ausgewählt.",
+ "translations": {
+ "de": "Kein Modell ausgewählt.",
+ "en": "No model selected.",
+ "pt": "Nenhum modelo selecionado.",
+ "hi": "कोई मॉडल चयनित नहीं है।",
+ "ar": "لم يتم اختيار أي نموذج.",
+ "ru": "Модель не выбрана.",
+ "es": "Ningún modelo seleccionado.",
+ "ja": "モデルが選択されていません。",
+ "zh": "请选择一个模型。",
+ "bn": "কোনো মডেল নির্বাচন করা হয়নি।",
+ "fr": "Aucun modèle sélectionné."
+ }
+ },
+ {
+ "key": "gui.main_window.kein_projekt_geoffnet",
+ "canonical_source_value": "Kein Projekt geöffnet.",
+ "translations": {
+ "de": "Kein Projekt geöffnet.",
+ "ja": "プロジェクトが開かれていません。",
+ "hi": "कोई प्रोजेक्ट नहीं खुला है।",
+ "en": "No project open.",
+ "fr": "Aucun projet ouvert.",
+ "ru": "Проект не открыт.",
+ "zh": "没有打开任何项目。",
+ "pt": "Nenhum projeto aberto.",
+ "ar": "لم يتم فتح أي مشروع.",
+ "es": "No hay ningún proyecto abierto.",
+ "bn": "কোনো প্রকল্প খোলা নেই।"
+ }
+ },
+ {
+ "key": "gui.main_window.keine_ausgewahlten_dokumente_mit_text_gefunden",
+ "canonical_source_value": "Keine ausgewählten Dokumente mit Text gefunden.",
+ "translations": {
+ "de": "Keine ausgewählten Dokumente mit Text gefunden.",
+ "ru": "Ни одного документа с текстом по выбранным критериям не найдено.",
+ "es": "No se encontraron documentos seleccionados con texto.",
+ "bn": "ব্যাখ্যাযোগ্য পাঠসহ কোনো নির্বাচিত নথি পাওয়া যায়নি।",
+ "ja": "選択されたテキストを含むドキュメントは見つかりませんでした。",
+ "pt": "Nenhum documento selecionado com texto encontrado.",
+ "zh": "没有找到包含文本的已选文档。",
+ "fr": "Aucun document sélectionné contenant du texte n'a été trouvé.",
+ "en": "No selected documents containing text were found.",
+ "ar": "لم يتم العثور على مستندات محددة تحتوي على نص.",
+ "hi": "पाठ वाले कोई चयनित दस्तावेज़ नहीं मिले।"
+ }
+ },
+ {
+ "key": "gui.main_window.keine_ausstehenden_analysen",
+ "canonical_source_value": "Keine ausstehenden Analysen",
+ "translations": {
+ "de": "Keine ausstehenden Analysen",
+ "pt": "Nenhuma análise pendente",
+ "fr": "Aucune analyse en attente",
+ "en": "No pending analyses",
+ "ru": "Нет ожидающих анализов",
+ "ar": "لا توجد تحليلات معلّقة",
+ "es": "No hay análisis pendientes",
+ "zh": "没有待处理的分析",
+ "ja": "現在、保留中の分析はありません",
+ "hi": "कोई लंबित विश्लेषण नहीं",
+ "bn": "কোনো অপেক্ষমাণ বিশ্লেষণ নেই"
+ }
+ },
+ {
+ "key": "gui.main_window.keine_dokumente",
+ "canonical_source_value": "📄 Keine Dokumente",
+ "translations": {
+ "de": "📄 Keine Dokumente",
+ "ru": "📄 Нет документов",
+ "bn": "📄 কোনো নথি নেই",
+ "es": "📄 Sin documentos",
+ "pt": "📄 Nenhum documento",
+ "en": "📄 No documents",
+ "ar": "📄 لا توجد مستندات",
+ "ja": "📄 ドキュメントはありません",
+ "zh": "📄 没有文档",
+ "hi": "📄 कोई दस्तावेज़ नहीं",
+ "fr": "📄 Aucun document"
+ }
+ },
+ {
+ "key": "gui.main_window.keine_dokumente_mit_extrahiertem_text_gefunden",
+ "canonical_source_value": "Keine Dokumente mit extrahiertem Text gefunden.",
+ "translations": {
+ "de": "Keine Dokumente mit extrahiertem Text gefunden.",
+ "hi": "उपयोग योग्य निकाले गए पाठ वाले कोई दस्तावेज़ नहीं मिले।",
+ "pt": "Nenhum documento com texto extraído foi encontrado.",
+ "zh": "未找到包含提取文本的文档。",
+ "ru": "Не найдено документов с извлечённым текстом.",
+ "en": "No documents with extracted text were found.",
+ "bn": "উদ্ধার করা পাঠ্যসহ কোনো নথি পাওয়া যায়নি।",
+ "ja": "抽出されたテキストを含む文書は見つかりませんでした。",
+ "ar": "لم يتم العثور على مستندات تحتوي على نص مستخرج.",
+ "es": "No se encontraron documentos con texto extraído.",
+ "fr": "Aucun document avec du texte extrait n'a été trouvé."
+ }
+ },
+ {
+ "key": "gui.main_window.keine_dokumente_mit_extrahiertem_text_vorhanden_bitte",
+ "canonical_source_value": "Keine Dokumente mit extrahiertem Text vorhanden.\nBitte zuerst Dokumente hinzufügen.",
+ "translations": {
+ "de": "Keine Dokumente mit extrahiertem Text vorhanden.\nBitte zuerst Dokumente hinzufügen.",
+ "ru": "Нет документов с извлечённым текстом.\nСначала добавьте документы.",
+ "ar": "لا توجد مستندات تحتوي على نص مستخرج.\nيرجى إضافة المستندات أولاً.",
+ "zh": "没有包含提取文本的文档。\n请先添加文档。",
+ "fr": "Aucun document avec du texte extrait n'est disponible.\nVeuillez d'abord ajouter des documents.",
+ "pt": "Não há documentos com texto extraído disponíveis.\nPor favor, adicione documentos primeiro.",
+ "es": "No hay documentos con texto extraído disponibles.\nPor favor, añada primero documentos.",
+ "hi": "निकाले गए पाठ वाले कोई दस्तावेज़ उपलब्ध नहीं हैं।\nकृपया पहले दस्तावेज़ जोड़ें।",
+ "en": "No documents with extracted text are available.\nPlease add documents first.",
+ "bn": "এখানে নিষ্কাশিত পাঠ্যসহ কোনো নথি নেই।\nঅনুগ্রহ করে প্রথমে নথি যোগ করুন।",
+ "ja": "抽出済みテキストを含む文書はありません。\n先に文書を追加してください。"
+ }
+ },
+ {
+ "key": "gui.main_window.keine_extrahierten_texte_vorhanden",
+ "canonical_source_value": "Keine extrahierten Texte vorhanden.",
+ "translations": {
+ "de": "Keine extrahierten Texte vorhanden.",
+ "en": "No extracted texts available.",
+ "fr": "Aucun texte extrait n'est disponible.",
+ "hi": "कोई निकाला गया पाठ उपलब्ध नहीं है।",
+ "ja": "抽出されたテキストはありません。",
+ "bn": "এখনও কোনো নিষ্কাশিত পাঠ্য নেই।",
+ "ru": "Извлечённых текстов нет.",
+ "zh": "没有已提取的文本。",
+ "ar": "لا توجد نصوص مستخرجة.",
+ "pt": "Não há textos extraídos disponíveis.",
+ "es": "No hay textos extraídos disponibles."
+ }
+ },
+ {
+ "key": "gui.main_window.keine_projekte_vorhanden",
+ "canonical_source_value": "Keine Projekte vorhanden.",
+ "translations": {
+ "de": "Keine Projekte vorhanden.",
+ "en": "No projects available.",
+ "ru": "Проектов нет.",
+ "es": "No hay proyectos disponibles.",
+ "hi": "कोई प्रोजेक्ट मौजूद नहीं है।",
+ "zh": "没有项目。",
+ "ja": "プロジェクトはありません。",
+ "fr": "Aucun projet disponible.",
+ "pt": "Nenhum projeto disponível.",
+ "ar": "لا توجد مشاريع.",
+ "bn": "কোনো প্রকল্প নেই।"
+ }
+ },
+ {
+ "key": "gui.main_window.lade",
+ "canonical_source_value": "Lade...",
+ "translations": {
+ "de": "Lade...",
+ "zh": "加载中...",
+ "fr": "Chargement...",
+ "bn": "লোড হচ্ছে...",
+ "en": "Loading...",
+ "ja": "読み込み中...",
+ "hi": "लोड हो रहा है...",
+ "pt": "Carregando...",
+ "es": "Cargando...",
+ "ru": "Загрузка...",
+ "ar": "جارٍ التحميل..."
+ }
+ },
+ {
+ "key": "gui.main_window.leer_lassen_vorhandenen_schlussel_behalten",
+ "canonical_source_value": "Leer lassen = vorhandenen Schlüssel behalten",
+ "translations": {
+ "de": "Leer lassen = vorhandenen Schlüssel behalten",
+ "hi": "खाली छोड़ें = मौजूदा कुंजी रखें",
+ "ru": "Оставить пустым = сохранить существующий ключ",
+ "pt": "Deixe em branco = manter a chave existente",
+ "bn": "ফাঁকা রাখুন = বিদ্যমান কীটি রেখে দিন",
+ "es": "Dejar en blanco = conservar la clave existente",
+ "en": "Leave empty = keep existing key",
+ "zh": "留空 = 保留已有密钥",
+ "fr": "Laisser vide = conserver la clé existante",
+ "ar": "ترك الحقل فارغًا = الاحتفاظ بالمفتاح الموجود",
+ "ja": "空欄のままにすると既存のキーを保持します"
+ }
+ },
+ {
+ "key": "gui.main_window.llm",
+ "canonical_source_value": "&LLM",
+ "translations": {
+ "de": "&LLM",
+ "en": "&LLM",
+ "es": "&LLM",
+ "zh": "&LLM",
+ "ja": "&LLM",
+ "ru": "&LLM",
+ "fr": "&LLM",
+ "hi": "&LLM",
+ "ar": "&LLM",
+ "bn": "&LLM",
+ "pt": "&LLM"
+ }
+ },
+ {
+ "key": "gui.main_window.llm.370515d5",
+ "canonical_source_value": "LLM",
+ "translations": {
+ "de": "LLM",
+ "en": "LLM",
+ "es": "LLM",
+ "zh": "LLM",
+ "ja": "LLM",
+ "ru": "LLM",
+ "fr": "LLM",
+ "hi": "LLM",
+ "ar": "LLM",
+ "bn": "LLM",
+ "pt": "LLM"
+ }
+ },
+ {
+ "key": "gui.main_window.llm_einstellungen",
+ "canonical_source_value": "LLM-&Einstellungen...",
+ "translations": {
+ "de": "LLM-&Einstellungen...",
+ "ja": "LLM-&設定...",
+ "hi": "LLM-&सेटिंग्स...",
+ "en": "LLM-&Settings...",
+ "pt": "LLM-&Configurações...",
+ "bn": "LLM-&সেটিংস...",
+ "es": "LLM-&Configuración...",
+ "zh": "LLM-&设置...",
+ "ar": "LLM-&الإعدادات...",
+ "ru": "LLM-&Настройки...",
+ "fr": "LLM-&Paramètres..."
+ }
+ },
+ {
+ "key": "gui.main_window.llm_einstellungen.a8636bac",
+ "canonical_source_value": "LLM-Einstellungen",
+ "translations": {
+ "de": "LLM-Einstellungen",
+ "ru": "LLM-Настройки",
+ "es": "LLM-Configuración",
+ "hi": "LLM-सेटिंग्स",
+ "fr": "LLM-Paramètres",
+ "pt": "LLM-Configurações",
+ "en": "LLM-Settings",
+ "ar": "LLM-الإعدادات",
+ "ja": "LLM-設定",
+ "zh": "LLM-设置",
+ "bn": "LLM-সেটিংস"
+ }
+ },
+ {
+ "key": "gui.main_window.llm_einstellungen_nicht_gespeichert",
+ "canonical_source_value": "LLM-Einstellungen nicht gespeichert",
+ "translations": {
+ "de": "LLM-Einstellungen nicht gespeichert",
+ "hi": "LLM-सेटिंग्स सहेजी नहीं गईं",
+ "en": "LLM-Settings not saved",
+ "ar": "لم يتم حفظ إعدادات LLM",
+ "fr": "Paramètres LLM non enregistrés",
+ "es": "No se guardó la configuración de LLM",
+ "ru": "LLM — настройки не сохранены",
+ "bn": "LLM-সেটিংস সংরক্ষণ করা হয়নি",
+ "zh": "LLM-设置未保存",
+ "ja": "LLM-設定が保存されませんでした",
+ "pt": "As configurações de LLM não foram salvas"
+ }
+ },
+ {
+ "key": "gui.main_window.llm_initialisiert_value_value",
+ "canonical_source_value": "LLM initialisiert: {value1}/{value2}",
+ "translations": {
+ "de": "LLM initialisiert: {value1}/{value2}",
+ "es": "LLM inicializado: {value1}/{value2}",
+ "zh": "LLM 已初始化:{value1}/{value2}",
+ "en": "LLM initialized: {value1}/{value2}",
+ "hi": "LLM प्रारंभ किया गया: {value1}/{value2}",
+ "ar": "تمت تهيئة LLM: {value1}/{value2}",
+ "fr": "LLM initialisé : {value1}/{value2}",
+ "ru": "LLM инициализирован: {value1}/{value2}",
+ "bn": "LLM শুরু হয়েছে: {value1}/{value2}",
+ "ja": "LLM を初期化しました: {value1}/{value2}",
+ "pt": "LLM inicializado: {value1}/{value2}"
+ }
+ },
+ {
+ "key": "gui.main_window.llm_konnte_nicht_initialisiert_werden_value",
+ "canonical_source_value": "LLM konnte nicht initialisiert werden ({value1}).",
+ "translations": {
+ "de": "LLM konnte nicht initialisiert werden ({value1}).",
+ "zh": "LLM 无法初始化({value1})。",
+ "ar": "تعذّر تهيئة LLM ({value1}).",
+ "ja": "LLM を初期化できませんでした ({value1})。",
+ "en": "LLM could not be initialized ({value1}).",
+ "bn": "LLM শুরু করা যায়নি ({value1})।",
+ "fr": "LLM n'a pas pu être initialisé ({value1}).",
+ "pt": "LLM não pôde ser inicializado ({value1}).",
+ "ru": "LLM не удалось инициализировать ({value1}).",
+ "hi": "LLM आरंभ नहीं किया जा सका ({value1})।",
+ "es": "LLM no se pudo inicializar ({value1})."
+ }
+ },
+ {
+ "key": "gui.main_window.llm_value_value",
+ "canonical_source_value": "LLM: {value1} / {value2}",
+ "translations": {
+ "de": "LLM: {value1} / {value2}",
+ "en": "LLM: {value1} / {value2}",
+ "es": "LLM: {value1} / {value2}",
+ "zh": "LLM: {value1} / {value2}",
+ "ja": "LLM: {value1} / {value2}",
+ "ru": "LLM: {value1} / {value2}",
+ "fr": "LLM: {value1} / {value2}",
+ "hi": "LLM: {value1} / {value2}",
+ "ar": "LLM: {value1} / {value2}",
+ "bn": "LLM: {value1} / {value2}",
+ "pt": "LLM: {value1} / {value2}"
+ }
+ },
+ {
+ "key": "gui.main_window.loschen",
+ "canonical_source_value": "Löschen",
+ "translations": {
+ "de": "Löschen",
+ "ru": "Удалить",
+ "ar": "حذف",
+ "pt": "Excluir",
+ "hi": "हटाएँ",
+ "zh": "删除",
+ "fr": "Supprimer",
+ "en": "Delete",
+ "bn": "মুছুন",
+ "ja": "削除",
+ "es": "Eliminar"
+ }
+ },
+ {
+ "key": "gui.main_window.migration_abgeschlossen",
+ "canonical_source_value": "Migration abgeschlossen",
+ "translations": {
+ "de": "Migration abgeschlossen",
+ "ja": "移行が完了しました",
+ "zh": "迁移已完成",
+ "ar": "اكتمل الترحيل",
+ "ru": "Миграция завершена",
+ "bn": "মাইগ্রেশন সম্পন্ন",
+ "en": "Migration completed",
+ "fr": "Migration terminée",
+ "pt": "Migração concluída",
+ "es": "Migración completada",
+ "hi": "माइग्रेशन पूरा हुआ"
+ }
+ },
+ {
+ "key": "gui.main_window.migration_fehlgeschlagen",
+ "canonical_source_value": "Migration fehlgeschlagen",
+ "translations": {
+ "de": "Migration fehlgeschlagen",
+ "pt": "Falha na migração",
+ "es": "Migración fallida",
+ "ru": "Миграция не удалась",
+ "zh": "迁移失败",
+ "ja": "移行に失敗しました",
+ "hi": "माइग्रेशन विफल रहा",
+ "en": "Migration failed",
+ "fr": "Migration échouée",
+ "ar": "فشل الترحيل",
+ "bn": "মাইগ্রেশন ব্যর্থ হয়েছে"
+ }
+ },
+ {
+ "key": "gui.main_window.mochtest_du_den_gesamten_rag_index_leeren_alle_embeddi",
+ "canonical_source_value": "Möchtest du den gesamten RAG-Index leeren?\nAlle Embeddings werden gelöscht.",
+ "translations": {
+ "de": "Möchtest du den gesamten RAG-Index leeren?\nAlle Embeddings werden gelöscht.",
+ "zh": "是否要清空整个 RAG 索引?\n所有嵌入将被删除。",
+ "bn": "RAG-ইনডেক্সের সমস্ত তথ্য কি মুছে ফেলতে চাও?\nসমস্ত এমবেডিং মুছে ফেলা হবে।",
+ "es": "¿Quieres vaciar todo el índice RAG?\nSe eliminarán todos los embeddings.",
+ "fr": "Voulez-vous vider l'intégralité de l'index RAG ?\nTous les embeddings seront supprimés.",
+ "en": "Do you want to clear the entire RAG index?\nAll embeddings will be deleted.",
+ "ru": "Хочешь очистить весь индекс RAG?\nВсе эмбеддинги будут удалены.",
+ "pt": "Deseja limpar todo o índice RAG?\nTodos os embeddings serão excluídos.",
+ "hi": "क्या आप पूरा RAG इंडेक्स खाली करना चाहते हैं?\nसभी एम्बेडिंग हटा दी जाएँगी।",
+ "ar": "هل تريد مسح فهرس RAG بالكامل؟\nسيتم حذف جميع التضمينات.",
+ "ja": "RAGのインデックス全体を消去しますか?\nすべての埋め込みが削除されます。"
+ }
+ },
+ {
+ "key": "gui.main_window.modell",
+ "canonical_source_value": "Modell",
+ "translations": {
+ "de": "Modell",
+ "en": "Model",
+ "bn": "মডেল",
+ "zh": "模型",
+ "ru": "Модель",
+ "hi": "मॉडल",
+ "ja": "モデル",
+ "ar": "النموذج",
+ "fr": "Modèle",
+ "es": "Modelo",
+ "pt": "Modelo"
+ }
+ },
+ {
+ "key": "gui.main_window.modelle_aktualisieren",
+ "canonical_source_value": "Modelle &aktualisieren",
+ "translations": {
+ "de": "Modelle &aktualisieren",
+ "zh": "更新&模型",
+ "en": "&Update models",
+ "ru": "&Обновить модели",
+ "fr": "&Mettre à jour les modèles",
+ "es": "Actualizar &modelos",
+ "bn": "মডেল &হালনাগাদ করুন",
+ "ja": "モデルを&更新",
+ "pt": "&Atualizar modelos",
+ "hi": "&मॉडल अपडेट करें",
+ "ar": "&تحديث النماذج"
+ }
+ },
+ {
+ "key": "gui.main_window.modelle_laden",
+ "canonical_source_value": "Modelle laden",
+ "translations": {
+ "de": "Modelle laden",
+ "es": "Cargar modelos",
+ "en": "Load models",
+ "zh": "加载模型",
+ "ru": "Загрузка моделей",
+ "fr": "Charger les modèles",
+ "pt": "Carregar modelos",
+ "ar": "تحميل النماذج",
+ "ja": "モデルを読み込む",
+ "bn": "মডেল লোড করুন",
+ "hi": "मॉडल लोड करें"
+ }
+ },
+ {
+ "key": "gui.main_window.modelle_werden_abgefragt",
+ "canonical_source_value": "Modelle werden abgefragt...",
+ "translations": {
+ "de": "Modelle werden abgefragt...",
+ "es": "Consultando modelos...",
+ "fr": "Les modèles sont en cours d'interrogation...",
+ "zh": "正在查询模型...",
+ "ja": "モデルを照会しています...",
+ "ar": " جارٍ استعلام النماذج...",
+ "ru": "Модели запрашиваются...",
+ "hi": "मॉडल से पूछताछ की जा रही है...",
+ "en": "Models are being queried...",
+ "pt": "Os modelos estão sendo consultados...",
+ "bn": "মডেলগুলোকে জিজ্ঞাসা করা হচ্ছে..."
+ }
+ },
+ {
+ "key": "gui.main_window.muss_auf_dem_ollama_server_installiert_sein_ollama_pul",
+ "canonical_source_value": "Muss auf dem Ollama-Server installiert sein (ollama pull ...)",
+ "translations": {
+ "de": "Muss auf dem Ollama-Server installiert sein (ollama pull ...)",
+ "en": "Must be installed on the Ollama server (ollama pull ...)",
+ "es": "Debe estar instalado en el servidor de Ollama (ollama pull ...)",
+ "ru": "Должен быть установлен на сервере Ollama (ollama pull ...)",
+ "zh": "必须安装在 Ollama 服务器上(ollama pull ...)",
+ "pt": "Deve estar instalado no servidor Ollama (ollama pull ...)",
+ "ar": "يجب أن يكون مثبتًا على خادم Ollama (ollama pull ...)",
+ "fr": "Doit être installé sur le serveur Ollama (ollama pull ...)",
+ "ja": "Ollama サーバーにインストールされている必要があります (ollama pull ...)",
+ "hi": "इसे Ollama सर्वर पर इंस्टॉल होना चाहिए (ollama pull ...)",
+ "bn": "এটি Ollama সার্ভারে ইনস্টল থাকতে হবে (ollama pull ...)"
+ }
+ },
+ {
+ "key": "gui.main_window.neuer_profilname",
+ "canonical_source_value": "Neuer Profilname:",
+ "translations": {
+ "de": "Neuer Profilname:",
+ "es": "Nuevo nombre de perfil:",
+ "en": "New profile name:",
+ "zh": "新配置文件名称:",
+ "hi": "नया प्रोफ़ाइल नाम:",
+ "ja": "新しいプロファイル名:",
+ "pt": "Novo nome de perfil:",
+ "bn": "নতুন প্রোফাইলের নাম:",
+ "fr": "Nouveau nom de profil :",
+ "ru": "Новое имя профиля:",
+ "ar": "اسم الملف الشخصي الجديد:"
+ }
+ },
+ {
+ "key": "gui.main_window.neues_projekt",
+ "canonical_source_value": "&Neues Projekt",
+ "translations": {
+ "de": "&Neues Projekt",
+ "ar": "&نشروع جديد",
+ "pt": "&Novo Projeto",
+ "en": "&Neues Projekt → &New Project",
+ "ja": "&新しいプロジェクト",
+ "hi": "&नया प्रोजेक्ट",
+ "ru": "&Новый проект",
+ "bn": "&Neues Projekt",
+ "fr": "&Nouveau projet",
+ "zh": "&Neues Projekt",
+ "es": "&Nuevo proyecto"
+ }
+ },
+ {
+ "key": "gui.main_window.neues_projekt.4bc9d33f",
+ "canonical_source_value": "Neues Projekt",
+ "translations": {
+ "de": "Neues Projekt",
+ "es": "Nuevo proyecto",
+ "en": "New Project",
+ "zh": "新项目",
+ "pt": "Novo Projeto",
+ "ru": "Новый проект",
+ "ja": "新しいプロジェクト",
+ "fr": "Nouveau projet",
+ "hi": "नया प्रोजेक्ट",
+ "ar": "مشروع جديد",
+ "bn": "নতুন প্রকল্প"
+ }
+ },
+ {
+ "key": "gui.main_window.neues_projekt_erstellt",
+ "canonical_source_value": "Neues Projekt erstellt",
+ "translations": {
+ "de": "Neues Projekt erstellt",
+ "hi": "नया प्रोजेक्ट बनाया गया",
+ "zh": "新建项目已创建",
+ "es": "Nuevo proyecto creado",
+ "ar": "تم إنشاء مشروع جديد",
+ "ja": "新しいプロジェクトを作成しました",
+ "pt": "Novo projeto criado",
+ "en": "New project created",
+ "fr": "Nouveau projet créé",
+ "ru": "Новый проект создан",
+ "bn": "নতুন প্রকল্প তৈরি হয়েছে"
+ }
+ },
+ {
+ "key": "gui.main_window.notespacellm_dokumenten_analyse_und_berichterstellung",
+ "canonical_source_value": "NoteSpaceLLM - Dokumenten-Analyse und Berichterstellung",
+ "translations": {
+ "de": "NoteSpaceLLM - Dokumenten-Analyse und Berichterstellung",
+ "ja": "NoteSpaceLLM - ドキュメント分析とレポート作成",
+ "en": "NoteSpaceLLM - Document Analysis and Report Generation",
+ "pt": "NoteSpaceLLM - Análise de documentos e geração de relatórios",
+ "ru": "NoteSpaceLLM - Анализ документов и создание отчётов",
+ "bn": "NoteSpaceLLM - দস্তাবেজ বিশ্লেষণ ও প্রতিবেদন তৈরি",
+ "fr": "NoteSpaceLLM - Analyse de documents et génération de rapports",
+ "zh": "NoteSpaceLLM - 文档分析与报告生成",
+ "hi": "NoteSpaceLLM - दस्तावेज़ विश्लेषण और रिपोर्ट निर्माण",
+ "es": "NoteSpaceLLM - Análisis de documentos y generación de informes",
+ "ar": "NoteSpaceLLM - تحليل المستندات وإنشاء التقارير"
+ }
+ },
+ {
+ "key": "gui.main_window.notespacellm_v1_0_0_ein_privater_notebooklm_clone_fur",
+ "canonical_source_value": "NoteSpaceLLM v1.0.0\n\nEin privater NotebookLM-Clone für lokale Dokumentenanalyse und Berichterstellung.\n\nFeatures:\n- Drag & Drop Dokumentenverwaltung\n- Detailrecherchen pro Dokument\n- Visuelle Workflow-Steuerung\n- LLM-gestützter Dokumenten-Chat\n- Multi-Format-Export",
+ "translations": {
+ "de": "NoteSpaceLLM v1.0.0\n\nEin privater NotebookLM-Clone für lokale Dokumentenanalyse und Berichterstellung.\n\nFeatures:\n- Drag & Drop Dokumentenverwaltung\n- Detailrecherchen pro Dokument\n- Visuelle Workflow-Steuerung\n- LLM-gestützter Dokumenten-Chat\n- Multi-Format-Export",
+ "en": "NoteSpaceLLM v1.0.0\n\nA private NotebookLM clone for local document analysis and report generation.\n\nFeatures:\n- Drag & drop document management\n- Detailed research per document\n- Visual workflow control\n- LLM-powered document chat\n- Multi-format export",
+ "ru": "NoteSpaceLLM v1.0.0\n\nЧастный клон NotebookLM для локального анализа документов и создания отчётов.\n\nВозможности:\n- Управление документами с помощью перетаскивания (Drag & Drop)\n- Подробные исследования по каждому документу\n- Визуальное управление рабочими процессами\n- Чат с документами на основе LLM\n- Экспорт в несколько форматов",
+ "fr": "NoteSpaceLLM v1.0.0\n\nUn clone privé de NotebookLM pour l'analyse de documents en local et la génération de rapports.\n\nFonctionnalités :\n- Gestion des documents par glisser-déposer\n- Recherches détaillées par document\n- Pilotage visuel des workflows\n- Chat avec les documents propulsé par LLM\n- Export multi-format",
+ "ar": "NoteSpaceLLM v1.0.0\n\nمنصة خاصة تشبه NotebookLM لتحليل المستندات محليًا وإنشاء التقارير.\n\nالميزات:\n- إدارة المستندات بالسحب والإفلات\n- أبحاث مفصلة لكل مستند\n- التحكم البصري في سير العمل\n- دردشة مع المستندات مدعومة بـ LLM\n- تصدير متعدد الصيغ",
+ "pt": "NoteSpaceLLM v1.0.0\n\nUm clone privado do NotebookLM para análise local de documentos e geração de relatórios.\n\nRecursos:\n- Gerenciamento de documentos por arrastar e soltar\n- Pesquisas detalhadas por documento\n- Controle visual de fluxo de trabalho\n- Chat com documentos apoiado por LLM\n- Exportação em múltiplos formatos",
+ "zh": "NoteSpaceLLM v1.0.0\n\n一款用于本地文档分析与报告生成的私有 NotebookLM 克隆版。\n\n功能:\n- 拖放式文档管理\n- 按文档进行的详细研究\n- 可视化工作流控制\n- 由 LLM 支持的文档对话\n- 多格式导出",
+ "bn": "NoteSpaceLLM v1.0.0\n\nএকটি ব্যক্তিগত NotebookLM-ক্লোন, যা স্থানীয় নথি বিশ্লেষণ এবং প্রতিবেদন তৈরির জন্য।\n\nবৈশিষ্ট্যসমূহ:\n- ড্র্যাগ ও ড্রপ নথি ব্যবস্থাপনা\n- প্রতিটি নথির জন্য বিস্তারিত অনুসন্ধান\n- ভিজ্যুয়াল ওয়ার্কফ্লো নিয়ন্ত্রণ\n- LLM-সমর্থিত নথি-চ্যাট\n- মাল্টি-ফরম্যাট এক্সপোর্ট",
+ "ja": "NoteSpaceLLM v1.0.0\n\nローカル文書の分析とレポート作成のための、プライベートなNotebookLMクローンです。\n\n機能:\n- ドラッグ&ドロップによる文書管理\n- 文書ごとの詳細な調査\n- ビジュアルなワークフロー制御\n- LLMを活用した文書チャット\n- 複数フォーマットへのエクスポート",
+ "es": "NoteSpaceLLM v1.0.0\n\nUn clon privado de NotebookLM para el análisis local de documentos y la generación de informes.\n\nFunciones:\n- Gestión de documentos mediante arrastrar y soltar\n- Investigaciones detalladas por documento\n- Control visual de flujos de trabajo\n- Chat de documentos impulsado por LLM\n- Exportación en múltiples formatos",
+ "hi": "NoteSpaceLLM v1.0.0\n\nएक निजी NotebookLM-क्लोन, जो स्थानीय दस्तावेज़ विश्लेषण और रिपोर्ट निर्माण के लिए है।\n\nविशेषताएँ:\n- ड्रैग और ड्रॉप दस्तावेज़ प्रबंधन\n- प्रति दस्तावेज़ विस्तृत शोध\n- दृश्य वर्कफ़्लो नियंत्रण\n- LLM-समर्थित दस्तावेज़-चैट\n- मल्टी-फ़ॉर्मेट निर्यात"
+ }
+ },
+ {
+ "key": "gui.main_window.ollama_schlussel_nicht_verfugbar_value",
+ "canonical_source_value": "Ollama-Schlüssel nicht verfügbar ({value1}).",
+ "translations": {
+ "de": "Ollama-Schlüssel nicht verfügbar ({value1}).",
+ "es": "La clave de Ollama no está disponible ({value1}).",
+ "en": "Ollama key not available ({value1}).",
+ "bn": "Ollama-কী উপলব্ধ নেই ({value1}).",
+ "zh": "Ollama 密钥不可用({value1})。",
+ "ja": "Ollama キーを利用できません ({value1})。",
+ "pt": "Chave Ollama não disponível ({value1}).",
+ "ru": "Ключ Ollama недоступен ({value1}).",
+ "ar": "مفتاح Ollama غير متاح ({value1}).",
+ "fr": "Clé Ollama indisponible ({value1}).",
+ "hi": "Ollama-कुंजी उपलब्ध नहीं है ({value1})।"
+ }
+ },
+ {
+ "key": "gui.main_window.ollama_schlussel_nicht_verfugbar_value.da4a241a",
+ "canonical_source_value": "Ollama-Schlüssel nicht verfügbar: {value1}",
+ "translations": {
+ "de": "Ollama-Schlüssel nicht verfügbar: {value1}",
+ "zh": "Ollama 密钥不可用:{value1}",
+ "fr": "Clé Ollama non disponible : {value1}",
+ "es": "Clave de Ollama no disponible: {value1}",
+ "ru": "Ключ Ollama недоступен: {value1}",
+ "bn": "Ollama কী উপলভ্য নয়: {value1}",
+ "pt": "Chave Ollama não disponível: {value1}",
+ "hi": "Ollama-कुंजी उपलब्ध नहीं है: {value1}",
+ "en": "Ollama key not available: {value1}",
+ "ja": "Ollama キーを利用できません: {value1}",
+ "ar": "مفتاح Ollama غير متاح: {value1}"
+ }
+ },
+ {
+ "key": "gui.main_window.ollama_url",
+ "canonical_source_value": "Ollama URL:",
+ "translations": {
+ "de": "Ollama URL:",
+ "ar": "Ollama URL:",
+ "hi": "Ollama URL:",
+ "zh": "Ollama URL:",
+ "en": "Ollama URL:",
+ "ru": "Ollama URL:",
+ "ja": "Ollama の URL:",
+ "pt": "URL do Ollama:",
+ "fr": "URL d'Ollama :",
+ "bn": "Ollama-এর URL:",
+ "es": "URL de Ollama:"
+ }
+ },
+ {
+ "key": "gui.main_window.ollama_value",
+ "canonical_source_value": "Ollama: {value1}",
+ "translations": {
+ "de": "Ollama: {value1}",
+ "es": "Ollama: {value1}",
+ "hi": "Ollama: {value1}",
+ "pt": "Ollama: {value1}",
+ "bn": "Ollama: {value1}",
+ "ru": "Ollama: {value1}",
+ "fr": "Ollama : {value1}",
+ "ja": "Ollama: {value1}",
+ "ar": "Ollama: {value1}",
+ "zh": "Ollama: {value1}",
+ "en": "Ollama: {value1}"
+ }
+ },
+ {
+ "key": "gui.main_window.optional",
+ "canonical_source_value": "optional",
+ "translations": {
+ "de": "optional",
+ "en": "optional",
+ "bn": "ঐচ্ছিক",
+ "hi": "वैकल्पिक",
+ "ru": "необязательно",
+ "ja": "オプション",
+ "es": "opcional",
+ "zh": "optional",
+ "fr": "optionnel",
+ "pt": "opcional",
+ "ar": "اختياري"
+ }
+ },
+ {
+ "key": "gui.main_window.ordner_hinzufugen",
+ "canonical_source_value": "Ordner hinzufügen...",
+ "translations": {
+ "de": "Ordner hinzufügen...",
+ "hi": "ओर्डनर हिंजुफ़ुगेन...",
+ "en": "Add folder...",
+ "bn": "ফোল্ডার যোগ করুন...",
+ "fr": "Ajouter un dossier...",
+ "ja": "フォルダを追加...",
+ "zh": "添加文件夹...",
+ "pt": "Adicionar pasta...",
+ "ru": "Добавить папку...",
+ "ar": "إضافة مجلد...",
+ "es": "Añadir carpeta..."
+ }
+ },
+ {
+ "key": "gui.main_window.pipeline_ubersicht",
+ "canonical_source_value": "Pipeline-Übersicht",
+ "translations": {
+ "de": "Pipeline-Übersicht",
+ "en": "Pipeline Overview",
+ "zh": "管道概览",
+ "ja": "パイプライン概要",
+ "hi": "पाइपलाइन अवलोकन",
+ "fr": "Vue d'ensemble du pipeline",
+ "ru": "Обзор конвейера",
+ "pt": "Visão geral do pipeline",
+ "es": "Descripción general de la canalización",
+ "ar": "نظرة عامة على خط المعالجة",
+ "bn": "পাইপলাইন ওভারভিউ"
+ }
+ },
+ {
+ "key": "gui.main_window.pipeline_ubersicht_erklart_alle_schritte",
+ "canonical_source_value": "Pipeline-Übersicht — Erklärt alle Schritte",
+ "translations": {
+ "de": "Pipeline-Übersicht — Erklärt alle Schritte",
+ "en": "Pipeline Overview — Explains all steps",
+ "ru": "Обзор конвейера — объясняет все шаги",
+ "ja": "パイプライン概要 — すべてのステップを説明",
+ "es": "Descripción general de la canalización — Explica todos los pasos",
+ "fr": "Aperçu du pipeline — Explique toutes les étapes",
+ "ar": "نظرة عامة على خط المعالجة — يشرح جميع الخطوات",
+ "bn": "পাইপলাইন-সংক্ষিপ্ত বিবরণ — সকল ধাপের ব্যাখ্যা",
+ "pt": "Visão geral do pipeline — Explica todas as etapas",
+ "zh": "流水线概览 — 说明所有步骤",
+ "hi": "पाइपलाइन का अवलोकन — सभी चरणों की व्याख्या"
+ }
+ },
+ {
+ "key": "gui.main_window.profil",
+ "canonical_source_value": "Profil:",
+ "translations": {
+ "de": "Profil:",
+ "hi": "प्रोफ़ाइल:",
+ "pt": "Perfil:",
+ "ar": "الملف الشخصي:",
+ "zh": "档案:",
+ "es": "Perfil:",
+ "ru": "Профиль:",
+ "bn": "প্রোফাইল:",
+ "ja": "プロフィール:",
+ "fr": "Profil :",
+ "en": "Profile:"
+ }
+ },
+ {
+ "key": "gui.main_window.profil_nicht_gespeichert",
+ "canonical_source_value": "Profil nicht gespeichert",
+ "translations": {
+ "de": "Profil nicht gespeichert",
+ "ru": "Профиль не сохранён",
+ "zh": "个人资料未保存",
+ "hi": "प्रोफ़ाइल सहेजी नहीं गई",
+ "en": "Profile not saved",
+ "es": "Perfil no guardado",
+ "ja": "プロフィールは保存されていません",
+ "fr": "Profil non enregistré",
+ "bn": "প্রোফাইল সংরক্ষিত হয়নি",
+ "pt": "Perfil não salvo",
+ "ar": "لم يتم حفظ الملف الشخصي"
+ }
+ },
+ {
+ "key": "gui.main_window.profil_speichern",
+ "canonical_source_value": "Profil speichern",
+ "translations": {
+ "de": "Profil speichern",
+ "zh": "保存配置文件",
+ "pt": "Salvar perfil",
+ "ja": "プロフィールを保存",
+ "en": "Save profile",
+ "ru": "Сохранить профиль",
+ "bn": "প্রোফাইল সংরক্ষণ করুন",
+ "fr": "Enregistrer le profil",
+ "es": "Guardar perfil",
+ "hi": "प्रोफ़ाइल सहेजें",
+ "ar": "احفظ الملف الشخصي"
+ }
+ },
+ {
+ "key": "gui.main_window.profil_umbenennen",
+ "canonical_source_value": "Profil umbenennen",
+ "translations": {
+ "de": "Profil umbenennen",
+ "ja": "プロフィール名を変更",
+ "en": "Rename profile",
+ "es": "Cambiar nombre del perfil",
+ "ar": "إعادة تسمية الملف الشخصي",
+ "zh": "重命名配置文件",
+ "ru": "Переименовать профиль",
+ "fr": "Renommer le profil",
+ "pt": "Renomear perfil",
+ "hi": "प्रोफ़ाइल का नाम बदलें",
+ "bn": "প্রোফাইলের নাম পরিবর্তন করুন"
+ }
+ },
+ {
+ "key": "gui.main_window.profil_value_geloscht",
+ "canonical_source_value": "Profil '{value1}' gelöscht",
+ "translations": {
+ "de": "Profil '{value1}' gelöscht",
+ "ar": "تم حذف الملف الشخصي '{value1}'",
+ "zh": "{value1} 配置文件已删除",
+ "en": "Profile '{value1}' deleted",
+ "bn": "প্রোফাইল '{value1}' মুছে ফেলা হয়েছে",
+ "pt": "Perfil '{value1}' excluído",
+ "fr": "Profil « {value1} » supprimé",
+ "ja": "プロフィール '{value1}' が削除されました",
+ "es": "Perfil '{value1}' eliminado",
+ "ru": "Профиль '{value1}' удалён",
+ "hi": "प्रोफ़ाइल '{value1}' हटाई गई"
+ }
+ },
+ {
+ "key": "gui.main_window.profil_value_gespeichert",
+ "canonical_source_value": "Profil '{value1}' gespeichert",
+ "translations": {
+ "de": "Profil '{value1}' gespeichert",
+ "ar": "تم حفظ الملف الشخصي '{value1}'",
+ "bn": "প্রোফাইল '{value1}' সংরক্ষিত হয়েছে",
+ "es": "Perfil '{value1}' guardado",
+ "ru": "Профиль «{value1}» сохранён",
+ "en": "Profile '{value1}' saved",
+ "ja": "プロファイル '{value1}' が保存されました",
+ "pt": "Perfil '{value1}' salvo",
+ "zh": "Profil '{value1}' gespeichert",
+ "hi": "प्रोफ़ाइल '{value1}' सहेजी गई",
+ "fr": "Profil '{value1}' enregistré"
+ }
+ },
+ {
+ "key": "gui.main_window.profilname",
+ "canonical_source_value": "Profilname:",
+ "translations": {
+ "de": "Profilname:",
+ "pt": "Nome do perfil:",
+ "en": "Profile name:",
+ "ar": "اسم الملف الشخصي:",
+ "ru": "Имя профиля:",
+ "fr": "Nom du profil :",
+ "ja": "プロファイル名:",
+ "bn": "প্রোফাইলের নাম:",
+ "hi": "प्रोफ़ाइल नाम:",
+ "es": "Nombre del perfil:",
+ "zh": "Profilname:"
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_gespeichert",
+ "canonical_source_value": "Projekt gespeichert",
+ "translations": {
+ "de": "Projekt gespeichert",
+ "pt": "Projeto salvo",
+ "bn": "প্রকল্প সংরক্ষিত হয়েছে",
+ "ru": "Проект сохранён",
+ "en": "Project saved",
+ "ja": "プロジェクトを保存しました",
+ "fr": "Projet enregistré",
+ "es": "Proyecto guardado",
+ "zh": "项目已保存",
+ "hi": "प्रोजेक्ट सहेजा गया",
+ "ar": "تم حفظ المشروع"
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_konnte_nicht_geoffnet_werden",
+ "canonical_source_value": "Projekt konnte nicht geöffnet werden",
+ "translations": {
+ "de": "Projekt konnte nicht geöffnet werden",
+ "fr": "Le projet n'a pas pu être ouvert",
+ "en": "Project could not be opened",
+ "ru": "Не удалось открыть проект",
+ "zh": "无法打开项目",
+ "ja": "プロジェクトを開けませんでした",
+ "es": "No se pudo abrir el proyecto",
+ "bn": "প্রজেক্ট খোলা যায়নি",
+ "pt": "Não foi possível abrir o projeto",
+ "ar": "لم يتمكن من فتح المشروع",
+ "hi": "प्रोजेक्ट खोलना संभव नहीं हुआ"
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_offnen",
+ "canonical_source_value": "Projekt &Öffnen...",
+ "translations": {
+ "de": "Projekt &Öffnen...",
+ "hi": "प्रोजेक्ट &खोलें...",
+ "fr": "Ouvrir le projet...",
+ "pt": "Projekt &Öffnen...",
+ "zh": "项目&打开...",
+ "en": "Open Project...",
+ "es": "Proyecto &Abrir...",
+ "ru": "Проект &Открыть...",
+ "bn": "&প্রকল্প খুলুন...",
+ "ja": "プロジェクトを&開く...",
+ "ar": "&فتح المشروع..."
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_offnen.d8d03432",
+ "canonical_source_value": "Projekt öffnen",
+ "translations": {
+ "de": "Projekt öffnen",
+ "ru": "Открыть проект",
+ "es": "Abrir proyecto",
+ "zh": "项目打开",
+ "en": "Open project",
+ "ar": "فتح المشروع",
+ "pt": "Abrir projeto",
+ "bn": "প্রজেক্ট খুলুন",
+ "fr": "Ouvrir le projet",
+ "ja": "プロジェクトを開く",
+ "hi": "प्रोजेक्ट खोलें"
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_speichern",
+ "canonical_source_value": "Projekt &speichern",
+ "translations": {
+ "de": "Projekt &speichern",
+ "ar": "حفظ المشروع",
+ "ru": "Сохранить проект &speichern",
+ "ja": "Projekt &speichern",
+ "en": "Save project &speichern",
+ "fr": "Projekt &speichern",
+ "zh": "Projekt speichern",
+ "pt": "Salvar projeto &",
+ "bn": "&প্রকল্প সংরক্ষণ করুন",
+ "es": "&Guardar proyecto",
+ "hi": "&प्रोजेक्ट सहेजें"
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_value_erstellt",
+ "canonical_source_value": "Projekt '{value1}' erstellt",
+ "translations": {
+ "de": "Projekt '{value1}' erstellt",
+ "hi": "{value1} प्रोजेक्ट बनाया गया",
+ "es": "Proyecto '{value1}' creado",
+ "fr": "Projekt « {value1} » créé",
+ "ar": "تم إنشاء المشروع '{value1}'",
+ "ja": "Projekt '{value1}' wurde erstellt",
+ "zh": "项目 '{value1}' 已创建",
+ "bn": "Projekt '{value1}' erstellt",
+ "en": "Project '{value1}' created",
+ "pt": "Projeto '{value1}' criado",
+ "ru": "Проект «{value1}» создан"
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_value_geoffnet",
+ "canonical_source_value": "Projekt '{value1}' geöffnet",
+ "translations": {
+ "de": "Projekt '{value1}' geöffnet",
+ "ar": "تم فتح المشروع '{value1}'",
+ "bn": "{value1} প্রকল্পটি খোলা হয়েছে",
+ "ru": "Проект «{value1}» открыт",
+ "hi": "{value1} परियोजना खोली गई",
+ "fr": "Projekt '{value1}' geöffnet",
+ "pt": "Projeto '{value1}' aberto",
+ "zh": "项目 '{value1}' 已打开",
+ "ja": "プロジェクト '{value1}' が開かれました",
+ "en": "Project '{value1}' opened",
+ "es": "Proyecto '{value1}' abierto"
+ }
+ },
+ {
+ "key": "gui.main_window.projekt_wahlen",
+ "canonical_source_value": "Projekt wählen:",
+ "translations": {
+ "de": "Projekt wählen:",
+ "ru": "Выберите проект:",
+ "ja": "プロジェクトを選択:",
+ "pt": "Selecione o projeto:",
+ "bn": "প্রকল্প নির্বাচন করুন:",
+ "es": "Seleccione un proyecto:",
+ "hi": "प्रोजेक्ट चुनें:",
+ "fr": "Sélectionnez un projet :",
+ "zh": "选择项目:",
+ "ar": "اختر المشروع:",
+ "en": "Select a project:"
+ }
+ },
+ {
+ "key": "gui.main_window.projektname",
+ "canonical_source_value": "Projektname:",
+ "translations": {
+ "de": "Projektname:",
+ "ru": "Название проекта:",
+ "hi": "Projektname:",
+ "pt": "Nome do projeto:",
+ "ja": "Projektname:",
+ "bn": "Projektname:",
+ "en": "Project name:",
+ "ar": "اسم المشروع:",
+ "fr": "Nom du projet :",
+ "es": "Nombre del proyecto:",
+ "zh": "项目名称:"
+ }
+ },
+ {
+ "key": "gui.main_window.prompt_export",
+ "canonical_source_value": "Prompt-Export",
+ "translations": {
+ "de": "Prompt-Export",
+ "hi": "Prompt-Export",
+ "ja": "Prompt-Export",
+ "fr": "Prompt-Export",
+ "zh": "Prompt 导出",
+ "en": "Prompt-Export",
+ "pt": "Exportação de Prompt",
+ "bn": "Prompt-Export",
+ "ar": "تصدير-المطالبة",
+ "ru": "Экспорт промпта",
+ "es": "Exportación de prompts"
+ }
+ },
+ {
+ "key": "gui.main_window.prompt_exportieren",
+ "canonical_source_value": "Prompt exportieren",
+ "translations": {
+ "de": "Prompt exportieren",
+ "pt": "Exportar prompt",
+ "fr": "Exporter le prompt",
+ "es": "Exportar prompt",
+ "en": "Export prompt",
+ "ar": "تصدير المطالبة",
+ "hi": "प्रॉम्प्ट निर्यात करें",
+ "ja": "プロンプトをエクスポート",
+ "bn": "প্রম্পট রপ্তানি করুন",
+ "zh": "导出提示词",
+ "ru": "Экспортировать промпт"
+ }
+ },
+ {
+ "key": "gui.main_window.prompt_exportiert_value",
+ "canonical_source_value": "Prompt exportiert: {value1}",
+ "translations": {
+ "de": "Prompt exportiert: {value1}",
+ "fr": "Prompt exporté : {value1}",
+ "pt": "Prompt exportiert: {value1}",
+ "ja": "エクスポートされたプロンプト: {value1}",
+ "ru": "Промпт экспортирован: {value1}",
+ "zh": "导出的提示词:{value1}",
+ "es": "Prompt exportiert: {value1}",
+ "hi": "प्रॉम्प्ट निर्यात किया गया: {value1}",
+ "en": "Prompt exported: {value1}",
+ "bn": "Prompt exportiert: {value1}",
+ "ar": "تم تصدير الموجه: {value1}"
+ }
+ },
+ {
+ "key": "gui.main_window.provider",
+ "canonical_source_value": "Provider",
+ "translations": {
+ "de": "Provider",
+ "ar": "المزوّد",
+ "zh": "提供方",
+ "bn": "Provider",
+ "ja": "Provider",
+ "pt": "Provedor",
+ "ru": "Provider",
+ "fr": "Fournisseur",
+ "es": "Proveedor",
+ "en": "Provider",
+ "hi": "प्रदाता"
+ }
+ },
+ {
+ "key": "gui.main_window.provider.f60f8140",
+ "canonical_source_value": "Provider:",
+ "translations": {
+ "de": "Provider:",
+ "zh": "Provider:",
+ "ja": "Provider:",
+ "ar": "المزوّد:",
+ "bn": "Provider:",
+ "ru": "Провайдер:",
+ "fr": "Fournisseur :",
+ "es": "Proveedor:",
+ "hi": "Provider:",
+ "en": "Provider:",
+ "pt": "Fornecedor:"
+ }
+ },
+ {
+ "key": "gui.main_window.rag",
+ "canonical_source_value": "&RAG",
+ "translations": {
+ "de": "&RAG",
+ "en": "&RAG",
+ "es": "&RAG",
+ "zh": "&RAG",
+ "ja": "&RAG",
+ "ru": "&RAG",
+ "fr": "&RAG",
+ "hi": "&RAG",
+ "ar": "&RAG",
+ "bn": "&RAG",
+ "pt": "&RAG"
+ }
+ },
+ {
+ "key": "gui.main_window.rag.1879da55",
+ "canonical_source_value": "RAG",
+ "translations": {
+ "de": "RAG",
+ "en": "RAG",
+ "es": "RAG",
+ "zh": "RAG",
+ "ja": "RAG",
+ "ru": "RAG",
+ "fr": "RAG",
+ "hi": "RAG",
+ "ar": "RAG",
+ "bn": "RAG",
+ "pt": "RAG"
+ }
+ },
+ {
+ "key": "gui.main_window.rag_engine_bereit_value",
+ "canonical_source_value": "RAG-Engine bereit ({value1})",
+ "translations": {
+ "de": "RAG-Engine bereit ({value1})",
+ "zh": "RAG-引擎已就绪 ({value1})",
+ "es": "RAG-Motor listo ({value1})",
+ "ar": "RAG-المحرك جاهز ({value1})",
+ "en": "RAG-Engine ready ({value1})",
+ "hi": "RAG-इंजन तैयार है ({value1})",
+ "fr": "Moteur RAG prêt ({value1})",
+ "ru": "RAG-движок готов ({value1})",
+ "bn": "RAG-ইঞ্জিন প্রস্তুত ({value1})",
+ "ja": "RAG-Engine 準備完了 ({value1})",
+ "pt": "Mecanismo RAG pronto ({value1})"
+ }
+ },
+ {
+ "key": "gui.main_window.rag_engine_nicht_verfugbar",
+ "canonical_source_value": "RAG Engine nicht verfügbar.",
+ "translations": {
+ "de": "RAG Engine nicht verfügbar.",
+ "es": "El motor RAG no está disponible.",
+ "ar": "RAG المحرّك غير متاح.",
+ "ja": "RAG エンジンが利用できません。",
+ "zh": "RAG 引擎不可用。",
+ "ru": "Движок RAG недоступен.",
+ "en": "RAG Engine not available.",
+ "pt": "O mecanismo RAG não está disponível.",
+ "bn": "RAG ইঞ্জিন উপলব্ধ নেই।",
+ "hi": "RAG इंजन उपलब्ध नहीं है।",
+ "fr": "Le moteur RAG n'est pas disponible."
+ }
+ },
+ {
+ "key": "gui.main_window.rag_engine_nicht_verfugbar_value",
+ "canonical_source_value": "RAG-Engine nicht verfügbar ({value1}).",
+ "translations": {
+ "de": "RAG-Engine nicht verfügbar ({value1}).",
+ "ru": "RAG-движок недоступен ({value1}).",
+ "ja": "RAG-Engine が利用できません ({value1})。",
+ "ar": "RAG-المحرك غير متاح ({value1}).",
+ "es": "El motor RAG no está disponible ({value1}).",
+ "zh": "RAG-引擎不可用({value1})。",
+ "bn": "RAG-ইঞ্জিন উপলব্ধ নেই ({value1}).",
+ "pt": "O mecanismo RAG não está disponível ({value1}).",
+ "en": "RAG-Engine not available ({value1}).",
+ "hi": "RAG-इंजन उपलब्ध नहीं है ({value1})।",
+ "fr": "Le moteur RAG n'est pas disponible ({value1})."
+ }
+ },
+ {
+ "key": "gui.main_window.rag_engine_wird_verbunden",
+ "canonical_source_value": "RAG-Engine wird verbunden …",
+ "translations": {
+ "de": "RAG-Engine wird verbunden …",
+ "en": "Connecting to the RAG engine …",
+ "hi": "RAG इंजन से कनेक्ट हो रहा है…",
+ "bn": "RAG ইঞ্জিনে সংযোগ করা হচ্ছে…",
+ "ar": "جارٍ الاتصال بمحرك RAG…",
+ "ru": "Подключение к движку RAG …",
+ "zh": "正在连接 RAG 引擎……",
+ "ja": "RAG エンジンに接続しています…",
+ "es": "Conectando con el motor RAG …",
+ "fr": "Connexion au moteur RAG…",
+ "pt": "Conectando ao mecanismo RAG…"
+ }
+ },
+ {
+ "key": "gui.main_window.rag_index_geleert",
+ "canonical_source_value": "RAG-Index geleert",
+ "translations": {
+ "de": "RAG-Index geleert",
+ "ru": "Индекс RAG очищен",
+ "zh": "RAG 索引已清空",
+ "es": "Índice RAG vaciado",
+ "bn": "RAG সূচি খালি করা হয়েছে",
+ "ar": "تم إفراغ فهرس RAG",
+ "hi": "RAG इंडेक्स खाली कर दिया गया",
+ "pt": "Índice RAG esvaziado",
+ "en": "RAG index cleared",
+ "ja": "RAG インデックスを消去しました",
+ "fr": "Index RAG vidé"
+ }
+ },
+ {
+ "key": "gui.main_window.rag_statistiken",
+ "canonical_source_value": "RAG-Statistiken...",
+ "translations": {
+ "de": "RAG-Statistiken...",
+ "hi": "RAG-आंकड़े...",
+ "ar": "RAG-إحصاءات...",
+ "ru": "RAG-статистика...",
+ "es": "RAG-Estadísticas...",
+ "fr": "RAG-Statistiques...",
+ "ja": "RAGの統計…",
+ "en": "RAG-statistics...",
+ "zh": "RAG-统计数据...",
+ "pt": "RAG-Estatísticas...",
+ "bn": "RAG-পরিসংখ্যান..."
+ }
+ },
+ {
+ "key": "gui.main_window.rag_statistiken.8078882a",
+ "canonical_source_value": "RAG-Statistiken",
+ "translations": {
+ "de": "RAG-Statistiken",
+ "bn": "RAG-পরিসংখ্যান",
+ "es": "RAG-Estadísticas",
+ "ru": "RAG — статистика",
+ "zh": "RAG-统计数据",
+ "fr": "RAG-Statistiques",
+ "pt": "RAG-Estatísticas",
+ "hi": "RAG-आंकड़े",
+ "ja": "RAG-統計",
+ "ar": "RAG-الإحصاءات",
+ "en": "RAG Statistics"
+ }
+ },
+ {
+ "key": "gui.main_window.rag_value_value_dokumente_indexiert",
+ "canonical_source_value": "RAG: {value1}/{value2} Dokumente indexiert",
+ "translations": {
+ "de": "RAG: {value1}/{value2} Dokumente indexiert",
+ "ar": "RAG: تمت فهرسة {value1}/{value2} من المستندات",
+ "fr": "RAG : {value1}/{value2} documents indexés",
+ "hi": "RAG: {value1}/{value2} दस्तावेज़ अनुक्रमित किए गए",
+ "ru": "RAG: Проиндексировано документов: {value1}/{value2}",
+ "es": "RAG: {value1}/{value2} documentos indexados",
+ "pt": "RAG: {value1}/{value2} documentos indexados",
+ "ja": "RAG: {value1}/{value2} ドキュメントをインデックス化しました",
+ "en": "RAG: {value1}/{value2} documents indexed",
+ "zh": "RAG:已索引 {value1}/{value2} 文档",
+ "bn": "RAG: {value1}/{value2} ডকুমেন্ট ইনডেক্স করা হয়েছে"
+ }
+ },
+ {
+ "key": "gui.main_window.schlieen",
+ "canonical_source_value": "Schließen",
+ "translations": {
+ "de": "Schließen",
+ "zh": "关闭",
+ "fr": "Fermer",
+ "ar": "إغلاق",
+ "en": "Close",
+ "hi": "बंद करें",
+ "pt": "Fechar",
+ "ru": "Закрыть",
+ "bn": "বন্ধ করুন",
+ "ja": "閉じる",
+ "es": "Cerrar"
+ }
+ },
+ {
+ "key": "gui.main_window.schlussel_migrieren",
+ "canonical_source_value": "Schlüssel migrieren",
+ "translations": {
+ "de": "Schlüssel migrieren",
+ "bn": "ক্রিপ্টোগ্রাফিক কী স্থানান্তর করা",
+ "pt": "Migrar chave",
+ "es": "Migrar la clave",
+ "ja": "鍵の移行",
+ "ar": "نقل المفتاح",
+ "fr": "Migrer la clé",
+ "en": "Migrate key",
+ "zh": "迁移密钥",
+ "ru": "Миграция ключа",
+ "hi": "कुंजी स्थानांतरित करें"
+ }
+ },
+ {
+ "key": "gui.main_window.schlusselstatus",
+ "canonical_source_value": "Schlüsselstatus:",
+ "translations": {
+ "de": "Schlüsselstatus:",
+ "fr": "Statut de la clé :",
+ "es": "Estado de la clave:",
+ "en": "Key status:",
+ "zh": "密钥状态:",
+ "hi": "कुंजी की स्थिति:",
+ "bn": "কী-এর অবস্থা:",
+ "ja": "キーステータス:",
+ "ar": "حالة المفتاح:",
+ "pt": "Estado da chave:",
+ "ru": "Статус ключа:"
+ }
+ },
+ {
+ "key": "gui.main_window.schritt_1_text_aus_dokumenten_extrahieren_lauft_im_hin",
+ "canonical_source_value": "Schritt 1 — Text aus Dokumenten extrahieren\nLäuft im Hintergrund, die GUI bleibt reaktionsfähig.\nNotwendig vor Analyse und Bericht-Erstellung.",
+ "translations": {
+ "de": "Schritt 1 — Text aus Dokumenten extrahieren\nLäuft im Hintergrund, die GUI bleibt reaktionsfähig.\nNotwendig vor Analyse und Bericht-Erstellung.",
+ "zh": "步骤 1 — 从文档中提取文本\n在后台运行,GUI 保持响应。\n这是分析和报告生成之前的必要步骤。",
+ "ar": "الخطوة 1 — استخراج النص من المستندات\nتعمل في الخلفية، وتظل GUI مستجيبة.\nضرورية قبل التحليل وإعداد التقرير.",
+ "fr": "Étape 1 — Extraire le texte des documents\nS'exécute en arrière-plan, la GUI reste réactive.\nNécessaire avant l'analyse et la création du rapport.",
+ "ja": "ステップ1 — ドキュメントからテキストを抽出する\nバックグラウンドで実行され、GUI は応答性を保ちます。\n分析およびレポート作成の前に必要です。",
+ "es": "Paso 1 — Extraer texto de los documentos\nSe ejecuta en segundo plano; la GUI sigue respondiendo.\nNecesario antes del análisis y la generación del informe.",
+ "en": "Step 1 — Extract text from documents\nRuns in the background, the GUI remains responsive.\nNecessary before analysis and report generation.",
+ "bn": "ধাপ ১ — নথি থেকে টেক্সট বের করা\nGUI প্রতিক্রিয়াশীল থাকাকালীন এটি পটভূমিতে চলে।\nবিশ্লেষণ ও প্রতিবেদন তৈরির আগে এটি আবশ্যক।",
+ "hi": "चरण 1 — दस्तावेज़ों से पाठ निकालना\nयह पृष्ठभूमि में चलता है, और GUI प्रतिक्रियाशील बना रहता है।\nविश्लेषण और रिपोर्ट निर्माण से पहले आवश्यक।",
+ "pt": "Passo 1 — Extrair texto de documentos\nExecuta em segundo plano; a GUI permanece responsiva.\nNecessário antes da análise e da geração do relatório.",
+ "ru": "Шаг 1 — Извлечение текста из документов\nРаботает в фоновом режиме, интерфейс GUI остаётся отзывчивым.\nНеобходимо перед анализом и созданием отчёта."
+ }
+ },
+ {
+ "key": "gui.main_window.schritt_2_optional_detailrecherchen_per_llm_beantworte",
+ "canonical_source_value": "Schritt 2 (optional) — Detailrecherchen per LLM\nBeantwortet die sub-queries pro Dokument (Rechtsklick auf Dokument).\nKann lange dauern — läuft im Hintergrund.",
+ "translations": {
+ "de": "Schritt 2 (optional) — Detailrecherchen per LLM\nBeantwortet die sub-queries pro Dokument (Rechtsklick auf Dokument).\nKann lange dauern — läuft im Hintergrund.",
+ "zh": "第 2 步(可选)— 通过 LLM 进行详细研究\n回答每个文档的子查询(右键点击文档)。\n可能耗时较长——在后台运行。",
+ "ja": "ステップ2(任意)— LLM による詳細調査\n文書ごとのサブクエリに回答します(文書を右クリック)。\n長時間かかる場合があります — バックグラウンドで実行されます。",
+ "es": "Paso 2 (opcional) — Investigaciones detalladas mediante LLM\nResponde a las subconsultas por documento (clic derecho en el documento).\nPuede tardar mucho — se ejecuta en segundo plano.",
+ "pt": "Passo 2 (opcional) — Pesquisas detalhadas por meio de LLM\nResponde às sub-consultas por documento (clique com o botão direito no documento).\nPode levar muito tempo — é executado em segundo plano.",
+ "ru": "Шаг 2 (необязательный) — Подробные исследования через LLM\nОтвечает на подзапросы по каждому документу (щёлкните правой кнопкой мыши по документу).\nМожет занять много времени — выполняется в фоновом режиме.",
+ "hi": "चरण 2 (वैकल्पिक) — LLM के माध्यम से विस्तृत शोध\nप्रत्येक दस्तावेज़ के लिए उप-प्रश्नों के उत्तर देता है (दस्तावेज़ पर राइट-क्लिक करें)।\nइसमें लंबा समय लग सकता है — यह पृष्ठभूमि में चलता है।",
+ "en": "Step 2 (optional) — Detailed research via LLM\nAnswers the sub-queries per document (right-click on the document).\nCan take a long time — runs in the background.",
+ "ar": "الخطوة 2 (اختيارية) — أبحاث تفصيلية عبر LLM\nتجيب عن الاستعلامات الفرعية لكل مستند (انقر بزر الماوس الأيمن على المستند).\nقد تستغرق وقتًا طويلًا — تعمل في الخلفية.",
+ "fr": "Étape 2 (facultative) — Recherches détaillées par LLM\nRépond aux sous-requêtes par document (clic droit sur le document).\nPeut prendre beaucoup de temps — s'exécute en arrière-plan.",
+ "bn": "ধাপ ২ (ঐচ্ছিক) — LLM-এর মাধ্যমে বিস্তারিত গবেষণা\nপ্রতি নথির উপ-প্রশ্নগুলোর উত্তর দেয় (নথিতে ডান-ক্লিক করুন)।\nএতে অনেক সময় লাগতে পারে — এটি পটভূমিতে চলে।"
+ }
+ },
+ {
+ "key": "gui.main_window.schritt_3_bericht_per_llm_generieren_lost_fehlende_tex",
+ "canonical_source_value": "Schritt 3 — Bericht per LLM generieren\nLöst fehlende Textextraktion automatisch aus.\nNutzt Hauptfragestellung + Detailanalysen aus dem Workflow-Panel.",
+ "translations": {
+ "de": "Schritt 3 — Bericht per LLM generieren\nLöst fehlende Textextraktion automatisch aus.\nNutzt Hauptfragestellung + Detailanalysen aus dem Workflow-Panel.",
+ "ru": "Шаг 3 — Создать отчёт по LLM\nАвтоматически запускает извлечение текста, если он отсутствует.\nИспользует основной вопрос и подробные анализы из панели рабочего процесса.",
+ "ja": "ステップ3 — LLM でレポートを生成\n不足しているテキスト抽出を自動的に実行します。\nワークフローパネルのメインの質問とその詳細分析を使用します。",
+ "ar": "الخطوة 3 — إنشاء التقرير عبر LLM\nيُطلق استخراج النص المفقود تلقائياً.\nيستخدم السؤال الرئيسي وتحليلات التفاصيل من لوحة سير العمل.",
+ "pt": "Passo 3 — Gerar relatório por LLM\nAciona automaticamente a extração de texto ausente.\nUtiliza a pergunta principal + as análises detalhadas do painel de fluxo de trabalho.",
+ "hi": "चरण 3 — LLM के माध्यम से रिपोर्ट उत्पन्न करें\nगायब पाठ निष्कर्षण को स्वचालित रूप से ट्रिगर करता है।\nवर्कफ़्लो पैनल से मुख्य प्रश्न और विस्तृत विश्लेषण का उपयोग करता है।",
+ "es": "Paso 3 — Generar informe mediante LLM\nActiva automáticamente la extracción de texto que falte.\nUtiliza la pregunta principal y los análisis detallados del panel de flujo de trabajo.",
+ "fr": "Étape 3 — Générer le rapport via LLM\nDéclenche automatiquement l'extraction de texte manquante.\nUtilise la question principale et les analyses détaillées du panneau de workflow.",
+ "en": "Step 3 — Generate report via LLM\nAutomatically triggers missing text extraction.\nUses main question + detailed analyses from the workflow panel.",
+ "zh": "步骤 3 — 通过 LLM 生成报告\n自动触发缺失的文本提取。\n使用工作流面板中的主问题与详细分析。",
+ "bn": "ধাপ ৩ — LLM-এর মাধ্যমে প্রতিবেদন তৈরি করুন\nঅনুপস্থিত পাঠ্য নিষ্কাশন স্বয়ংক্রিয়ভাবে শুরু করে।\nওয়ার্কফ্লো প্যানেল থেকে মূল প্রশ্ন + বিস্তারিত বিশ্লেষণ ব্যবহার করে।"
+ }
+ },
+ {
+ "key": "gui.main_window.so_funktioniert_notespacellm_in_5_schritten",
+ "canonical_source_value": "So funktioniert NoteSpaceLLM in 5 Schritten",
+ "translations": {
+ "de": "So funktioniert NoteSpaceLLM in 5 Schritten",
+ "ja": "NoteSpaceLLMの使い方は5つのステップで説明できます。",
+ "es": "Así funciona NoteSpaceLLM en 5 pasos",
+ "pt": "Como o NoteSpaceLLM funciona em 5 passos",
+ "fr": "Voici comment NoteSpaceLLM fonctionne en 5 étapes",
+ "bn": "NoteSpaceLLM-এর কাজ করার পদ্ধতি ৫টি ধাপে",
+ "hi": "NoteSpaceLLM 5 चरणों में कैसे काम करता है",
+ "ru": "Как работает NoteSpaceLLM: 5 шагов",
+ "ar": "كيف يعمل NoteSpaceLLM في 5 خطوات",
+ "en": "How NoteSpaceLLM works in 5 steps",
+ "zh": "NoteSpaceLLM 的工作原理:5 个步骤"
+ }
+ },
+ {
+ "key": "gui.main_window.speichern_als",
+ "canonical_source_value": "Speichern als...",
+ "translations": {
+ "de": "Speichern als...",
+ "ar": "حفظ باسم...",
+ "hi": "इस रूप में सहेजें...",
+ "ru": "Сохранить как...",
+ "zh": "另存为...",
+ "en": "Save as...",
+ "pt": "Salvar como...",
+ "fr": "Enregistrer sous...",
+ "ja": "名前を付けて保存...",
+ "es": "Guardar como...",
+ "bn": "নাম দিয়ে সংরক্ষণ করুন..."
+ }
+ },
+ {
+ "key": "gui.main_window.standard_modelle_editierbar",
+ "canonical_source_value": "Standard-Modelle (editierbar)",
+ "translations": {
+ "de": "Standard-Modelle (editierbar)",
+ "ja": "標準モデル(編集可能)",
+ "en": "Standard models (editable)",
+ "zh": "标准模型(可编辑)",
+ "fr": "Modèles standard (modifiables)",
+ "bn": "Standard-Modelle (editierbar)",
+ "ru": "Стандартные модели (редактируемые)",
+ "pt": "Modelos padrão (editáveis)",
+ "es": "Modelos estándar (editables)",
+ "ar": "نماذج قياسية (قابلة للتحرير)",
+ "hi": "मानक मॉडल (संपादन योग्य)"
+ }
+ },
+ {
+ "key": "gui.main_window.text",
+ "canonical_source_value": "?",
+ "translations": {
+ "de": "?",
+ "en": "?",
+ "es": "?",
+ "zh": "?",
+ "ja": "?",
+ "ru": "?",
+ "fr": "?",
+ "hi": "?",
+ "ar": "?",
+ "bn": "?",
+ "pt": "?"
+ }
+ },
+ {
+ "key": "gui.main_window.text_extrahieren",
+ "canonical_source_value": "Text extrahieren",
+ "translations": {
+ "de": "Text extrahieren",
+ "ru": "Извлечь текст",
+ "zh": "提取文本",
+ "fr": "Extraire le texte",
+ "es": "Extraer texto",
+ "pt": "Extrair texto",
+ "ja": "テキストを抽出する",
+ "en": "Extract text",
+ "hi": "टेक्स्ट निकालें",
+ "ar": "استخراج النص",
+ "bn": "পাঠ্য বের করুন"
+ }
+ },
+ {
+ "key": "gui.main_window.textextraktion_abgeschlossen_value_dokumente",
+ "canonical_source_value": "Textextraktion abgeschlossen: {value1} Dokumente",
+ "translations": {
+ "de": "Textextraktion abgeschlossen: {value1} Dokumente",
+ "en": "Text extraction completed: {value1} documents",
+ "ar": "اكتمل استخراج النص: {value1} مستندات",
+ "bn": "{value1} নথির পাঠ্য নিষ্কাশন সম্পন্ন হয়েছে।",
+ "zh": "文本提取已完成:{value1} 份文档",
+ "ja": "テキスト抽出が完了しました: {value1} ドキュメント",
+ "es": "Extracción de texto completada: {value1} documentos",
+ "ru": "Извлечение текста завершено: {value1} документов",
+ "pt": "Extração de texto concluída: {value1} documentos",
+ "hi": "{value1} दस्तावेज़ों का पाठ निष्कर्षण पूरा हुआ।",
+ "fr": "Extraction de texte terminée : {value1} documents"
+ }
+ },
+ {
+ "key": "gui.main_window.tipp_bewege_den_mauszeiger_uber_die_toolbar_buttons_fu",
+ "canonical_source_value": "💡 Tipp: Bewege den Mauszeiger über die Toolbar-Buttons für Kurzerklärungen.",
+ "translations": {
+ "de": "💡 Tipp: Bewege den Mauszeiger über die Toolbar-Buttons für Kurzerklärungen.",
+ "en": "💡 Tip: Hover the mouse pointer over the toolbar buttons for short explanations.",
+ "zh": "💡 提示:将鼠标指针悬停在工具栏按钮上,即可查看简短说明。",
+ "bn": "💡 পরামর্শ: সংক্ষিপ্ত ব্যাখ্যা দেখতে টুলবারের বোতামগুলোর ওপর মাউস পয়েন্টার রাখুন।",
+ "pt": "💡 Dica: Passe o ponteiro do mouse sobre os botões da barra de ferramentas para ver explicações breves.",
+ "ja": "💡 ヒント:ツールバーのボタンにマウスポインターを合わせると、簡単な説明が表示されます。",
+ "fr": "💡 Astuce : Placez le pointeur de la souris sur les boutons de la barre d'outils pour obtenir de brèves explications.",
+ "ar": "💡 نصيحة: مرّر مؤشر الفأرة فوق أزرار شريط الأدوات للحصول على شروحات موجزة.",
+ "ru": "💡 Подсказка: наведите указатель мыши на кнопки панели инструментов, чтобы увидеть краткие пояснения.",
+ "hi": "💡 सुझाव: संक्षिप्त विवरण देखने के लिए टूलबार बटनों पर माउस पॉइंटर ले जाएँ।",
+ "es": "💡 Consejo: Mueve el puntero sobre los botones de la barra de herramientas para ver explicaciones breves."
+ }
+ },
+ {
+ "key": "gui.main_window.uber_notespacellm",
+ "canonical_source_value": "über NoteSpaceLLM",
+ "translations": {
+ "de": "über NoteSpaceLLM",
+ "es": "Acerca de NoteSpaceLLM",
+ "ar": "حول NoteSpaceLLM",
+ "en": "About NoteSpaceLLM",
+ "fr": "À propos de NoteSpaceLLM",
+ "zh": "关于 NoteSpaceLLM",
+ "ja": "NoteSpaceLLM について",
+ "ru": "О NoteSpaceLLM",
+ "bn": "NoteSpaceLLM সম্পর্কে",
+ "hi": "NoteSpaceLLM के बारे में",
+ "pt": "Sobre o NoteSpaceLLM"
+ }
+ },
+ {
+ "key": "gui.main_window.umbenennen",
+ "canonical_source_value": "Umbenennen",
+ "translations": {
+ "de": "Umbenennen",
+ "es": "Renombrar",
+ "fr": "Renommer",
+ "bn": "পুনঃনামকরণ",
+ "pt": "Renomear",
+ "ru": "Переименовать",
+ "en": "Rename",
+ "ar": "إعادة التسمية",
+ "zh": "重命名",
+ "hi": "नाम बदलें",
+ "ja": "名前の変更"
+ }
+ },
+ {
+ "key": "gui.main_window.umbenennen_fehlgeschlagen_name_belegt",
+ "canonical_source_value": "Umbenennen fehlgeschlagen (Name belegt?)",
+ "translations": {
+ "de": "Umbenennen fehlgeschlagen (Name belegt?)",
+ "ru": "Переименование не удалось (имя занято?)",
+ "ja": "名前の変更に失敗しました(名前が既に使用されていますか?)",
+ "bn": "ইতিমধ্যে ব্যবহৃত নাম? (নাম পরিবর্তন ব্যর্থ হয়েছে)",
+ "ar": "تعذّرت إعادة التسمية (هل الاسم مستخدم بالفعل؟)",
+ "fr": "Échec du renommage (le nom est-il déjà utilisé ?)",
+ "hi": "नाम बदलना विफल (नाम पहले से लिया हुआ है?)",
+ "zh": "重命名失败(名称已被占用?)",
+ "pt": "Falha ao renomear (nome já em uso?)",
+ "es": "No se pudo cambiar el nombre (¿el nombre ya está en uso?)",
+ "en": "Rename failed (name already taken?)"
+ }
+ },
+ {
+ "key": "gui.main_window.value_alle_dateien",
+ "canonical_source_value": "{value1};;Alle Dateien (*)",
+ "translations": {
+ "de": "{value1};;Alle Dateien (*)",
+ "hi": "{value1};;सभी फ़ाइलें (*)",
+ "es": "{value1};;Todos los archivos (*)",
+ "fr": "{value1};;Tous les fichiers (*)",
+ "pt": "{value1};;Todos os arquivos (*)",
+ "en": "{value1};;All Files (*)",
+ "ja": "{value1};;すべてのファイル (*)",
+ "bn": "{value1};;সমস্ত ফাইল (*)",
+ "zh": "{value1};;所有文件 (*)",
+ "ru": "{value1};;Все файлы (*)",
+ "ar": "{value1};;جميع الملفات (*)"
+ }
+ },
+ {
+ "key": "gui.main_window.value_alte_r_schlussel_werden_in_den_nativen_schlussel",
+ "canonical_source_value": "{value1} alte(r) Schlüssel werden in den nativen Schlüsselspeicher des Betriebssystems übertragen und anschließend aus der Konfigurationsdatei entfernt. Fortfahren?",
+ "translations": {
+ "de": "{value1} alte(r) Schlüssel werden in den nativen Schlüsselspeicher des Betriebssystems übertragen und anschließend aus der Konfigurationsdatei entfernt. Fortfahren?",
+ "zh": "{value1} 旧的密钥将被迁移到操作系统的原生密钥存储中,随后从配置文件中删除。是否继续?",
+ "ar": "{value1} ستُنقل المفاتيح القديمة إلى مخزن المفاتيح الأصلي في نظام التشغيل، ثم تُحذف من ملف الإعدادات. هل تريد المتابعة؟",
+ "fr": "{value1} Les anciennes clés seront transférées dans le magasin de clés natif du système d'exploitation, puis supprimées du fichier de configuration. Continuer ?",
+ "ru": "{value1} Старые ключи переносятся в нативное хранилище ключей операционной системы и затем удаляются из файла конфигурации. Продолжить?",
+ "es": "Se transferirán {value1} claves antiguas al almacén de claves nativo del sistema operativo y luego se eliminarán del archivo de configuración. ¿Continuar?",
+ "en": "{value1} old key(s) are transferred to the operating system's native keychain and then removed from the configuration file. Continue?",
+ "bn": "পুরোনো {value1}টি কী অপারেটিং সিস্টেমের নেটিভ কী-স্টোরে স্থানান্তর করা হবে, তারপর কনফিগারেশন ফাইল থেকে সরিয়ে ফেলা হবে। চালিয়ে যাবেন?",
+ "hi": "पुरानी {value1} कुंजियाँ ऑपरेटिंग सिस्टम के मूल कुंजी-भंडार में स्थानांतरित की जाएँगी और फिर कॉन्फ़िगरेशन फ़ाइल से हटा दी जाएँगी। जारी रखें?",
+ "ja": "古いキー {value1} 件を OS のネイティブキーストアに移し、その後設定ファイルから削除します。続行しますか?",
+ "pt": "As {value1} chaves antigas serão transferidas para o armazenamento nativo de chaves do sistema operacional e, em seguida, removidas do arquivo de configuração. Continuar?"
+ }
+ },
+ {
+ "key": "gui.main_window.value_dateien_hinzugefugt",
+ "canonical_source_value": "{value1} Dateien hinzugefügt",
+ "translations": {
+ "de": "{value1} Dateien hinzugefügt",
+ "ja": "{value1} ファイルを追加しました",
+ "ar": "تمت إضافة {value1} ملفًا",
+ "pt": "{value1} Arquivos adicionados",
+ "hi": "{value1} फ़ाइलें जोड़ी गईं",
+ "es": "{value1} Archivos añadidos",
+ "zh": "{value1} 已添加文件",
+ "bn": "{value1} ফাইল যোগ করা হয়েছে",
+ "ru": "{value1} Файлы добавлены",
+ "fr": "{value1} Fichiers ajoutés",
+ "en": "{value1} Files added"
+ }
+ },
+ {
+ "key": "gui.main_window.value_dok_text_extrahieren_schritt_1",
+ "canonical_source_value": "🔤 {value1} Dok. → Text extrahieren (Schritt 1)",
+ "translations": {
+ "de": "🔤 {value1} Dok. → Text extrahieren (Schritt 1)",
+ "bn": "🔤 {value1}টি নথি → পাঠ্য বের করুন (ধাপ ১)",
+ "ja": "🔤 {value1} ドキュメント → テキストを抽出する(ステップ 1)",
+ "fr": "🔤 {value1} documents → Extraire le texte (étape 1)",
+ "ar": "🔤 {value1} مستند → استخراج النص (الخطوة 1)",
+ "pt": "🔤 {value1} documentos → Extrair texto (etapa 1)",
+ "es": "🔤 {value1} documentos → Extraer texto (paso 1)",
+ "en": "🔤 {value1} Doc. → Extract text (step 1)",
+ "ru": "🔤 {value1} Док. → Извлечь текст (шаг 1)",
+ "zh": "🔤 {value1} 文档 → 提取文本(步骤 1)",
+ "hi": "🔤 {value1} दस्तावेज़ → पाठ निकालें (चरण 1)"
+ }
+ },
+ {
+ "key": "gui.main_window.value_elemente_hinzugefugt",
+ "canonical_source_value": "{value1} Elemente hinzugefügt",
+ "translations": {
+ "de": "{value1} Elemente hinzugefügt",
+ "ru": "{value1} Элементы добавлены",
+ "fr": "{value1} Éléments ajoutés",
+ "en": "{value1} Elements added",
+ "hi": "{value1} तत्व जोड़े गए",
+ "ja": "{value1} 要素が追加されました",
+ "zh": "{value1} 已添加元素",
+ "pt": "{value1} Elementos adicionados",
+ "es": "{value1} Elementos agregados",
+ "bn": "{value1} আইটেম যোগ করা হয়েছে",
+ "ar": "{value1} أُضيفت عناصر"
+ }
+ },
+ {
+ "key": "gui.main_window.value_extrahiert_analysieren_oder_bericht_erstellen",
+ "canonical_source_value": "✅ {value1} extrahiert → Analysieren oder Bericht erstellen",
+ "translations": {
+ "de": "✅ {value1} extrahiert → Analysieren oder Bericht erstellen",
+ "en": "✅ {value1} extracted → Analyze or create a report",
+ "bn": "✅ {value1} নিষ্কাশন করা হয়েছে → বিশ্লেষণ করুন বা প্রতিবেদন তৈরি করুন",
+ "es": "✅ {value1} extraído → Analizar o crear un informe",
+ "fr": "✅ {value1} extrait → Analyser ou créer un rapport",
+ "ru": "✅ {value1} извлечено → Проанализировать или создать отчёт",
+ "ar": "✅ تم استخراج {value1} → حلّل أو أنشئ تقريرًا",
+ "zh": "✅ {value1} 已提取 → 分析或生成报告",
+ "hi": "✅ {value1} निकाला गया → विश्लेषण करें या रिपोर्ट बनाएं",
+ "pt": "✅ {value1} extraído → Analisar ou criar relatório",
+ "ja": "✅ {value1} を抽出 → 分析またはレポートを作成"
+ }
+ },
+ {
+ "key": "gui.main_window.value_extrahiert_value_indexiert_bericht_erstellen",
+ "canonical_source_value": "🎯 {value1} extrahiert, {value2} indexiert → Bericht erstellen",
+ "translations": {
+ "de": "🎯 {value1} extrahiert, {value2} indexiert → Bericht erstellen",
+ "en": "🎯 {value1} extracted, {value2} indexed → Create report",
+ "ru": "🎯 {value1} извлечено, {value2} проиндексировано → создать отчёт",
+ "zh": "🎯 {value1} 提取,{value2} 建立索引 → 生成报告",
+ "pt": "🎯 {value1} extraído, {value2} indexado → Gerar relatório",
+ "es": "🎯 {value1} extraído, {value2} indexado → Generar informe",
+ "fr": "🎯 {value1} extrait, {value2} indexé → Générer le rapport",
+ "ja": "🎯 {value1} を抽出し、{value2} にインデックスしてレポートを作成する",
+ "hi": "🎯 {value1} निकाला गया, {value2} अनुक्रमित किया गया → रिपोर्ट बनाएँ",
+ "ar": "🎯 تم استخراج {value1}، وفهرسة {value2} → إنشاء تقرير",
+ "bn": "🎯 {value1} নিষ্কাশন করা হয়েছে, {value2} সূচীভুক্ত করা হয়েছে → প্রতিবেদন তৈরি করুন"
+ }
+ },
+ {
+ "key": "gui.main_window.value_schlussel_wurden_uberpruft_und_migriert",
+ "canonical_source_value": "{value1} Schlüssel wurden überprüft und migriert.",
+ "translations": {
+ "de": "{value1} Schlüssel wurden überprüft und migriert.",
+ "fr": "{value1} clés ont été vérifiées et migrées.",
+ "ar": "{value1} تم التحقق من المفاتيح وترحيلها.",
+ "zh": "{value1} 密钥已验证并迁移。",
+ "hi": "{value1} कुंजियों की जाँच की गई और उन्हें माइग्रेट किया गया।",
+ "en": "{value1} Keys have been verified and migrated.",
+ "ru": "{value1} Ключи были проверены и мигрированы.",
+ "es": "Se verificaron y migraron {value1} claves.",
+ "ja": "{value1} 鍵が検証され、移行されました。",
+ "bn": "{value1}টি কী যাচাই করে স্থানান্তর করা হয়েছে।",
+ "pt": "{value1} chaves foram verificadas e migradas."
+ }
+ },
+ {
+ "key": "gui.main_window.value_value",
+ "canonical_source_value": "{value1} {value2}",
+ "translations": {
+ "de": "{value1} {value2}",
+ "en": "{value1} {value2}",
+ "es": "{value1} {value2}",
+ "zh": "{value1} {value2}",
+ "ja": "{value1} {value2}",
+ "ru": "{value1} {value2}",
+ "fr": "{value1} {value2}",
+ "hi": "{value1} {value2}",
+ "ar": "{value1} {value2}",
+ "bn": "{value1} {value2}",
+ "pt": "{value1} {value2}"
+ }
+ },
+ {
+ "key": "gui.main_window.value_value.13928937",
+ "canonical_source_value": "'{value1}' → '{value2}'",
+ "translations": {
+ "de": "'{value1}' → '{value2}'",
+ "en": "'{value1}' → '{value2}'",
+ "es": "'{value1}' → '{value2}'",
+ "zh": "'{value1}' → '{value2}'",
+ "ja": "'{value1}' → '{value2}'",
+ "ru": "'{value1}' → '{value2}'",
+ "fr": "'{value1}' → '{value2}'",
+ "hi": "'{value1}' → '{value2}'",
+ "ar": "'{value1}' → '{value2}'",
+ "bn": "'{value1}' → '{value2}'",
+ "pt": "'{value1}' → '{value2}'"
+ }
+ },
+ {
+ "key": "gui.main_window.value_value.1d364144",
+ "canonical_source_value": "{value1}: {value2}",
+ "translations": {
+ "de": "{value1}: {value2}",
+ "en": "{value1}: {value2}",
+ "es": "{value1}: {value2}",
+ "zh": "{value1}: {value2}",
+ "ja": "{value1}: {value2}",
+ "ru": "{value1}: {value2}",
+ "fr": "{value1}: {value2}",
+ "hi": "{value1}: {value2}",
+ "ar": "{value1}: {value2}",
+ "bn": "{value1}: {value2}",
+ "pt": "{value1}: {value2}"
+ }
+ },
+ {
+ "key": "gui.main_window.value_value_dokumente_erfolgreich_indexiert",
+ "canonical_source_value": "{value1}/{value2} Dokumente erfolgreich indexiert.",
+ "translations": {
+ "de": "{value1}/{value2} Dokumente erfolgreich indexiert.",
+ "ar": "تمت فهرسة مستندات {value1}/{value2} بنجاح.",
+ "hi": "{value1}/{value2} दस्तावेज़ सफलतापूर्वक अनुक्रमित किए गए।",
+ "en": "{value1}/{value2} documents successfully indexed.",
+ "zh": "{value1}/{value2} 文档已成功索引。",
+ "pt": "{value1}/{value2} documentos indexados com sucesso.",
+ "fr": "{value1}/{value2} documents indexés avec succès.",
+ "bn": "{value1}/{value2} ডকুমেন্ট সফলভাবে ইনডেক্স করা হয়েছে।",
+ "es": "{value1}/{value2} documentos indexados correctamente.",
+ "ja": "{value1}/{value2} のドキュメントのインデックス作成に成功しました。",
+ "ru": "Успешно проиндексировано документов: {value1}/{value2}."
+ }
+ },
+ {
+ "key": "gui.main_window.value_value_extrahiert_weiter_extrahieren",
+ "canonical_source_value": "⏳ {value1}/{value2} extrahiert → weiter extrahieren",
+ "translations": {
+ "de": "⏳ {value1}/{value2} extrahiert → weiter extrahieren",
+ "fr": "⏳ {value1}/{value2} extrait → poursuivre l'extraction",
+ "es": "⏳ {value1}/{value2} extraído → continuar extrayendo",
+ "pt": "⏳ {value1}/{value2} extraído → continuar extraindo",
+ "hi": "⏳ {value1}/{value2} निकाला जा रहा है → निकालना जारी रखें",
+ "zh": "⏳ 已提取 {value1}/{value2} → 继续提取",
+ "ja": "⏳ {value1}/{value2} を抽出中 → 引き続き抽出",
+ "en": "⏳ {value1}/{value2} extracted → continue extracting",
+ "ru": "⏳ {value1}/{value2} извлечено → продолжить извлечение",
+ "bn": "⏳ {value1}/{value2} নিষ্কাশিত → আরও নিষ্কাশন চালিয়ে যান",
+ "ar": "⏳ تم استخراج {value1}/{value2} → متابعة الاستخراج"
+ }
+ },
+ {
+ "key": "gui.main_window.verbindungsprofile",
+ "canonical_source_value": "Verbindungsprofile",
+ "translations": {
+ "de": "Verbindungsprofile",
+ "en": "Connection profiles",
+ "zh": "连接配置文件",
+ "hi": "कनेक्शन प्रोफ़ाइल",
+ "pt": "Perfis de conexão",
+ "ja": "接続プロファイル",
+ "es": "Perfiles de conexión",
+ "ru": "Профили подключения",
+ "ar": "ملفات تعريف الاتصال",
+ "bn": "সংযোগ প্রোফাইল",
+ "fr": "Profils de connexion"
+ }
+ },
+ {
+ "key": "gui.main_window.verfugbare_modelle",
+ "canonical_source_value": "Verfügbare Modelle:",
+ "translations": {
+ "de": "Verfügbare Modelle:",
+ "hi": "उपलब्ध मॉडल:",
+ "bn": "উপলব্ধ মডেল:",
+ "ru": "Доступные модели:",
+ "en": "Available models:",
+ "zh": "可用模型:",
+ "ar": "النماذج المتاحة:",
+ "ja": "利用可能なモデル:",
+ "fr": "Modèles disponibles :",
+ "pt": "Modelos disponíveis:",
+ "es": "Modelos disponibles:"
+ }
+ },
+ {
+ "key": "gui.main_window.warte_auf_laufende_hintergrundaufgaben",
+ "canonical_source_value": "Warte auf laufende Hintergrundaufgaben …",
+ "translations": {
+ "de": "Warte auf laufende Hintergrundaufgaben …",
+ "fr": "Attente des tâches d'arrière-plan en cours …",
+ "en": "Waiting for ongoing background tasks …",
+ "pt": "Aguardando tarefas de segundo plano em execução …",
+ "ar": "في انتظار المهام الجارية في الخلفية …",
+ "zh": "等待正在运行的后台任务 …",
+ "es": "Esperando a que terminen las tareas en segundo plano en curso …",
+ "ru": "Ожидание выполнения фоновых задач …",
+ "ja": "実行中のバックグラウンドタスクを待機中…",
+ "hi": "पृष्ठभूमि में चल रहे कार्यों की प्रतीक्षा की जा रही है…",
+ "bn": "চলমান ব্যাকগ্রাউন্ড কাজের জন্য অপেক্ষা করা হচ্ছে…"
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_export_abgebrochen",
+ "canonical_source_value": "Workspace-Export abgebrochen.",
+ "translations": {
+ "de": "Workspace-Export abgebrochen.",
+ "ar": "تم إلغاء تصدير مساحة العمل.",
+ "fr": "Exportation de l'espace de travail interrompue.",
+ "bn": "ওয়ার্কস্পেস এক্সপোর্ট বাতিল করা হয়েছে।",
+ "en": "Workspace export cancelled.",
+ "hi": "वर्कस्पेस-एक्सपोर्ट रद्द किया गया।",
+ "es": "Exportación del espacio de trabajo cancelada.",
+ "pt": "Exportação do espaço de trabalho cancelada.",
+ "ru": "Экспорт рабочего пространства прерван.",
+ "ja": "ワークスペースのエクスポートが中止されました。",
+ "zh": "工作区导出已取消。"
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_export_bestatigen",
+ "canonical_source_value": "Workspace-Export bestätigen",
+ "translations": {
+ "de": "Workspace-Export bestätigen",
+ "bn": "ওয়ার্কস্পেস-এক্সপোর্ট নিশ্চিত করুন",
+ "en": "Confirm workspace export",
+ "es": "Confirmar exportación del espacio de trabajo",
+ "pt": "Confirmar exportação do espaço de trabalho",
+ "ru": "Подтвердить экспорт рабочего пространства",
+ "ja": "ワークスペースエクスポートを確認",
+ "fr": "Confirmer l'exportation de l'espace de travail",
+ "ar": "تأكيد تصدير مساحة العمل",
+ "hi": "कार्यस्थान निर्यात की पुष्टि करें",
+ "zh": "确认工作区导出"
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_export_fehlgeschlagen",
+ "canonical_source_value": "Workspace-Export fehlgeschlagen.",
+ "translations": {
+ "de": "Workspace-Export fehlgeschlagen.",
+ "ru": "Экспорт рабочего пространства не удался.",
+ "pt": "Falha na exportação do espaço de trabalho.",
+ "en": "Workspace export failed.",
+ "es": "La exportación del espacio de trabajo falló.",
+ "ja": "ワークスペースのエクスポートに失敗しました。",
+ "fr": "Échec de l'exportation de l'espace de travail.",
+ "zh": "工作区导出失败。",
+ "bn": "কর্মক্ষেত্র রপ্তানি ব্যর্থ হয়েছে।",
+ "hi": "कार्यस्थान निर्यात विफल हुआ।",
+ "ar": "فشل تصدير مساحة العمل."
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_export_fehlgeschlagen_value",
+ "canonical_source_value": "Workspace-Export fehlgeschlagen ({value1}).",
+ "translations": {
+ "de": "Workspace-Export fehlgeschlagen ({value1}).",
+ "bn": "কর্মক্ষেত্র রপ্তানি ব্যর্থ হয়েছে ({value1})।",
+ "fr": "L'exportation de l'espace de travail a échoué ({value1}).",
+ "hi": "कार्यक्षेत्र निर्यात विफल हुआ ({value1})।",
+ "en": "Workspace export failed ({value1}).",
+ "ar": "فشل تصدير مساحة العمل ({value1}).",
+ "zh": "工作区导出失败({value1})。",
+ "pt": "Falha na exportação do espaço de trabalho ({value1}).",
+ "es": "Error al exportar el espacio de trabajo ({value1}).",
+ "ru": "Не удалось экспортировать рабочее пространство ({value1}).",
+ "ja": "ワークスペースのエクスポートに失敗しました ({value1})。"
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_exportieren",
+ "canonical_source_value": "Workspace exportieren",
+ "translations": {
+ "de": "Workspace exportieren",
+ "en": "Export workspace",
+ "ru": "Экспортировать рабочее пространство",
+ "es": "Exportar espacio de trabajo",
+ "fr": "Exporter l'espace de travail",
+ "ar": "تصدير مساحة العمل",
+ "pt": "Exportar espaço de trabalho",
+ "hi": "कार्यस्थान निर्यात करें",
+ "zh": "导出工作区",
+ "ja": "ワークスペースをエクスポート",
+ "bn": "কর্মক্ষেত্র রপ্তানি করুন"
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_exportieren_json",
+ "canonical_source_value": "Workspace exportieren (JSON)...",
+ "translations": {
+ "de": "Workspace exportieren (JSON)...",
+ "ar": "تصدير مساحة العمل (JSON)...",
+ "en": "Export workspace (JSON)...",
+ "ru": "Экспортировать рабочую область (JSON)...",
+ "pt": "Exportar espaço de trabalho (JSON)...",
+ "fr": "Exporter l'espace de travail (JSON)...",
+ "bn": "কর্মক্ষেত্র রপ্তানি করুন (JSON)...",
+ "hi": "कार्यस्थान निर्यात करें (JSON)...",
+ "ja": "ワークスペースをエクスポート (JSON)...",
+ "es": "Exportar el espacio de trabajo (JSON)...",
+ "zh": "导出工作区 (JSON)..."
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_exportiert",
+ "canonical_source_value": "Workspace exportiert",
+ "translations": {
+ "de": "Workspace exportiert",
+ "zh": "工作区已导出",
+ "es": "Espacio de trabajo exportado",
+ "pt": "Espaço de trabalho exportado",
+ "ja": "ワークスペースをエクスポートしました",
+ "en": "Workspace exported",
+ "hi": "कार्यक्षेत्र निर्यात किया गया",
+ "ru": "Рабочая область экспортирована",
+ "ar": "تم تصدير مساحة العمل",
+ "fr": "Espace de travail exporté",
+ "bn": "ওয়ার্কস্পেস এক্সপোর্ট করা হয়েছে"
+ }
+ },
+ {
+ "key": "gui.main_window.workspace_exportiert_value_value_dokumente",
+ "canonical_source_value": "Workspace exportiert: {value1} ({value2} Dokumente)",
+ "translations": {
+ "de": "Workspace exportiert: {value1} ({value2} Dokumente)",
+ "es": "Espacio de trabajo exportado: {value1} ({value2} documentos)",
+ "zh": "已导出工作区:{value1}({value2} 文档)",
+ "ru": "Рабочее пространство экспортировано: {value1} ({value2} документов)",
+ "ja": "ワークスペースのエクスポート: {value1} ({value2} 件のドキュメント)",
+ "en": "Workspace exported: {value1} ({value2} documents)",
+ "pt": "Espaço de trabalho exportado: {value1} ({value2} documentos)",
+ "fr": "Espace de travail exporté : {value1} ({value2} documents)",
+ "bn": "কর্মক্ষেত্র রপ্তানি করা হয়েছে: {value1} ({value2}টি নথি)",
+ "ar": "تم تصدير مساحة العمل: {value1} ({value2} مستندات)",
+ "hi": "कार्यक्षेत्र निर्यात किया गया: {value1} ({value2} दस्तावेज़)"
+ }
+ },
+ {
+ "key": "gui.output.profile_academic",
+ "canonical_source_value": "Akademisch",
+ "translations": {
+ "de": "Akademisch",
+ "en": "Academic",
+ "hi": "शैक्षणिक",
+ "ru": "Академический",
+ "ar": "أكاديمي",
+ "fr": "Académique",
+ "zh": "学术的",
+ "ja": "学術的",
+ "es": "Académico",
+ "pt": "Acadêmico",
+ "bn": "শিক্ষাবিষয়ক"
+ }
+ },
+ {
+ "key": "gui.output.profile_complete",
+ "canonical_source_value": "Vollständig",
+ "translations": {
+ "de": "Vollständig",
+ "ja": "完全",
+ "ar": "مكتمل",
+ "es": "Completo",
+ "zh": "完整",
+ "en": "Complete",
+ "ru": "Полный",
+ "fr": "Entièrement",
+ "bn": "সম্পূর্ণ",
+ "pt": "Completo",
+ "hi": "पूर्ण"
+ }
+ },
+ {
+ "key": "gui.output.profile_markdown_only",
+ "canonical_source_value": "Nur Markdown",
+ "translations": {
+ "de": "Nur Markdown",
+ "ja": "Markdownのみ",
+ "hi": "केवल Markdown",
+ "zh": "仅限 Markdown",
+ "fr": "Markdown uniquement",
+ "ru": "Только Markdown",
+ "bn": "শুধু Markdown",
+ "en": "Markdown only",
+ "pt": "Apenas Markdown",
+ "es": "Solo Markdown",
+ "ar": "Markdown فقط"
+ }
+ },
+ {
+ "key": "gui.output.profile_pdf_only",
+ "canonical_source_value": "Nur PDF",
+ "translations": {
+ "de": "Nur PDF",
+ "ru": "Только PDF",
+ "es": "Solo PDF",
+ "ar": "PDF فقط",
+ "ja": "PDF のみ",
+ "hi": "केवल PDF",
+ "bn": "শুধু PDF",
+ "fr": "Seul PDF",
+ "zh": "仅 PDF",
+ "en": "Only PDF",
+ "pt": "Somente PDF"
+ }
+ },
+ {
+ "key": "gui.output.profile_standard",
+ "canonical_source_value": "Standard",
+ "translations": {
+ "de": "Standard",
+ "fr": "Standard",
+ "es": "Estándar",
+ "ja": "標準",
+ "bn": "মানক",
+ "ru": "Стандарт",
+ "zh": "标准",
+ "en": "Standard",
+ "hi": "मानक",
+ "pt": "Padrão",
+ "ar": "قياسي"
+ }
+ },
+ {
+ "key": "gui.output_panel.als_profil_speichern",
+ "canonical_source_value": "Als Profil speichern",
+ "translations": {
+ "de": "Als Profil speichern",
+ "ja": "プロファイルとして保存",
+ "bn": "প্রোফাইল হিসেবে সংরক্ষণ করুন",
+ "zh": "以配置文件形式保存",
+ "es": "Guardar como perfil",
+ "en": "Save as profile",
+ "pt": "Salvar como perfil",
+ "hi": "प्रोफ़ाइल के रूप में सहेजें",
+ "ru": "Сохранить как профиль",
+ "fr": "Enregistrer comme profil",
+ "ar": "حفظ كملف تعريف"
+ }
+ },
+ {
+ "key": "gui.output_panel.ausgabeformate",
+ "canonical_source_value": "Ausgabeformate",
+ "translations": {
+ "de": "Ausgabeformate",
+ "es": "Formatos de salida",
+ "bn": "আউটপুট ফরম্যাট",
+ "ar": "صيغ الإخراج",
+ "zh": "输出格式",
+ "ja": "出力形式",
+ "hi": "आउटपुट प्रारूप",
+ "en": "Output formats",
+ "pt": "Formatos de saída",
+ "ru": "Форматы вывода",
+ "fr": "Formats de sortie"
+ }
+ },
+ {
+ "key": "gui.output_panel.ausgabeordner",
+ "canonical_source_value": "Ausgabeordner",
+ "translations": {
+ "de": "Ausgabeordner",
+ "bn": "আউটপুট ফোল্ডার",
+ "ja": "出力フォルダー",
+ "es": "Carpeta de salida",
+ "ru": "Папка вывода",
+ "zh": "输出文件夹",
+ "pt": "Pasta de saída",
+ "en": "Output folder",
+ "ar": "مجلد الإخراج",
+ "hi": "आउटपुट फ़ोल्डर",
+ "fr": "Dossier de sortie"
+ }
+ },
+ {
+ "key": "gui.output_panel.ausgabeordner_wahlen",
+ "canonical_source_value": "Ausgabeordner wählen",
+ "translations": {
+ "de": "Ausgabeordner wählen",
+ "bn": "আউটপুট ফোল্ডার বাছাই করুন",
+ "ja": "出力フォルダーを選択",
+ "es": "Seleccionar carpeta de salida",
+ "ru": "Выберите папку вывода",
+ "zh": "选择输出文件夹",
+ "en": "Choose output folder",
+ "fr": "Choisir le dossier de sortie",
+ "ar": "اختر مجلد الإخراج",
+ "pt": "Escolha a pasta de saída",
+ "hi": "आउटपुट फ़ोल्डर चुनें"
+ }
+ },
+ {
+ "key": "gui.output_panel.ausgabeprofil",
+ "canonical_source_value": "Ausgabeprofil",
+ "translations": {
+ "de": "Ausgabeprofil",
+ "pt": "Perfil de saída",
+ "zh": "输出配置文件",
+ "ru": "Профиль вывода",
+ "fr": "Profil de sortie",
+ "ar": "ملف تعريف الإخراج",
+ "en": "Output profile",
+ "hi": "आउटपुट प्रोफ़ाइल",
+ "es": "Perfil de salida",
+ "ja": "出力プロファイル",
+ "bn": "আউটপুট প্রোফাইল"
+ }
+ },
+ {
+ "key": "gui.output_panel.bereit_zum_exportieren",
+ "canonical_source_value": "Bereit zum Exportieren",
+ "translations": {
+ "de": "Bereit zum Exportieren",
+ "ja": "エクスポートの準備ができました",
+ "ru": "Готово к экспорту",
+ "es": "Listo para exportar",
+ "zh": "准备导出",
+ "pt": "Pronto para exportar",
+ "hi": "निर्यात के लिए तैयार",
+ "fr": "Prêt à exporter",
+ "en": "Ready to export",
+ "bn": "রপ্তানির জন্য প্রস্তুত",
+ "ar": "جاهز للتصدير"
+ }
+ },
+ {
+ "key": "gui.output_panel.bericht_vorschau",
+ "canonical_source_value": "Bericht-Vorschau",
+ "translations": {
+ "de": "Bericht-Vorschau",
+ "fr": "Aperçu du rapport",
+ "zh": "报告预览",
+ "ja": "レポートプレビュー",
+ "pt": "Visualização do relatório",
+ "ru": "Предпросмотр отчёта",
+ "es": "Vista previa del informe",
+ "bn": "প্রতিবেদন পূর্বরূপ",
+ "en": "Report preview",
+ "ar": "معاينة التقرير",
+ "hi": "रिपोर्ट पूर्वावलोकन"
+ }
+ },
+ {
+ "key": "gui.output_panel.bitte_mindestens_ein_ausgabeformat_wahlen",
+ "canonical_source_value": "Bitte mindestens ein Ausgabeformat wählen.",
+ "translations": {
+ "de": "Bitte mindestens ein Ausgabeformat wählen.",
+ "ar": "يرجى اختيار تنسيق إخراج واحد على الأقل.",
+ "en": "Please select at least one output format.",
+ "fr": "Veuillez choisir au moins un format de sortie.",
+ "pt": "Por favor, selecione pelo menos um formato de saída.",
+ "hi": "कृपया कम से कम एक आउटपुट प्रारूप चुनें।",
+ "bn": "অন্তত একটি আউটপুট বিন্যাস নির্বাচন করুন।",
+ "es": "Por favor, elija al menos un formato de salida.",
+ "ru": "Пожалуйста, выберите хотя бы один формат вывода.",
+ "zh": "请至少选择一种输出格式。",
+ "ja": "少なくとも1つの出力形式を選択してください。"
+ }
+ },
+ {
+ "key": "gui.output_panel.durchsuchen",
+ "canonical_source_value": "Durchsuchen...",
+ "translations": {
+ "de": "Durchsuchen...",
+ "fr": "Parcourir...",
+ "en": "Browse...",
+ "zh": "浏览...",
+ "ru": "Просмотреть...",
+ "hi": "ब्राउज़ करें...",
+ "es": "Examinar...",
+ "ar": "استعراض...",
+ "ja": "ブラウズ...",
+ "pt": "Procurar...",
+ "bn": "ব্রাউজ করুন..."
+ }
+ },
+ {
+ "key": "gui.output_panel.einstellungen",
+ "canonical_source_value": "Einstellungen",
+ "translations": {
+ "de": "Einstellungen",
+ "es": "Configuración",
+ "ru": "Настройки",
+ "hi": "सेटिंग्स",
+ "ja": "設定",
+ "fr": "Paramètres",
+ "ar": "الإعدادات",
+ "en": "Settings",
+ "zh": "设置",
+ "bn": "সেটিংস",
+ "pt": "Configurações"
+ }
+ },
+ {
+ "key": "gui.output_panel.exportieren",
+ "canonical_source_value": "Exportieren",
+ "translations": {
+ "de": "Exportieren",
+ "ar": "تصدير",
+ "en": "Export",
+ "ru": "Экспортировать",
+ "pt": "Exportar",
+ "fr": "Exporter",
+ "es": "Exportar",
+ "ja": "エクスポート",
+ "zh": "导出",
+ "bn": "রপ্তানি করুন",
+ "hi": "निर्यात करें"
+ }
+ },
+ {
+ "key": "gui.output_panel.exportiert_den_an_das_llm_gesendeten_analyse_prompt_al",
+ "canonical_source_value": "Exportiert den an das LLM gesendeten Analyse-Prompt als .md Datei\n(nützlich zum Einspeisen in andere LLMs oder zur Dokumentation)",
+ "translations": {
+ "de": "Exportiert den an das LLM gesendeten Analyse-Prompt als .md Datei\n(nützlich zum Einspeisen in andere LLMs oder zur Dokumentation)",
+ "hi": "LLM को भेजे गए विश्लेषण प्रॉम्प्ट को .md फ़ाइल के रूप में निर्यात करता है\n(अन्य LLMs में फ़ीड करने या दस्तावेज़ीकरण के लिए उपयोगी)",
+ "es": "Exporta el prompt de análisis enviado al LLM como archivo .md\n(útil para introducirlo en otros LLM o para documentación)",
+ "zh": "将发送至 LLM 的分析提示词导出为 .md 文件\n(便于输入其他 LLM 或用于文档记录)",
+ "fr": "Exporte le prompt d'analyse envoyé au LLM sous forme de fichier .md\n(utile pour le transmettre à d'autres LLM ou pour la documentation)",
+ "en": "Exports the analysis prompt sent to the LLM as a .md file\n(useful for feeding into other LLMs or for documentation)",
+ "ar": "يُصدّر موجه التحليل المُرسل إلى LLM كملف .md\n(مفيد للتغذية إلى نماذج LLM أخرى أو للتوثيق)",
+ "pt": "Exporta o prompt de análise enviado ao LLM como arquivo .md\n(útil para enviar a outros LLMs ou para documentação)",
+ "ru": "Экспортирует аналитический запрос, отправленный в LLM, в файл .md\n(удобно для передачи другим LLM или для документации)",
+ "ja": "LLM に送信した分析プロンプトを .md ファイルとしてエクスポートします\n(他の LLM への入力やドキュメント作成に便利です)",
+ "bn": "LLM-এ পাঠানো বিশ্লেষণমূলক প্রম্পটটি .md ফাইল হিসেবে রপ্তানি করে\n(অন্য LLM-এ দেওয়া বা নথিভুক্ত করার কাজে উপযোগী)"
+ }
+ },
+ {
+ "key": "gui.output_panel.in_zwischenablage_kopiert",
+ "canonical_source_value": "In Zwischenablage kopiert",
+ "translations": {
+ "de": "In Zwischenablage kopiert",
+ "fr": "Copié dans le presse-papiers",
+ "pt": "Copiado para a área de transferência",
+ "en": "Copied to clipboard",
+ "es": "Copiado al portapapeles",
+ "ru": "Скопировано в буфер обмена",
+ "zh": "已复制到剪贴板",
+ "ar": "نُسخ إلى الحافظة",
+ "ja": "クリップボードにコピーしました",
+ "hi": "क्लिपबोर्ड पर कॉपी किया गया",
+ "bn": "ক্লিপবোর্ডে কপি করা হয়েছে"
+ }
+ },
+ {
+ "key": "gui.output_panel.kein_inhalt_zum_exportieren_vorhanden",
+ "canonical_source_value": "Kein Inhalt zum Exportieren vorhanden.",
+ "translations": {
+ "de": "Kein Inhalt zum Exportieren vorhanden.",
+ "zh": "没有可导出的内容。",
+ "bn": "এক্সপোর্ট করার জন্য কোনো বিষয়বস্তু নেই।",
+ "ru": "Для экспорта нет содержимого.",
+ "es": "No hay contenido para exportar.",
+ "hi": "निर्यात करने के लिए कोई सामग्री उपलब्ध नहीं है।",
+ "ar": "لا يوجد محتوى للتصدير.",
+ "ja": "エクスポートするコンテンツはありません。",
+ "en": "No content available to export.",
+ "fr": "Aucun contenu à exporter.",
+ "pt": "Nenhum conteúdo disponível para exportação."
+ }
+ },
+ {
+ "key": "gui.output_panel.kopieren",
+ "canonical_source_value": "Kopieren",
+ "translations": {
+ "de": "Kopieren",
+ "fr": "Copier",
+ "zh": "复制",
+ "ar": "Kopieren",
+ "en": "Copy",
+ "ru": "Копировать",
+ "pt": "Copiar",
+ "es": "Copiar",
+ "hi": "कॉपी करें",
+ "bn": "কপি করুন",
+ "ja": "コピー"
+ }
+ },
+ {
+ "key": "gui.output_panel.llm_prompt_exportieren",
+ "canonical_source_value": "LLM-Prompt exportieren",
+ "translations": {
+ "de": "LLM-Prompt exportieren",
+ "es": "Exportar el prompt del LLM",
+ "ar": "تصدير مطالبة LLM",
+ "hi": "LLM प्रॉम्प्ट निर्यात करें",
+ "ru": "Экспортировать промпт LLM",
+ "fr": "Exporter le prompt du LLM",
+ "en": "Export LLM prompt",
+ "pt": "Exportar o prompt do LLM",
+ "ja": "LLM プロンプトをエクスポート",
+ "bn": "LLM-প্রম্পট রপ্তানি করুন",
+ "zh": "导出 LLM 提示词"
+ }
+ },
+ {
+ "key": "gui.output_panel.vorschau",
+ "canonical_source_value": "Vorschau",
+ "translations": {
+ "de": "Vorschau",
+ "fr": "Aperçu",
+ "bn": "পূর্বরূপ",
+ "ru": "Предпросмотр",
+ "en": "Preview",
+ "zh": "预览",
+ "ja": "プレビュー",
+ "ar": "معاينة",
+ "pt": "Prévia",
+ "hi": "पूर्वावलोकन",
+ "es": "Vista previa"
+ }
+ },
+ {
+ "key": "gui.pipeline_help.step1.body",
+ "canonical_source_value": "+ Dateien oder + Ordner in der Toolbar klicken.\n\nUnterstützte Formate: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nDie Textextraktion startet automatisch im Hintergrund.",
+ "translations": {
+ "de": "+ Dateien oder + Ordner in der Toolbar klicken.\n\nUnterstützte Formate: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nDie Textextraktion startet automatisch im Hintergrund.",
+ "en": "Click + Files or + Folder in the toolbar.\n\nSupported formats: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nText extraction starts automatically in the background.",
+ "es": "Haga clic en + Archivos o + Carpeta en la barra de herramientas.\n\nFormatos compatibles: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nLa extracción de texto se inicia automáticamente en segundo plano.",
+ "fr": "Cliquez sur + Fichiers ou + Dossier dans la barre d’outils.\n\nFormats pris en charge : PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nL’extraction de texte démarre automatiquement en arrière-plan.",
+ "ru": "Нажмите + Файлы или + Папка на панели инструментов.\n\nПоддерживаемые форматы: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nИзвлечение текста автоматически запускается в фоновом режиме.",
+ "ja": "ツールバーで + ファイル または + フォルダー をクリックします。\n\n対応形式: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nテキスト抽出はバックグラウンドで自動的に開始されます。",
+ "hi": "टूलबार में + फ़ाइलें या + फ़ोल्डर पर क्लिक करें।\n\nसमर्थित फ़ॉर्मैट: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nटेक्स्ट निष्कर्षण पृष्ठभूमि में अपने-आप शुरू हो जाता है।",
+ "bn": "+ ফাইল অথবা + ফোল্ডার টুলবারে ক্লিক করুন।\n\nসমর্থিত ফরম্যাট: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nটেক্সট এক্সট্র্যাকশন স্বয়ংক্রিয়ভাবে ব্যাকগ্রাউন্ডে শুরু হয়।",
+ "zh": "在工具栏中点击+ 文件 或 + 文件夹。\n\n支持的格式:PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\n文本提取会自动在后台启动。",
+ "ar": "انقر على + ملفات أو + مجلد في شريط الأدوات.\n\nالتنسيقات المدعومة: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nتبدأ استخراج النص تلقائيًا في الخلفية.",
+ "pt": "Clique em + Arquivos ou + Pasta na barra de ferramentas.\n\nFormatos compatíveis: PDF, DOCX, TXT, MD, XLSX, PPTX, RTF, CSV, JSON, XML, EML …\n\nA extração de texto começa automaticamente em segundo plano."
+ }
+ },
+ {
+ "key": "gui.pipeline_help.step1.title",
+ "canonical_source_value": "Dokumente hinzufügen",
+ "translations": {
+ "de": "Dokumente hinzufügen",
+ "ru": "Добавить документы",
+ "pt": "Adicionar documentos",
+ "es": "Añadir documentos",
+ "ja": "ドキュメントを追加",
+ "zh": "添加文档",
+ "fr": "Ajouter des documents",
+ "en": "Add documents",
+ "ar": "إضافة مستندات",
+ "bn": "Dokumente hinzufügen",
+ "hi": "दस्तावेज़ जोड़ें"
+ }
+ },
+ {
+ "key": "gui.pipeline_help.step2.body",
+ "canonical_source_value": "Läuft automatisch nach dem Hinzufügen.\n\nAlternativ: Text extrahieren in der Toolbar, um alle ausgewählten Dokumente manuell zu verarbeiten.\n\nDer Fortschritt wird in der Statusleiste angezeigt.",
+ "translations": {
+ "de": "Läuft automatisch nach dem Hinzufügen.\n\nAlternativ: Text extrahieren in der Toolbar, um alle ausgewählten Dokumente manuell zu verarbeiten.\n\nDer Fortschritt wird in der Statusleiste angezeigt.",
+ "ja": "追加後に自動的に実行されます。\n\nまたは、選択したすべてのドキュメントを手動で処理するには、ツールバーのテキストを抽出をクリックします。\n\n進行状況はステータスバーに表示されます。",
+ "bn": "স্বয়ংক্রিয়ভাবে যোগ করার পরে চলে।\n\nবিকল্পভাবে, নির্বাচিত সব নথি ম্যানুয়ালি প্রক্রিয়া করতে টুলবারে পাঠ্য বের করুন চাপুন।\n\nঅগ্রগতি স্ট্যাটাস বারে দেখানো হয়।",
+ "pt": "Executa automaticamente após a adição.\n\nAlternativamente: Extrair texto na barra de ferramentas para processar manualmente todos os documentos selecionados.\n\nO progresso é exibido na barra de status.",
+ "ru": "Запускается автоматически после добавления.\n\nЛибо: нажмите Извлечь текст на панели инструментов, чтобы обработать все выбранные документы вручную.\n\nХод выполнения отображается в строке состояния.",
+ "en": "Runs automatically after adding.\n\nAlternatively: Extract Text in the toolbar to process all selected documents manually.\n\nProgress is shown in the status bar.",
+ "fr": "S’exécute automatiquement après l’ajout.\n\nVous pouvez aussi cliquer sur Extraire le texte dans la barre d’outils pour traiter manuellement tous les documents sélectionnés.\n\nLa progression s’affiche dans la barre d’état.",
+ "hi": "जोड़ने के बाद स्वचालित रूप से चलता है।\n\nवैकल्पिक रूप से, सभी चुने हुए दस्तावेज़ों को मैन्युअल रूप से संसाधित करने के लिए टूलबार में पाठ निकालें पर क्लिक करें।\n\nप्रगति स्थिति पट्टी में दिखाई जाती है।",
+ "ar": "يُشغَّل تلقائيًا بعد الإضافة.\n\nبدلًا من ذلك: انقر على استخراج النص في شريط الأدوات لمعالجة جميع المستندات المحددة يدويًا.\n\nيُعرض التقدم في شريط الحالة.",
+ "zh": "在添加后会自动运行。\n\n或者,在工具栏中点击提取文本,以手动处理所有选定的文档。\n\n处理进度将显示在状态栏中。",
+ "es": "Se ejecuta automáticamente después de agregar.\n\nAlternativamente: Extraer texto en la barra de herramientas para procesar manualmente todos los documentos seleccionados.\n\nEl progreso se muestra en la barra de estado."
+ }
+ },
+ {
+ "key": "gui.pipeline_help.step3.body",
+ "canonical_source_value": "Rechtsklick auf ein Dokument → Sub-Query hinzufügen.\n\nDann Analysieren in der Toolbar drücken.\n\nDas LLM beantwortet die Sub-Queries pro Dokument und die Ergebnisse fließen in den Bericht ein.\n\nOptional — der Bericht funktioniert auch ohne diesen Schritt.",
+ "translations": {
+ "de": "Rechtsklick auf ein Dokument → Sub-Query hinzufügen.\n\nDann Analysieren in der Toolbar drücken.\n\nDas LLM beantwortet die Sub-Queries pro Dokument und die Ergebnisse fließen in den Bericht ein.\n\nOptional — der Bericht funktioniert auch ohne diesen Schritt.",
+ "es": "Clic derecho en un documento → Añadir sub-consulta.\n\nLuego pulse Analizar en la barra de herramientas.\n\nEl LLM responde las sub-consultas por documento, y los resultados se incorporan al informe.\n\nOpcional: el informe también funciona sin este paso.",
+ "bn": "একটি নথিতে ডান-ক্লিক করুন → উপ-প্রশ্ন যোগ করুন।\n\nএরপর টুলবারে বিশ্লেষণ চাপুন।\n\nLLM প্রতিটি নথির উপ-প্রশ্নগুলোর উত্তর দেয়, এবং ফলাফলগুলো প্রতিবেদনে যুক্ত হয়।\n\nঐচ্ছিক — এই ধাপ ছাড়াও প্রতিবেদনটি কাজ করে।",
+ "fr": "Cliquez avec le bouton droit sur un document → Ajouter une sous-requête.\n\nCliquez ensuite sur Analyser dans la barre d’outils.\n\nLe LLM répond aux sous-requêtes pour chaque document, et les résultats sont intégrés au rapport.\n\nFacultatif — le rapport fonctionne aussi sans cette étape.",
+ "ar": "انقر بزر الفأرة الأيمن على مستند → أضف استعلامًا فرعيًا.\n\nثم انقر على تحليل في شريط الأدوات.\n\nيجيب LLM عن الاستعلامات الفرعية لكل مستند، وتُضمَّن النتائج في التقرير.\n\nاختياري — يعمل التقرير أيضًا من دون هذه الخطوة.",
+ "ja": "右クリックでドキュメント → サブクエリを追加。\n\n次にツールバーの 分析 を押します。\n\nLLM は各ドキュメントに対するサブクエリに回答し、その結果がレポートに反映されます。\n\n任意 — このステップを行わなくてもレポートは機能します。",
+ "en": "Right-click a document → Add sub-query.\n\nThen click Analyze in the toolbar.\n\nThe LLM answers the sub-queries for each document, and the results are included in the report.\n\nOptional — the report also works without this step.",
+ "ru": "Щёлкните правой кнопкой мыши по документу → Добавить подзапрос.\n\nЗатем нажмите Анализировать на панели инструментов.\n\nLLM отвечает на подзапросы для каждого документа, и результаты попадают в отчёт.\n\nНеобязательно — отчёт работает и без этого шага.",
+ "pt": "Clique com o botão direito em um documento → Adicionar subconsulta.\n\nDepois, clique em Analisar na barra de ferramentas.\n\nO LLM responde às subconsultas de cada documento, e os resultados são incorporados ao relatório.\n\nOpcional — o relatório também funciona sem esta etapa.",
+ "zh": "在文档上右键单击 → 添加子查询。\n\n然后在工具栏中按下 分析。\n\nLLM 按文档回答各个子查询,结果会汇入报告。\n\n可选 — 即使不执行此步骤,报告也能正常生成。",
+ "hi": "किसी दस्तावेज़ पर राइट-क्लिक करें → उप-प्रश्न जोड़ें।\n\nफिर टूलबार में विश्लेषण करें पर क्लिक करें।\n\nLLM प्रत्येक दस्तावेज़ के उप-प्रश्नों के उत्तर देता है और परिणाम रिपोर्ट में शामिल होते हैं।\n\nवैकल्पिक — इस चरण के बिना भी रिपोर्ट काम करती है।"
+ }
+ },
+ {
+ "key": "gui.pipeline_help.step3.title",
+ "canonical_source_value": "Detailanalysen (optional)",
+ "translations": {
+ "de": "Detailanalysen (optional)",
+ "hi": "विस्तृत विश्लेषण (वैकल्पिक)",
+ "bn": "বিস্তারিত বিশ্লেষণ (ঐচ্ছিক)",
+ "ja": "詳細分析(任意)",
+ "es": "Análisis detallados (opcional)",
+ "pt": "Análises detalhadas (opcional)",
+ "ar": "تحليلات مفصلة (اختيارية)",
+ "zh": "详细分析(可选)",
+ "fr": "Analyses détaillées (facultatif)",
+ "en": "Detailed analyses (optional)",
+ "ru": "Детальный анализ (необязательно)"
+ }
+ },
+ {
+ "key": "gui.pipeline_help.step4.body",
+ "canonical_source_value": "Bericht erstellen in der Toolbar klicken.\n\nDas LLM kombiniert alle Dokumentinhalte und Detailanalysen zu einem strukturierten Bericht.\n\nFehlende Textextraktion wird automatisch ausgelöst.\n\nDer Bericht erscheint im Ausgabe-Panel rechts.",
+ "translations": {
+ "de": "Bericht erstellen in der Toolbar klicken.\n\nDas LLM kombiniert alle Dokumentinhalte und Detailanalysen zu einem strukturierten Bericht.\n\nFehlende Textextraktion wird automatisch ausgelöst.\n\nDer Bericht erscheint im Ausgabe-Panel rechts.",
+ "zh": "在工具栏中点击 创建报告。\n\nLLM 会将所有文档内容和详细分析整合成一份结构化报告。\n\n如果尚未提取文本,系统会自动启动提取。\n\n报告会显示在右侧的输出面板中。",
+ "ru": "Нажмите Создать отчёт на панели инструментов.\n\nLLM объединяет содержимое всех документов и подробные анализы в структурированный отчёт.\n\nЕсли текст ещё не извлечён, извлечение запускается автоматически.\n\nОтчёт отображается в панели вывода справа.",
+ "en": "Click Create report in the toolbar.\n\nThe LLM combines all document content and detailed analyses into a structured report.\n\nMissing text extraction is automatically triggered.\n\nThe report appears in the output panel on the right.",
+ "fr": "Cliquez sur Générer un rapport dans la barre d'outils.\n\nLe LLM combine tout le contenu du document et les analyses détaillées dans un rapport structuré.\n\nL'extraction de texte manquante est déclenchée automatiquement.\n\nLe rapport apparaît dans le panneau de sortie à droite.",
+ "hi": "टूलबार में रिपोर्ट बनाएँ पर क्लिक करें।\n\nLLM सभी दस्तावेज़ों की सामग्री और विस्तृत विश्लेषण को एक सुव्यवस्थित रिपोर्ट में जोड़ता है।\n\nयदि टेक्स्ट अभी तक नहीं निकाला गया है, तो उसे अपने-आप निकाला जाता है।\n\nरिपोर्ट दाईं ओर आउटपुट पैनल में दिखाई देती है।",
+ "ja": "レポートを作成をツールバーでクリックします。\n\nLLMは、すべての文書内容と詳細分析を構造化されたレポートにまとめます。\n\nテキスト抽出が欠落している場合、自動的に実行されます。\n\nレポートは右側の出力パネルに表示されます。",
+ "bn": "টুলবারে প্রতিবেদন তৈরি করুন-এ ক্লিক করুন।\n\nLLM সব নথির বিষয়বস্তু ও বিস্তারিত বিশ্লেষণ একত্র করে একটি কাঠামোবদ্ধ প্রতিবেদন তৈরি করে।\n\nটেক্সট নিষ্কাশন না হয়ে থাকলে তা স্বয়ংক্রিয়ভাবে শুরু হয়।\n\nপ্রতিবেদনটি ডান পাশের আউটপুট প্যানেলে দেখা যায়।",
+ "ar": "انقر على إنشاء تقرير في شريط الأدوات.\n\nيجمع LLM محتوى جميع المستندات والتحليلات التفصيلية في تقرير منظم.\n\nإذا لم يكن النص قد استُخرج بعد، فسيبدأ استخراجه تلقائيًا.\n\nيظهر التقرير في لوحة الإخراج على اليمين.",
+ "pt": "Clique em Gerar relatório na barra de ferramentas.\n\nO LLM combina todo o conteúdo do documento e as análises detalhadas em um relatório estruturado.\n\nA extração de texto ausente é iniciada automaticamente.\n\nO relatório aparece no painel de saída à direita.",
+ "es": "Crear informe en la barra de herramientas.\n\nEl LLM combina todo el contenido del documento y los análisis detallados en un informe estructurado.\n\nLa extracción de texto faltante se activa automáticamente.\n\nEl informe aparece en el panel de salida a la derecha."
+ }
+ },
+ {
+ "key": "gui.pipeline_help.step5.body",
+ "canonical_source_value": "Im Ausgabe-Panel rechts: Format wählen (Markdown, PDF, DOCX …) und Exportieren.\n\nLLM-Prompt exportieren: Exportiert den an das LLM gesendeten Prompt als .md Datei — nützlich für andere LLMs.\n\nWorkspace exportieren (Datei-Menü): Speichert Projekt, Dokument-Metadaten, Auszüge und Bericht als JSON für Web/Companion-Apps.",
+ "translations": {
+ "de": "Im Ausgabe-Panel rechts: Format wählen (Markdown, PDF, DOCX …) und Exportieren.\n\nLLM-Prompt exportieren: Exportiert den an das LLM gesendeten Prompt als .md Datei — nützlich für andere LLMs.\n\nWorkspace exportieren (Datei-Menü): Speichert Projekt, Dokument-Metadaten, Auszüge und Bericht als JSON für Web/Companion-Apps.",
+ "en": "In the output panel on the right: choose a format (Markdown, PDF, DOCX …) and click Export.\n\nExport LLM prompt: Exports the prompt sent to the LLM as a .md file — useful for other LLMs.\n\nExport workspace (File menu): Saves the project, document metadata, excerpts and report as JSON for web/companion apps.",
+ "ar": "في لوحة الإخراج على اليمين: اختر تنسيقًا (Markdown, PDF, DOCX …) ثم انقر على تصدير.\n\nتصدير موجّه LLM: يصدّر الموجّه المُرسل إلى LLM في ملف .md — وهو مفيد لاستخدامه مع نماذج LLM أخرى.\n\nتصدير مساحة العمل (قائمة ملف): يحفظ المشروع وبيانات المستند الوصفية والمقتطفات والتقرير بصيغة JSON لاستخدامها في تطبيقات الويب/التطبيقات المرافقة.",
+ "bn": "ডান দিকের আউটপুট প্যানেল-এ ফরম্যাট নির্বাচন করুন (Markdown, PDF, DOCX …) এবং রপ্তানি ক্লিক করুন।\n\nLLM প্রম্পট রপ্তানি: LLM-কে পাঠানো প্রম্পটটি .md ফাইল হিসেবে রপ্তানি করে — অন্য LLM-এ ব্যবহারের জন্য উপযোগী।\n\nওয়ার্কস্পেস রপ্তানি (ফাইল মেনু): ওয়েব/সহচর অ্যাপের জন্য প্রকল্প, নথির মেটাডেটা, উদ্ধৃতাংশ ও প্রতিবেদন JSON হিসেবে সংরক্ষণ করে।",
+ "ja": "右側の出力パネルで形式(Markdown、PDF、DOCX …)を選択し、エクスポートをクリックします。\n\nLLM プロンプトをエクスポート: LLM に送信したプロンプトを .md ファイルとしてエクスポートします。他の LLM でも利用できます。\n\nワークスペースをエクスポート(ファイルメニュー): プロジェクト、ドキュメントのメタデータ、抜粋、レポートを JSON として保存し、Web/コンパニオンアプリで利用できます。",
+ "hi": "दाईं ओर के आउटपुट पैनल में फ़ॉर्मैट चुनें (Markdown, PDF, DOCX …) और निर्यात करें पर क्लिक करें।\n\nLLM प्रॉम्प्ट निर्यात करें: LLM को भेजे गए प्रॉम्प्ट को .md फ़ाइल के रूप में निर्यात करता है — अन्य LLM के लिए उपयोगी।\n\nवर्कस्पेस निर्यात करें (फ़ाइल मेनू): वेब/सहायक ऐप्स के लिए प्रोजेक्ट, दस्तावेज़ मेटाडेटा, अंश और रिपोर्ट को JSON के रूप में सहेजता है।",
+ "pt": "No painel de saída à direita, escolha um formato (Markdown, PDF, DOCX …) e clique em Exportar.\n\nExportar prompt de LLM: exporta o prompt enviado ao LLM para um arquivo .md — útil para outros LLM.\n\nExportar espaço de trabalho (menu Arquivo): salva o projeto, os metadados dos documentos, os trechos e o relatório como JSON para aplicativos Web/complementares.",
+ "fr": "Dans le panneau de sortie à droite : choisissez un format (Markdown, PDF, DOCX …), puis cliquez sur Exporter.\n\nExporter le prompt LLM : exporte le prompt envoyé au LLM dans un fichier .md — utile pour d’autres LLM.\n\nExporter l’espace de travail (menu Fichier) : enregistre le projet, les métadonnées des documents, les extraits et le rapport au format JSON pour les applications Web/Companion.",
+ "ru": "В панели вывода справа выберите формат (Markdown, PDF, DOCX …) и нажмите Экспортировать.\n\nЭкспортировать промпт LLM: сохраняет отправленный LLM промпт в файл .md — это полезно для других LLM.\n\nЭкспортировать рабочее пространство (меню «Файл»): сохраняет проект, метаданные документов, выдержки и отчёт в JSON для веб-приложений и приложений-компаньонов.",
+ "es": "En el panel de salida de la derecha, elija el formato (Markdown, PDF, DOCX …) y haga clic en Exportar.\n\nExportar el prompt de LLM: exporta el prompt enviado al LLM en un archivo .md — útil para otros LLM.\n\nExportar el espacio de trabajo (menú Archivo): guarda el proyecto, los metadatos de los documentos, los extractos y el informe como JSON para aplicaciones web/complementarias.",
+ "zh": "在右侧的输出面板中选择格式(Markdown、PDF、DOCX …),然后点击导出。\n\n导出 LLM 提示词:将发送给 LLM 的提示词导出为 .md 文件,便于其他 LLM 使用。\n\n导出工作区(文件菜单):将项目、文档元数据、摘录和报告保存为 JSON,供 Web/伴随应用使用。"
+ }
+ },
+ {
+ "key": "gui.workflow.report_type_comparison",
+ "canonical_source_value": "Vergleich",
+ "translations": {
+ "de": "Vergleich",
+ "zh": "比较",
+ "pt": "Comparação",
+ "fr": "Comparaison",
+ "es": "Comparación",
+ "ru": "Сравнение",
+ "en": "Comparison",
+ "hi": "तुलना",
+ "bn": "তুলনা",
+ "ar": "مقارنة",
+ "ja": "比較"
+ }
+ },
+ {
+ "key": "gui.workflow.report_type_research",
+ "canonical_source_value": "Forschungsbericht",
+ "translations": {
+ "de": "Forschungsbericht",
+ "en": "Research Report",
+ "zh": "研究报告",
+ "pt": "Relatório de pesquisa",
+ "es": "Informe de investigación",
+ "bn": "গবেষণা প্রতিবেদন",
+ "ru": "Исследовательский отчёт",
+ "ar": "تقرير بحثي",
+ "fr": "Rapport de recherche",
+ "hi": "शोध रिपोर्ट",
+ "ja": "研究レポート"
+ }
+ },
+ {
+ "key": "gui.workflow.report_type_summary",
+ "canonical_source_value": "Zusammenfassung",
+ "translations": {
+ "de": "Zusammenfassung",
+ "es": "Resumen",
+ "en": "Summary",
+ "hi": "सारांश",
+ "zh": "摘要",
+ "pt": "Resumo",
+ "ja": "概要",
+ "fr": "Résumé",
+ "ar": "ملخص",
+ "ru": "Сводка",
+ "bn": "সারসংক্ষেপ"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.berichtstyp",
+ "canonical_source_value": "Berichtstyp:",
+ "translations": {
+ "de": "Berichtstyp:",
+ "es": "Tipo de informe:",
+ "bn": "বেরিচটস্টাইপ:",
+ "en": "Report type:",
+ "zh": "报告类型:",
+ "hi": "Berichtstyp:",
+ "ru": "Тип отчёта:",
+ "fr": "Type de rapport :",
+ "ja": "報告の種類:",
+ "ar": "نوع التقرير:",
+ "pt": "Tipo de relatório:"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.berichtstyp_und_workflow",
+ "canonical_source_value": "Berichtstyp und Workflow",
+ "translations": {
+ "de": "Berichtstyp und Workflow",
+ "en": "Report type and workflow",
+ "zh": "报告类型与工作流程",
+ "es": "Tipo de informe y flujo de trabajo",
+ "fr": "Type de rapport et flux de travail",
+ "pt": "Tipo de relatório e fluxo de trabalho",
+ "ru": "Тип отчёта и рабочий процесс",
+ "ja": "報告の種類とワークフロー",
+ "hi": "रिपोर्ट प्रकार और कार्यप्रवाह",
+ "ar": "نوع التقرير وسير العمل",
+ "bn": "প্রতিবেদনের ধরন ও কর্মপ্রবাহ"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.beschreibung",
+ "canonical_source_value": "Beschreibung:",
+ "translations": {
+ "de": "Beschreibung:",
+ "es": "Descripción:",
+ "en": "Description:",
+ "fr": "Description :",
+ "bn": "বিবরণ:",
+ "pt": "Descrição:",
+ "ru": "Описание:",
+ "ja": "説明:",
+ "ar": "الوصف:",
+ "hi": "विवरण:",
+ "zh": "描述:"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.definiere_die_zentrale_fragestellung_fur_den_bericht_b",
+ "canonical_source_value": "Definiere die zentrale Fragestellung für den Bericht...\n\nBeispiel: 'Welche Faktoren beeinflussten die Entwicklung im Jahr 2025?'",
+ "translations": {
+ "de": "Definiere die zentrale Fragestellung für den Bericht...\n\nBeispiel: 'Welche Faktoren beeinflussten die Entwicklung im Jahr 2025?'",
+ "hi": "रिपोर्ट के लिए केंद्रीय प्रश्न निर्धारित करें...\n\nउदाहरण: '2025 में विकास को किन कारकों ने प्रभावित किया?'",
+ "es": "Define la pregunta central para el informe...\n\nEjemplo: '¿Qué factores influyeron en la evolución durante 2025?'",
+ "zh": "为报告定义核心问题……\n\n示例:「哪些因素影响了 2025 年的发展?」",
+ "ru": "Определите центральный вопрос для отчёта...\n\nПример: 'Какие факторы повлияли на развитие ситуации в 2025 году?'",
+ "ja": "レポートの中心的な問いを定義する...\n\n例:「2025年の発展にはどのような要因が影響したか?」",
+ "fr": "Définissez la question centrale du rapport...\n\nExemple : « Quels facteurs ont influencé l'évolution en 2025 ? »",
+ "pt": "Defina a questão central para o relatório...\n\nExemplo: 'Quais fatores influenciaram o desenvolvimento em 2025?'",
+ "bn": "ব্রিফের কেন্দ্রীয় প্রশ্নটি সংজ্ঞায়িত করুন...\n\nউদাহরণ: '২০২৫ সালে কোন কারণগুলো উন্নয়নকে প্রভাবিত করেছিল?'",
+ "en": "Define the central question for the report...\n\nExample: 'Which factors influenced the development in the year 2025?'",
+ "ar": "حدّد السؤال المحوري للتقرير...\n\nمثال: 'ما العوامل التي أثرت في التطور خلال عام 2025؟'"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.hauptfragestellung",
+ "canonical_source_value": "Hauptfragestellung",
+ "translations": {
+ "de": "Hauptfragestellung",
+ "bn": "মূল প্রশ্ন",
+ "pt": "Pergunta principal",
+ "ru": "Основной вопрос",
+ "ar": "السؤال الرئيسي",
+ "hi": "मुख्य प्रश्न",
+ "zh": "主要问题",
+ "ja": "主要な問い",
+ "es": "Pregunta principal",
+ "en": "Main question",
+ "fr": "Question principale"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.name",
+ "canonical_source_value": "Name:",
+ "translations": {
+ "de": "Name:",
+ "pt": "Nome:",
+ "ar": "الاسم:",
+ "hi": "नाम:",
+ "es": "Nombre:",
+ "ru": "Имя:",
+ "bn": "নাম:",
+ "fr": "Nom :",
+ "en": "Name:",
+ "zh": "名称:",
+ "ja": "名前:"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.reihenfolge",
+ "canonical_source_value": "Reihenfolge:",
+ "translations": {
+ "de": "Reihenfolge:",
+ "hi": "क्रम:",
+ "bn": "ক্রম:",
+ "fr": "Ordre :",
+ "zh": "顺序:",
+ "ar": "الترتيب:",
+ "ru": "Порядок:",
+ "en": "Order:",
+ "es": "Orden:",
+ "ja": "順序:",
+ "pt": "Sequência:"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.workflow",
+ "canonical_source_value": "Workflow:",
+ "translations": {
+ "de": "Workflow:",
+ "ar": "سير العمل:",
+ "zh": "工作流程:",
+ "pt": "Fluxo de trabalho:",
+ "ru": "Рабочий процесс:",
+ "ja": "ワークフロー:",
+ "es": "Flujo de trabajo:",
+ "fr": "Flux de travail :",
+ "bn": "কর্মপ্রবাহ:",
+ "en": "Workflow:",
+ "hi": "कार्यप्रवाह:"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.workflow_bearbeiten",
+ "canonical_source_value": "Workflow bearbeiten",
+ "translations": {
+ "de": "Workflow bearbeiten",
+ "ar": "تحرير سير العمل",
+ "en": "Edit workflow",
+ "bn": "ওয়ার্কফ্লো সম্পাদনা করুন",
+ "ru": "Редактировать рабочий процесс",
+ "pt": "Editar fluxo de trabalho",
+ "ja": "ワークフローを編集",
+ "zh": "编辑工作流",
+ "fr": "Modifier le flux de travail",
+ "hi": "कार्यप्रवाह संपादित करें",
+ "es": "Editar flujo de trabajo"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.workflow_bearbeiten_value",
+ "canonical_source_value": "Workflow bearbeiten: {value1}",
+ "translations": {
+ "de": "Workflow bearbeiten: {value1}",
+ "hi": "कार्यप्रवाह संपादित करें: {value1}",
+ "ar": "تحرير سير العمل: {value1}",
+ "pt": "Editar fluxo de trabalho: {value1}",
+ "zh": "编辑工作流:{value1}",
+ "ru": "Редактировать рабочий процесс: {value1}",
+ "bn": "কর্মপ্রবাহ সম্পাদনা করুন: {value1}",
+ "es": "Editar flujo de trabajo: {value1}",
+ "ja": "ワークフローを編集: {value1}",
+ "fr": "Modifier le flux de travail : {value1}",
+ "en": "Edit workflow: {value1}"
+ }
+ },
+ {
+ "key": "gui.workflow_panel.workflow_schritte",
+ "canonical_source_value": "Workflow-Schritte",
+ "translations": {
+ "de": "Workflow-Schritte",
+ "ar": "خطوات سير العمل",
+ "hi": "कार्यप्रवाह चरण",
+ "es": "Pasos del flujo de trabajo",
+ "ru": "Шаги рабочего процесса",
+ "ja": "ワークフローのステップ",
+ "pt": "Etapas do fluxo de trabalho",
+ "fr": "Étapes du flux de travail",
+ "zh": "工作流步骤",
+ "bn": "কর্মপ্রবাহের ধাপ",
+ "en": "Workflow steps"
+ }
+ },
+ {
+ "key": "gui.main_window.language_selector_label",
+ "canonical_source_value": "Sprache:",
+ "translations": {
+ "en": "Language:",
+ "de": "Sprache:",
+ "es": "Idioma:",
+ "zh": "语言:",
+ "ja": "言語:",
+ "ru": "Язык:",
+ "fr": "Langue :",
+ "hi": "भाषा:",
+ "ar": "اللغة:",
+ "bn": "ভাষা:",
+ "pt": "Idioma:"
+ }
+ },
+ {
+ "key": "gui.main_window.language_preference_save_failed",
+ "canonical_source_value": "Spracheinstellung konnte nicht gespeichert werden.",
+ "translations": {
+ "en": "The language preference could not be saved.",
+ "de": "Spracheinstellung konnte nicht gespeichert werden.",
+ "es": "No se pudo guardar la preferencia de idioma.",
+ "zh": "无法保存语言首选项。",
+ "ja": "言語設定を保存できませんでした。",
+ "ru": "Не удалось сохранить языковые настройки.",
+ "fr": "La préférence de langue n’a pas pu être enregistrée.",
+ "hi": "भाषा प्राथमिकता सहेजी नहीं जा सकी।",
+ "ar": "تعذّر حفظ تفضيل اللغة.",
+ "bn": "ভাষার পছন্দ সংরক্ষণ করা যায়নি।",
+ "pt": "Não foi possível guardar a preferência de idioma."
+ }
+ }
+ ]
+}
diff --git a/pyproject.toml b/pyproject.toml
index 024e053..3b0992c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -90,9 +90,16 @@ dev = [
"ruff>=0.9.0",
]
+[tool.setuptools]
+py-modules = ["translator"]
+include-package-data = true
+
[tool.setuptools.packages.find]
where = ["."]
-include = ["src*"]
+include = ["src*", "locales*"]
+
+[tool.setuptools.package-data]
+locales = ["*.json"]
[tool.pytest.ini_options]
minversion = "7.0"
diff --git a/src/gui/chat_panel.py b/src/gui/chat_panel.py
index 89b4045..e04437f 100644
--- a/src/gui/chat_panel.py
+++ b/src/gui/chat_panel.py
@@ -33,6 +33,15 @@
from PySide6.QtGui import QTextCursor, QFont
from .worker_utils import retain_until_finished, stop_workers
PYSIDE_AVAILABLE = True
+ from .i18n import (
+ LocalizedLabel,
+ TranslatableLabel,
+ mark_not_translatable,
+ translate_ui_text,
+ translate_widget_tree,
+ tr,
+ )
+ QLabel = TranslatableLabel
except ImportError:
PYSIDE_AVAILABLE = False
class QWidget: pass
@@ -61,11 +70,13 @@ def __post_init__(self):
class MessageWidget(QFrame if PYSIDE_AVAILABLE else object):
"""Widget for displaying a single chat message."""
- def __init__(self, message: ChatMessage, parent=None):
+ def __init__(self, message: ChatMessage, parent=None, translator=None):
if not PYSIDE_AVAILABLE:
return
super().__init__(parent)
self.message = message
+ self._translator = translator
+ self._dynamic_labels = []
self._setup_ui()
def _setup_ui(self):
@@ -126,6 +137,7 @@ def _setup_ui(self):
# Content (PlainText: LLM-/Dokumenttext darf kein Rich-Text/HTML rendern)
self.content_label = QLabel()
+ mark_not_translatable(self.content_label)
self.content_label.setTextFormat(Qt.TextFormat.PlainText)
self.content_label.setText(self.message.content)
self.content_label.setWordWrap(True)
@@ -137,9 +149,11 @@ def _setup_ui(self):
# Document references
if self.message.document_refs:
- refs_label = QLabel()
+ refs_label = LocalizedLabel("", self._translator)
refs_label.setTextFormat(Qt.TextFormat.PlainText)
refs_label.setText(f"Dokumente: {', '.join(self.message.document_refs)}")
+ mark_not_translatable(refs_label)
+ self._dynamic_labels.append(refs_label)
refs_label.setStyleSheet("color: #666; font-size: 10px; font-style: italic;")
layout.addWidget(refs_label)
@@ -155,13 +169,19 @@ def _setup_ui(self):
if self.message.confidence > 0:
sources_text += f" (Konfidenz: {self.message.confidence:.0%})"
- sources_label = QLabel()
+ sources_label = LocalizedLabel("", self._translator)
sources_label.setTextFormat(Qt.TextFormat.PlainText)
sources_label.setText(sources_text)
+ mark_not_translatable(sources_label)
+ self._dynamic_labels.append(sources_label)
sources_label.setStyleSheet("color: #27ae60; font-size: 10px; font-style: italic;")
sources_label.setWordWrap(True)
layout.addWidget(sources_label)
+ def retranslate_ui(self):
+ for label in self._dynamic_labels:
+ label.retranslate()
+
def update_content(self, content: str):
"""Update the message content (for streaming)."""
self.message.content = content
@@ -260,11 +280,12 @@ class ChatPanel(QWidget if PYSIDE_AVAILABLE else object):
if PYSIDE_AVAILABLE:
message_sent = Signal(str)
- def __init__(self, parent=None):
+ def __init__(self, parent=None, translator=None):
if not PYSIDE_AVAILABLE:
raise ImportError("PySide6 is required. Install with: pip install PySide6")
super().__init__(parent)
+ self._translator = translator
self._messages: List[ChatMessage] = []
self._message_widgets: List[MessageWidget] = []
self._llm_client = None
@@ -281,6 +302,8 @@ def __init__(self, parent=None):
self._rag_k: int = 5 # Anzahl Kontext-Chunks
self._setup_ui()
+ if self._translator:
+ self.retranslate_ui()
def _setup_ui(self):
"""Set up the user interface."""
@@ -387,7 +410,7 @@ def _setup_ui(self):
layout.addWidget(input_frame)
# Status
- self.status_label = QLabel("Bereit")
+ self.status_label = LocalizedLabel("Bereit", self._translator)
self.status_label.setStyleSheet("color: #888; font-size: 10px;")
layout.addWidget(self.status_label)
@@ -425,19 +448,19 @@ def set_document_context(self, context: str):
def _toggle_rag(self):
"""Toggle RAG mode."""
self._use_rag = self.rag_toggle.isChecked()
- self.rag_toggle.setText("RAG: AN" if self._use_rag else "RAG: AUS")
+ self.rag_toggle.setText(tr(self._translator, "RAG: AN" if self._use_rag else "RAG: AUS"))
self._update_status()
def _update_rag_status(self):
"""Update RAG status display."""
if self._rag_engine:
stats = self._rag_engine.get_statistics()
- self.rag_toggle.setToolTip(
+ self.rag_toggle.setToolTip(translate_ui_text(
f"ChromaDB: {stats.get('total_chunks', 0)} Chunks indexiert\n"
- f"Embedding: {stats.get('embedding_model', 'N/A')}"
- )
+ f"Embedding: {stats.get('embedding_model', 'N/A')}", self._translator
+ ))
else:
- self.rag_toggle.setToolTip("RAG-Engine nicht verfügbar")
+ self.rag_toggle.setToolTip(tr(self._translator, "RAG-Engine nicht verfügbar"))
def _update_status(self):
"""Update status label."""
@@ -445,17 +468,33 @@ def _update_status(self):
if self._use_rag and self._rag_engine:
stats = self._rag_engine.get_statistics()
- parts.append(f"RAG: {stats.get('total_chunks', 0)} Chunks")
+ parts.append(translate_ui_text(
+ f"RAG: {stats.get('total_chunks', 0)} Chunks", self._translator
+ ))
elif self._document_context:
- parts.append(f"Kontext: ~{len(self._document_context)} Zeichen")
+ parts.append(translate_ui_text(
+ f"Kontext: ~{len(self._document_context)} Zeichen", self._translator
+ ))
if self._document_manager:
selected = len(self._document_manager.selected_documents)
indexed = len([d for d in self._document_manager.selected_documents if d.is_indexed])
- parts.append(f"Docs: {indexed}/{selected} indexiert")
+ parts.append(translate_ui_text(
+ f"Docs: {indexed}/{selected} indexiert", self._translator
+ ))
self.status_label.setText(" | ".join(parts) if parts else "Bereit")
+ def retranslate_ui(self):
+ if not self._translator:
+ return
+ translate_widget_tree(self, self._translator)
+ self.status_label.retranslate()
+ for widget in self._message_widgets:
+ widget.retranslate_ui()
+ self._update_status()
+ self._update_rag_status()
+
def _send_message(self):
"""Send the user's message."""
text = self.input_edit.text().strip()
@@ -535,10 +574,11 @@ def _on_response_error(self, error: str):
self.status_label.setText("Fehler")
# Update streaming widget with error
+ error_text = translate_ui_text(f"Fehler: {error}", self._translator)
if self._streaming_widget:
- self._streaming_widget.update_content(f"Fehler: {error}")
+ self._streaming_widget.update_content(error_text)
else:
- self._add_system_message(f"Fehler: {error}")
+ self._add_system_message(error_text)
self._streaming_widget = None
@@ -590,7 +630,7 @@ def _on_rag_response(self, result: dict):
# Create assistant message with sources
assistant_msg = ChatMessage(
role="assistant",
- content=result.get("answer", "Keine Antwort erhalten."),
+ content=result.get("answer") or tr(self._translator, "Keine Antwort erhalten."),
timestamp=datetime.now(),
sources=result.get("sources", []),
confidence=result.get("confidence", 0.0)
@@ -618,7 +658,7 @@ def _add_message(self, message: ChatMessage) -> MessageWidget:
"""Add a message to the chat."""
self._messages.append(message)
- widget = MessageWidget(message, self.messages_container)
+ widget = MessageWidget(message, self.messages_container, self._translator)
self._message_widgets.append(widget)
# Insert before stretch
@@ -632,7 +672,7 @@ def _add_message(self, message: ChatMessage) -> MessageWidget:
def _add_system_message(self, text: str):
"""Add a system message."""
- msg = ChatMessage("system", text, datetime.now())
+ msg = ChatMessage("system", translate_ui_text(text, self._translator), datetime.now())
self._add_message(msg)
def _scroll_to_bottom(self):
diff --git a/src/gui/document_panel.py b/src/gui/document_panel.py
index a7bc0ff..65aa69e 100644
--- a/src/gui/document_panel.py
+++ b/src/gui/document_panel.py
@@ -32,6 +32,18 @@ class QWidget:
class Signal:
def __init__(self, *args): pass
+if PYSIDE_AVAILABLE:
+ from .i18n import (
+ LocalizedFileDialog as QFileDialog,
+ LocalizedInputDialog as QInputDialog,
+ LocalizedLabel,
+ LocalizedMessageBox as QMessageBox,
+ TranslatableLabel,
+ translate_widget_tree,
+ tr,
+ )
+ QLabel = TranslatableLabel
+
class DocumentPanel(QWidget if PYSIDE_AVAILABLE else object):
"""
@@ -49,17 +61,20 @@ class DocumentPanel(QWidget if PYSIDE_AVAILABLE else object):
subquery_requested = Signal(str, str, str) # doc_id, query_type, query_text
files_added = Signal() # Emitted after files were added (for async extraction)
- def __init__(self, parent=None):
+ def __init__(self, parent=None, translator=None):
if not PYSIDE_AVAILABLE:
raise ImportError("PySide6 is required. Install with: pip install PySide6")
super().__init__(parent)
+ self._translator = translator
self._document_manager = None
self._subquery_manager = None
self._item_map = {} # doc_id -> QTreeWidgetItem
self._setup_ui()
self._setup_drag_drop()
+ if self._translator:
+ self.retranslate_ui()
def _setup_ui(self):
"""Set up the user interface."""
@@ -119,7 +134,7 @@ def _setup_ui(self):
layout.addWidget(self.tree)
# Status bar
- self.status_label = QLabel("Keine Dokumente")
+ self.status_label = LocalizedLabel("Keine Dokumente", self._translator)
layout.addWidget(self.status_label)
# Progress bar (hidden by default)
@@ -202,7 +217,7 @@ def _status_text(self, status) -> str:
DocumentStatus.COMPLETED: "Fertig",
DocumentStatus.ERROR: "Fehler"
}
- return status_map.get(status, str(status.value))
+ return tr(self._translator, status_map.get(status, str(status.value)))
def _format_size(self, size_bytes: int) -> str:
"""Format file size for display."""
@@ -225,6 +240,15 @@ def _update_status(self):
f"{stats['selected_documents']} von {stats['total_documents']} ausgewählt"
)
+ def retranslate_ui(self):
+ """Refresh UI chrome and app-owned document status labels."""
+ if self._translator:
+ translate_widget_tree(self, self._translator)
+ if self._document_manager:
+ self._refresh_tree()
+ else:
+ self._update_status()
+
def _on_document_change(self, action: str, document):
"""Handle document manager changes."""
if action in ("add", "remove", "load", "clear"):
@@ -273,23 +297,23 @@ def _show_context_menu(self, position):
menu = QMenu(self)
# Sub-query actions
- menu.addAction("Zusammenfassung erstellen", lambda: self._add_subquery(doc_id, "summary"))
- menu.addAction("Informationen extrahieren...", lambda: self._add_subquery_custom(doc_id, "extract"))
- menu.addAction("Analysieren...", lambda: self._add_subquery_custom(doc_id, "analyze"))
- menu.addAction("Frage stellen...", lambda: self._add_subquery_custom(doc_id, "question"))
+ menu.addAction(tr(self._translator, "Zusammenfassung erstellen"), lambda: self._add_subquery(doc_id, "summary"))
+ menu.addAction(tr(self._translator, "Informationen extrahieren..."), lambda: self._add_subquery_custom(doc_id, "extract"))
+ menu.addAction(tr(self._translator, "Analysieren..."), lambda: self._add_subquery_custom(doc_id, "analyze"))
+ menu.addAction(tr(self._translator, "Frage stellen..."), lambda: self._add_subquery_custom(doc_id, "question"))
menu.addSeparator()
# Quick templates
- submenu = menu.addMenu("Schnellanalyse")
- submenu.addAction("Kernaussagen", lambda: self._add_subquery(doc_id, "key_points"))
- submenu.addAction("Zeitachse", lambda: self._add_subquery(doc_id, "timeline"))
- submenu.addAction("Personen/Orte", lambda: self._add_subquery(doc_id, "entities"))
+ submenu = menu.addMenu(tr(self._translator, "Schnellanalyse"))
+ submenu.addAction(tr(self._translator, "Kernaussagen"), lambda: self._add_subquery(doc_id, "key_points"))
+ submenu.addAction(tr(self._translator, "Zeitachse"), lambda: self._add_subquery(doc_id, "timeline"))
+ submenu.addAction(tr(self._translator, "Personen/Orte"), lambda: self._add_subquery(doc_id, "entities"))
menu.addSeparator()
# Document actions
- menu.addAction("Entfernen", lambda: self._remove_document(doc_id))
+ menu.addAction(tr(self._translator, "Entfernen"), lambda: self._remove_document(doc_id))
menu.exec(self.tree.mapToGlobal(position))
diff --git a/src/gui/i18n.py b/src/gui/i18n.py
new file mode 100644
index 0000000..996a2d8
--- /dev/null
+++ b/src/gui/i18n.py
@@ -0,0 +1,577 @@
+"""Runtime helpers for NoteSpaceLLM's desktop interface localization."""
+
+from __future__ import annotations
+
+from pathlib import Path
+from typing import Any
+
+from PySide6.QtCore import QObject, QSettings, QTimer, Qt
+from PySide6.QtGui import QAction
+from PySide6.QtWidgets import (
+ QCheckBox,
+ QComboBox,
+ QDialog,
+ QDialogButtonBox,
+ QFileDialog,
+ QGroupBox,
+ QInputDialog,
+ QLabel,
+ QLineEdit,
+ QMainWindow,
+ QMenu,
+ QMessageBox,
+ QPlainTextEdit,
+ QPushButton,
+ QRadioButton,
+ QStatusBar,
+ QTabWidget,
+ QTextEdit,
+ QToolBar,
+ QTreeWidget,
+)
+import re
+from string import Formatter
+
+from translator import (
+ DEFAULT_LANGUAGE,
+ LANGUAGE_NAMES,
+ SUPPORTED_LANGUAGES,
+ TranslationSystem,
+)
+
+
+SETTINGS_ORGANIZATION = "BACH"
+SETTINGS_APPLICATION = "NoteSpaceLLM"
+UI_LANGUAGE_KEY = "ui_language"
+
+_SOURCE_PROPERTY = "_notespacellm_i18n_source"
+_NO_TRANSLATE_PROPERTY = "_notespacellm_i18n_skip"
+_TRANSLATE_PROPERTY = "_notespacellm_i18n_translatable"
+_COMBO_ITEMS_PROPERTY = "_notespacellm_i18n_combo_items"
+_TAB_ITEMS_PROPERTY = "_notespacellm_i18n_tab_items"
+_TREE_HEADERS_PROPERTY = "_notespacellm_i18n_tree_headers"
+
+
+def make_ui_settings() -> QSettings:
+ """Create the per-application preference store used only for the UI locale."""
+ return QSettings(SETTINGS_ORGANIZATION, SETTINGS_APPLICATION)
+
+
+def load_ui_language(settings: QSettings) -> str:
+ language = settings.value(UI_LANGUAGE_KEY, DEFAULT_LANGUAGE, type=str)
+ return language if language in SUPPORTED_LANGUAGES else DEFAULT_LANGUAGE
+
+
+def create_translator(settings: QSettings, app_dir: Path | None = None) -> TranslationSystem:
+ if app_dir is None:
+ app_dir = Path(__file__).resolve().parents[2]
+ return TranslationSystem(load_ui_language(settings), app_dir=app_dir)
+
+
+def persist_ui_language(settings: QSettings, translator: TranslationSystem, language: str) -> bool:
+ """Persist a valid UI locale; restore the old runtime state if QSettings fails."""
+ if language not in SUPPORTED_LANGUAGES:
+ return False
+ previous = translator.get_language()
+ try:
+ settings.setValue(UI_LANGUAGE_KEY, language)
+ settings.sync()
+ if settings.status() != QSettings.Status.NoError:
+ settings.setValue(UI_LANGUAGE_KEY, previous)
+ settings.sync()
+ return False
+ except Exception:
+ try:
+ settings.setValue(UI_LANGUAGE_KEY, previous)
+ settings.sync()
+ except Exception:
+ pass
+ return False
+ translator.set_language(language)
+ return True
+
+
+def tr(translator: TranslationSystem | None, source: str, **values: Any) -> str:
+ """Translate source UI text and safely format its named placeholders."""
+ translated = translator.t(source) if translator is not None else source
+ if not values:
+ return translated
+ try:
+ return translated.format(**values)
+ except (KeyError, IndexError, TypeError, ValueError):
+ # A stale or malformed translation must not break an interaction.
+ try:
+ return source.format(**values)
+ except (KeyError, IndexError, TypeError, ValueError):
+ return source
+
+
+class _PreformattedValue:
+ """Keep the already-rendered opaque portion of a dynamic UI message intact."""
+
+ def __init__(self, value: str):
+ self.value = value
+
+ def __format__(self, _format_spec: str) -> str:
+ return self.value
+
+
+def find_translator(parent: QObject | None) -> TranslationSystem | None:
+ current = parent
+ while current is not None:
+ translator = getattr(current, "_translator", None)
+ if isinstance(translator, TranslationSystem):
+ return translator
+ current = current.parent() if callable(getattr(current, "parent", None)) else None
+ return None
+
+
+def translate_ui_text(text: str, translator: TranslationSystem | None) -> str:
+ """Translate an exact UI source or a catalog template, preserving opaque values."""
+ if translator is None or not isinstance(text, str):
+ return text
+ if translator.has_translation_source(text):
+ return translator.t(text)
+
+ # A few application-owned checkboxes append a protected format suffix to
+ # a translatable label, e.g. "Word (.docx)". Translate only the label.
+ suffix_label = re.fullmatch(r"(.*?)\s+(\(\.[A-Za-z0-9]+(?:\s+\.[A-Za-z0-9]+)*\))", text)
+ if suffix_label and translator.has_translation_source(suffix_label.group(1)):
+ return f"{translator.t(suffix_label.group(1))} {suffix_label.group(2)}"
+
+ # Match only explicit catalog templates. Captured runtime values are copied
+ # verbatim into the translated template; they are never translated.
+ for source in translator.ui_translations:
+ if "{" not in source or "}" not in source:
+ continue
+ try:
+ parts = list(Formatter().parse(source))
+ except ValueError:
+ continue
+ pattern = []
+ names = []
+ valid = True
+ for literal, field_name, _format_spec, _conversion in parts:
+ pattern.append(re.escape(literal))
+ if field_name is None:
+ continue
+ if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", field_name):
+ valid = False
+ break
+ names.append(field_name)
+ pattern.append("(.*?)")
+ if not valid or not names:
+ continue
+ match = re.fullmatch("".join(pattern), text, flags=re.DOTALL)
+ if not match:
+ continue
+ values = {name: _PreformattedValue(value) for name, value in zip(names, match.groups())}
+ return tr(translator, source, **values)
+ return text
+
+
+class TranslatableLabel(QLabel):
+ """Opt-in label for static application chrome.
+
+ Plain QLabel instances are treated as data by the widget-tree translator.
+ This prevents a project/document/workflow value that happens to equal a UI
+ source string from being rewritten during a live language change.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.setProperty(_TRANSLATE_PROPERTY, True)
+
+
+class LocalizedLabel(QLabel):
+ """Label for app-owned status text that can replay its source template."""
+
+ def __init__(self, source_text: str = "", translator: TranslationSystem | None = None, parent=None):
+ self._translator = translator
+ self._source_text = ""
+ super().__init__(parent)
+ self.setProperty(_TRANSLATE_PROPERTY, True)
+ self.setText(source_text)
+
+ def setText(self, text: str) -> None:
+ self._source_text = text
+ if self.property(_NO_TRANSLATE_PROPERTY):
+ super().setText(text)
+ else:
+ super().setText(translate_ui_text(text, self._translator))
+
+ def retranslate(self) -> None:
+ if self.property(_NO_TRANSLATE_PROPERTY):
+ super().setText(self._source_text)
+ else:
+ super().setText(translate_ui_text(self._source_text, self._translator))
+
+
+class LocalizedStatusBar(QStatusBar):
+ """Status bar that translates catalog-backed messages and can replay its source."""
+
+ def __init__(self, translator: TranslationSystem, parent=None):
+ self._translator = translator
+ self._source_message = ""
+ self._message_timeout = 0
+ super().__init__(parent)
+ self.messageChanged.connect(self._on_message_changed)
+
+ def _on_message_changed(self, message: str) -> None:
+ # QStatusBar clears timed messages itself. Mirror that expiration in
+ # our replay state so a later language switch cannot resurrect it.
+ if not message:
+ self._source_message = ""
+ self._message_timeout = 0
+
+ def showMessage(self, message: str, timeout: int = 0) -> None:
+ self._source_message = message
+ self._message_timeout = timeout
+ super().showMessage(translate_ui_text(message, self._translator), timeout)
+
+ def retranslate(self) -> None:
+ if self._source_message:
+ super().showMessage(
+ translate_ui_text(self._source_message, self._translator),
+ self._message_timeout,
+ )
+
+ def clearMessage(self) -> None:
+ self._source_message = ""
+ super().clearMessage()
+
+
+def mark_not_translatable(widget: QObject) -> None:
+ """Keep user data and internal values out of the widget-tree translator."""
+ widget.setProperty(_NO_TRANSLATE_PROPERTY, True)
+ widget.setProperty(_TRANSLATE_PROPERTY, False)
+
+
+def mark_combo_items_translatable(combo: QObject, source_values: list[str] | None = None) -> None:
+ """Allow selected built-in combo labels to follow the current UI language."""
+ if source_values is None:
+ source_values = [combo.itemText(index) for index in range(combo.count())]
+ combo.setProperty(_COMBO_ITEMS_PROPERTY, source_values)
+
+
+def _source_for(widget: QObject, getter) -> str:
+ source = widget.property(_SOURCE_PROPERTY)
+ if isinstance(source, str):
+ return source
+ current = getter()
+ if not isinstance(current, str) or not current:
+ return ""
+ widget.setProperty(_SOURCE_PROPERTY, current)
+ return current
+
+
+def _apply_text(widget: QObject, translator: TranslationSystem, getter, setter) -> None:
+ source = _source_for(widget, getter)
+ if source and translator.has_translation_source(source):
+ setter(tr(translator, source))
+
+
+def _apply_property(widget: QObject, translator: TranslationSystem, getter_name: str, setter_name: str) -> None:
+ getter = getattr(widget, getter_name, None)
+ setter = getattr(widget, setter_name, None)
+ if callable(getter) and callable(setter):
+ _apply_text(widget, translator, getter, setter)
+
+
+def translate_widget_tree(root: QObject, translator: TranslationSystem) -> None:
+ """Retranslate catalog-backed GUI chrome while leaving dynamic user data alone."""
+ objects = [root, *root.findChildren(QObject)]
+ for widget in objects:
+ if widget.property(_NO_TRANSLATE_PROPERTY):
+ continue
+ if isinstance(widget, LocalizedLabel):
+ # Its current status source is dynamic and authoritative. Do not
+ # cache/replay the text currently rendered by an older locale.
+ widget.retranslate()
+ elif isinstance(widget, QLabel):
+ if widget.property(_TRANSLATE_PROPERTY):
+ _apply_property(widget, translator, "text", "setText")
+ elif isinstance(widget, (QPushButton, QCheckBox, QRadioButton, QAction)):
+ _apply_property(widget, translator, "text", "setText")
+ if isinstance(widget, (QGroupBox, QMenu, QToolBar, QDialog, QMainWindow)):
+ _apply_property(widget, translator, "title", "setTitle")
+ _apply_property(widget, translator, "windowTitle", "setWindowTitle")
+ if hasattr(widget, "toolTip") and hasattr(widget, "setToolTip"):
+ _apply_property(widget, translator, "toolTip", "setToolTip")
+ if hasattr(widget, "statusTip") and hasattr(widget, "setStatusTip"):
+ _apply_property(widget, translator, "statusTip", "setStatusTip")
+ if isinstance(widget, (QLineEdit, QTextEdit, QPlainTextEdit)):
+ _apply_property(widget, translator, "placeholderText", "setPlaceholderText")
+ if isinstance(widget, QTabWidget):
+ original = widget.property(_TAB_ITEMS_PROPERTY)
+ if not isinstance(original, list):
+ original = [widget.tabText(index) for index in range(widget.count())]
+ widget.setProperty(_TAB_ITEMS_PROPERTY, original)
+ for index, source in enumerate(original):
+ if index < widget.count() and translator.has_translation_source(source):
+ widget.setTabText(index, tr(translator, source))
+ if isinstance(widget, QTreeWidget):
+ original = widget.property(_TREE_HEADERS_PROPERTY)
+ if not isinstance(original, list):
+ header = widget.headerItem()
+ original = [header.text(index) for index in range(widget.columnCount())] if header else []
+ widget.setProperty(_TREE_HEADERS_PROPERTY, original)
+ if original:
+ headers = [tr(translator, value) if translator.has_translation_source(value) else value for value in original]
+ widget.setHeaderLabels(headers)
+ if isinstance(widget, QComboBox):
+ original = widget.property(_COMBO_ITEMS_PROPERTY)
+ if isinstance(original, list):
+ old_index = widget.currentIndex()
+ previous_block = widget.blockSignals(True)
+ try:
+ for index, source in enumerate(original):
+ if index < widget.count() and translator.has_translation_source(source):
+ widget.setItemText(index, tr(translator, source))
+ if old_index >= 0:
+ widget.setCurrentIndex(old_index)
+ finally:
+ widget.blockSignals(previous_block)
+
+ if isinstance(widget, QDialogButtonBox):
+ translate_standard_buttons(widget, translator)
+
+
+_STANDARD_BUTTON_SOURCES = {
+ QDialogButtonBox.StandardButton.Ok: "OK",
+ QDialogButtonBox.StandardButton.Cancel: "Abbrechen",
+ QDialogButtonBox.StandardButton.Yes: "Ja",
+ QDialogButtonBox.StandardButton.No: "Nein",
+ QDialogButtonBox.StandardButton.Close: "Schließen",
+}
+
+
+def translate_standard_buttons(root: QObject, translator: TranslationSystem) -> None:
+ """Translate Qt-owned standard button captions without touching button roles."""
+ if isinstance(root, QMessageBox):
+ for standard, source in _STANDARD_BUTTON_SOURCES.items():
+ button = root.button(QMessageBox.StandardButton(standard.value))
+ if button is not None and translator.has_translation_source(source):
+ button.setProperty(_SOURCE_PROPERTY, source)
+ button.setText(tr(translator, source))
+ boxes = [root] if isinstance(root, QDialogButtonBox) else root.findChildren(QDialogButtonBox)
+ for box in boxes:
+ for standard, source in _STANDARD_BUTTON_SOURCES.items():
+ button = box.button(standard)
+ if button is not None and translator.has_translation_source(source):
+ button.setProperty(_SOURCE_PROPERTY, source)
+ button.setText(tr(translator, source))
+
+
+def _translate_filter_entry(item: str, translator: TranslationSystem | None) -> str:
+ match = re.fullmatch(r"(.*?)\s+(\(.*\))", item)
+ if not match:
+ return translate_ui_text(item, translator)
+ label, patterns = match.groups()
+ return f"{translate_ui_text(label, translator)} {patterns}"
+
+
+def _translated_filters(filters: str, translator: TranslationSystem | None) -> list[str]:
+ return [_translate_filter_entry(item, translator) for item in filters.split(";;")]
+
+
+def _translate_input_dialog_buttons(dialog: QInputDialog, translator: TranslationSystem | None) -> None:
+ """Set Qt's built-in input-dialog captions before it creates its buttons."""
+ if translator is None:
+ return
+ cancel = translate_ui_text("Abbrechen", translator)
+ ok = translate_ui_text("OK", translator)
+ dialog.setCancelButtonText(cancel)
+ dialog.setOkButtonText(ok)
+ # Keep this pass for Qt styles that materialize the button box early.
+ translate_standard_buttons(dialog, translator)
+ QTimer.singleShot(0, lambda: translate_standard_buttons(dialog, translator))
+
+
+class LocalizedInputDialog:
+ """Drop-in static helpers that translate app copy and standard buttons."""
+
+ @staticmethod
+ def getText(parent, title, label, echo=QLineEdit.EchoMode.Normal, text="", flags=Qt.WindowType.Dialog,
+ inputMethodHints=Qt.InputMethodHint.ImhNone):
+ dialog = QInputDialog(parent, flags)
+ translator = find_translator(parent)
+ dialog.setWindowTitle(translate_ui_text(title, translator))
+ dialog.setLabelText(translate_ui_text(label, translator))
+ dialog.setTextEchoMode(echo)
+ dialog.setTextValue(text)
+ dialog.setInputMethodHints(inputMethodHints)
+ if translator:
+ _translate_input_dialog_buttons(dialog, translator)
+ accepted = dialog.exec() == QDialog.DialogCode.Accepted
+ return dialog.textValue(), accepted
+
+ @staticmethod
+ def getItem(parent, title, label, items, current=0, editable=True, flags=Qt.WindowType.Dialog,
+ inputMethodHints=Qt.InputMethodHint.ImhNone):
+ dialog = QInputDialog(parent, flags)
+ translator = find_translator(parent)
+ dialog.setWindowTitle(translate_ui_text(title, translator))
+ dialog.setLabelText(translate_ui_text(label, translator))
+ dialog.setComboBoxItems(items)
+ dialog.setComboBoxEditable(editable)
+ dialog.setTextValue(items[current] if 0 <= current < len(items) else "")
+ dialog.setInputMethodHints(inputMethodHints)
+ if translator:
+ _translate_input_dialog_buttons(dialog, translator)
+ accepted = dialog.exec() == QDialog.DialogCode.Accepted
+ return dialog.textValue(), accepted
+
+
+class LocalizedFileDialog:
+ """Static QFileDialog-compatible calls; filters keep their file patterns verbatim."""
+
+ @staticmethod
+ def getOpenFileNames(parent=None, caption="", directory="", filter="", selectedFilter="", options=QFileDialog.Option(0)):
+ translator = find_translator(parent)
+ dialog = QFileDialog(parent)
+ dialog.setWindowTitle(translate_ui_text(caption, translator))
+ if directory:
+ dialog.setDirectory(directory)
+ translated_filters = _translated_filters(filter, translator)
+ if translated_filters:
+ dialog.setNameFilters(translated_filters)
+ if selectedFilter:
+ dialog.selectNameFilter(_translate_filter_entry(selectedFilter, translator))
+ dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
+ dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
+ dialog.setOptions(options)
+ if dialog.exec() == QDialog.DialogCode.Accepted:
+ return dialog.selectedFiles(), dialog.selectedNameFilter()
+ return [], dialog.selectedNameFilter()
+
+ @staticmethod
+ def getSaveFileName(parent=None, caption="", directory="", filter="", selectedFilter="", options=QFileDialog.Option(0)):
+ translator = find_translator(parent)
+ dialog = QFileDialog(parent)
+ dialog.setWindowTitle(translate_ui_text(caption, translator))
+ if directory:
+ dialog.setDirectory(directory)
+ translated_filters = _translated_filters(filter, translator)
+ if translated_filters:
+ dialog.setNameFilters(translated_filters)
+ if selectedFilter:
+ dialog.selectNameFilter(_translate_filter_entry(selectedFilter, translator))
+ preferred_filter = selectedFilter or (filter.split(";;", maxsplit=1)[0] if filter else "")
+ suffix_match = re.search(r"\*\.([A-Za-z0-9]+)", preferred_filter)
+ if suffix_match:
+ dialog.setDefaultSuffix(suffix_match.group(1))
+ dialog.setFileMode(QFileDialog.FileMode.AnyFile)
+ dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
+ dialog.setOptions(options)
+ if dialog.exec() == QDialog.DialogCode.Accepted:
+ return dialog.selectedFiles()[0], dialog.selectedNameFilter()
+ return "", dialog.selectedNameFilter()
+
+ @staticmethod
+ def getExistingDirectory(parent=None, caption="", directory="", options=QFileDialog.Option.ShowDirsOnly):
+ translator = find_translator(parent)
+ dialog = QFileDialog(parent)
+ dialog.setWindowTitle(translate_ui_text(caption, translator))
+ if directory:
+ dialog.setDirectory(directory)
+ dialog.setFileMode(QFileDialog.FileMode.Directory)
+ dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
+ dialog.setOptions(options)
+ if dialog.exec() == QDialog.DialogCode.Accepted:
+ selected = dialog.selectedFiles()
+ return selected[0] if selected else ""
+ return ""
+
+
+class LocalizedMessageBox:
+ """QMessageBox static-call facade with app-locale text and button captions."""
+
+ StandardButton = QMessageBox.StandardButton
+ Icon = QMessageBox.Icon
+
+ _ICONS = {
+ "information": QMessageBox.Icon.Information,
+ "about": QMessageBox.Icon.Information,
+ "warning": QMessageBox.Icon.Warning,
+ "critical": QMessageBox.Icon.Critical,
+ "question": QMessageBox.Icon.Question,
+ }
+
+ @classmethod
+ def _show(cls, kind, parent, title, text, buttons=None, default_button=QMessageBox.StandardButton.NoButton):
+ translator = find_translator(parent)
+ box = QMessageBox(parent)
+ box.setIcon(cls._ICONS[kind])
+ box.setWindowTitle(translate_ui_text(title, translator))
+ box.setText(translate_ui_text(text, translator))
+ if buttons is None:
+ if kind == "question":
+ buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
+ else:
+ buttons = QMessageBox.StandardButton.Ok
+ box.setStandardButtons(buttons)
+ if default_button != QMessageBox.StandardButton.NoButton:
+ box.setDefaultButton(default_button)
+ if translator:
+ translate_standard_buttons(box, translator)
+ return box.exec()
+
+ @classmethod
+ def information(cls, parent, title, text, buttons=QMessageBox.StandardButton.Ok,
+ defaultButton=QMessageBox.StandardButton.NoButton):
+ return cls._show("information", parent, title, text, buttons, defaultButton)
+
+ @classmethod
+ def warning(cls, parent, title, text, buttons=QMessageBox.StandardButton.Ok,
+ defaultButton=QMessageBox.StandardButton.NoButton):
+ return cls._show("warning", parent, title, text, buttons, defaultButton)
+
+ @classmethod
+ def critical(cls, parent, title, text, buttons=QMessageBox.StandardButton.Ok,
+ defaultButton=QMessageBox.StandardButton.NoButton):
+ return cls._show("critical", parent, title, text, buttons, defaultButton)
+
+ @classmethod
+ def question(cls, parent, title, text,
+ buttons=QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
+ defaultButton=QMessageBox.StandardButton.NoButton):
+ return cls._show("question", parent, title, text, buttons, defaultButton)
+
+ @classmethod
+ def about(cls, parent, title, text):
+ return cls._show("about", parent, title, text, QMessageBox.StandardButton.Ok)
+
+
+def set_translated_text(widget: QObject, source: str, translator: TranslationSystem) -> None:
+ """Set and retain the source string for a later locale change."""
+ widget.setProperty(_SOURCE_PROPERTY, source)
+ widget.setProperty(_TRANSLATE_PROPERTY, True)
+ widget.setText(tr(translator, source))
+
+
+__all__ = [
+ "LANGUAGE_NAMES",
+ "SETTINGS_APPLICATION",
+ "SETTINGS_ORGANIZATION",
+ "SUPPORTED_LANGUAGES",
+ "UI_LANGUAGE_KEY",
+ "create_translator",
+ "find_translator",
+ "load_ui_language",
+ "make_ui_settings",
+ "mark_combo_items_translatable",
+ "mark_not_translatable",
+ "LocalizedFileDialog",
+ "LocalizedInputDialog",
+ "LocalizedLabel",
+ "LocalizedMessageBox",
+ "LocalizedStatusBar",
+ "TranslatableLabel",
+ "persist_ui_language",
+ "set_translated_text",
+ "tr",
+ "translate_ui_text",
+ "translate_standard_buttons",
+ "translate_widget_tree",
+]
diff --git a/src/gui/main_window.py b/src/gui/main_window.py
index a874152..d1279f7 100644
--- a/src/gui/main_window.py
+++ b/src/gui/main_window.py
@@ -36,6 +36,23 @@
from PySide6.QtCore import QThread, Signal
from .worker_utils import retain_until_finished, stop_workers
+ from .i18n import (
+ LANGUAGE_NAMES,
+ SUPPORTED_LANGUAGES,
+ LocalizedFileDialog as QFileDialog,
+ LocalizedInputDialog as QInputDialog,
+ LocalizedLabel,
+ LocalizedMessageBox as QMessageBox,
+ LocalizedStatusBar,
+ TranslatableLabel,
+ create_translator,
+ make_ui_settings,
+ persist_ui_language,
+ translate_widget_tree,
+ mark_combo_items_translatable,
+ tr,
+ )
+ QLabel = TranslatableLabel
class AnalysisWorker(QThread):
"""Worker thread for batch sub-query analysis."""
@@ -226,12 +243,16 @@ class _PipelineHelpDialog(QDialog):
},
]
- def __init__(self, parent=None):
+ def __init__(self, parent=None, translator=None):
super().__init__(parent)
+ self._translator = translator
+ self._step_title_labels = []
self.setWindowTitle("Pipeline-Übersicht")
self.setMinimumSize(560, 560)
self.resize(620, 640)
self._setup_ui()
+ if translator:
+ self.retranslate_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
@@ -335,12 +356,13 @@ def _make_step_card(self, step: dict, is_last: bool) -> QWidget:
# Title row
title_row = QHBoxLayout()
icon_label = QLabel(f"{step['icon']} {step['title']}")
+ self._step_title_labels.append((icon_label, step["icon"], step["title"]))
icon_label.setStyleSheet(
f"font-weight: 700; font-size: 13px; color: #1a1a2e;"
)
title_row.addWidget(icon_label)
if step["optional"]:
- badge = QLabel("optional")
+ badge = QLabel(tr(self._translator, "optional"))
badge.setStyleSheet(
"background: #fff3cd; color: #856404; padding: 1px 7px; "
"border-radius: 10px; font-size: 10px; font-weight: 600;"
@@ -359,6 +381,13 @@ def _make_step_card(self, step: dict, is_last: bool) -> QWidget:
row.addWidget(content, stretch=1)
return card
+ def retranslate_ui(self):
+ if not self._translator:
+ return
+ translate_widget_tree(self, self._translator)
+ for label, icon, source in self._step_title_labels:
+ label.setText(f"{icon} {tr(self._translator, source)}")
+
class MainWindow(QMainWindow if PYSIDE_AVAILABLE else object):
"""
@@ -384,12 +413,23 @@ class MainWindow(QMainWindow if PYSIDE_AVAILABLE else object):
+--------------------------------------------------+
"""
- def __init__(self):
+ def __init__(
+ self,
+ *,
+ settings=None,
+ translator=None,
+ project_manager=None,
+ start_background_tasks: bool = True,
+ ):
if not PYSIDE_AVAILABLE:
raise ImportError("PySide6 is required. Install with: pip install PySide6")
super().__init__()
+ self._settings = settings or make_ui_settings()
+ self._translator = translator or create_translator(self._settings)
+ self._start_background_tasks = start_background_tasks
+
# Initialize components
from ..core.document_manager import DocumentManager
from ..core.sub_query import SubQueryManager
@@ -397,10 +437,13 @@ def __init__(self):
from ..core.text_extractor import TextExtractor
# Project management
- self._projects_dir = Path.home() / "NoteSpaceLLM" / "projects"
- self._projects_dir.mkdir(parents=True, exist_ok=True)
-
- self._project_manager = ProjectManager(self._projects_dir)
+ if project_manager is None:
+ self._projects_dir = Path.home() / "NoteSpaceLLM" / "projects"
+ self._projects_dir.mkdir(parents=True, exist_ok=True)
+ self._project_manager = ProjectManager(self._projects_dir)
+ else:
+ self._projects_dir = None
+ self._project_manager = project_manager
self._current_project: Optional[Project] = None
# Core services
@@ -421,7 +464,8 @@ def __init__(self):
# bei Remote-Ollama oder langsamer Netzwerkverbindung)
self._rag_engine = None
self._rag_engine_ready = False
- QTimer.singleShot(0, self._init_rag_engine)
+ if self._start_background_tasks:
+ QTimer.singleShot(0, self._init_rag_engine)
# Setup UI
self._setup_window()
@@ -429,12 +473,14 @@ def __init__(self):
self._setup_toolbar()
self._setup_panels()
self._setup_statusbar()
+ self._apply_ui_language()
# Connect signals
self._connect_signals()
# Create new project by default
- QTimer.singleShot(100, self._create_default_project)
+ if self._start_background_tasks:
+ QTimer.singleShot(100, self._create_default_project)
def _setup_window(self):
"""Configure the main window."""
@@ -633,23 +679,23 @@ def _setup_panels(self):
main_splitter = QSplitter(Qt.Orientation.Horizontal)
# Left: Document panel
- self.document_panel = DocumentPanel()
+ self.document_panel = DocumentPanel(translator=self._translator)
main_splitter.addWidget(self.document_panel)
# Center: Workflow + Chat
center_splitter = QSplitter(Qt.Orientation.Vertical)
- self.workflow_panel = WorkflowPanel()
+ self.workflow_panel = WorkflowPanel(translator=self._translator)
center_splitter.addWidget(self.workflow_panel)
- self.chat_panel = ChatPanel()
+ self.chat_panel = ChatPanel(translator=self._translator)
center_splitter.addWidget(self.chat_panel)
center_splitter.setSizes([400, 300])
main_splitter.addWidget(center_splitter)
# Right: Output panel
- self.output_panel = OutputPanel()
+ self.output_panel = OutputPanel(translator=self._translator)
main_splitter.addWidget(self.output_panel)
# Set splitter sizes
@@ -659,12 +705,23 @@ def _setup_panels(self):
def _setup_statusbar(self):
"""Set up the status bar."""
- self.statusbar = QStatusBar()
+ self.statusbar = LocalizedStatusBar(self._translator, self)
self.setStatusBar(self.statusbar)
self.statusbar.showMessage("Bereit")
+ # UI locale is an application preference; it is separate from project data.
+ self._language_label = QLabel("Sprache:")
+ self._language_combo = QComboBox()
+ for code in SUPPORTED_LANGUAGES:
+ self._language_combo.addItem(LANGUAGE_NAMES[code], code)
+ current_index = self._language_combo.findData(self._translator.get_language())
+ self._language_combo.setCurrentIndex(max(0, current_index))
+ self._language_combo.currentIndexChanged.connect(self._on_language_changed)
+ self.statusbar.addPermanentWidget(self._language_label)
+ self.statusbar.addPermanentWidget(self._language_combo)
+
# Permanent pipeline phase indicator (right side of statusbar)
- self._pipeline_label = QLabel("📄 Keine Dokumente")
+ self._pipeline_label = LocalizedLabel("📄 Keine Dokumente", self._translator)
self._pipeline_label.setStyleSheet("color: #888; font-size: 10px; padding: 0 8px;")
self._pipeline_label.setToolTip(
"Aktuelle Pipeline-Phase:\n"
@@ -675,6 +732,40 @@ def _setup_statusbar(self):
)
self.statusbar.addPermanentWidget(self._pipeline_label)
+ def _on_language_changed(self, index: int):
+ if index < 0:
+ return
+ language = self._language_combo.itemData(index)
+ if not isinstance(language, str) or language == self._translator.get_language():
+ return
+ if not persist_ui_language(self._settings, self._translator, language):
+ old_index = self._language_combo.findData(self._translator.get_language())
+ self._language_combo.blockSignals(True)
+ self._language_combo.setCurrentIndex(max(0, old_index))
+ self._language_combo.blockSignals(False)
+ self.statusbar.showMessage("Spracheinstellung konnte nicht gespeichert werden.")
+ return
+ self._apply_ui_language()
+
+ def _apply_ui_language(self):
+ """Retranslate application-owned UI text while leaving data fields untouched."""
+ direction = (
+ Qt.LayoutDirection.RightToLeft
+ if self._translator.get_language() == "ar"
+ else Qt.LayoutDirection.LeftToRight
+ )
+ self.setLayoutDirection(direction)
+ translate_widget_tree(self, self._translator)
+ for panel_name in ("document_panel", "workflow_panel", "chat_panel", "output_panel"):
+ panel = getattr(self, panel_name, None)
+ retranslate = getattr(panel, "retranslate_ui", None)
+ if callable(retranslate):
+ retranslate()
+ if hasattr(self, "statusbar"):
+ self.statusbar.retranslate()
+ if hasattr(self, "_current_project"):
+ self._update_pipeline_phase()
+
def _connect_signals(self):
"""Connect panel signals."""
# Document panel signals
@@ -951,7 +1042,7 @@ def _llm_settings(self):
lambda text: clear_key_check.setChecked(False) if text else None
)
- credential_status_label = QLabel("")
+ credential_status_label = LocalizedLabel("", self._translator)
credential_status_label.setWordWrap(True)
credential_status_label.setStyleSheet("color: #666; font-size: 10px;")
provider_layout.addRow("Schlüsselstatus:", credential_status_label)
@@ -1042,7 +1133,7 @@ def _migrate_legacy_credentials():
model_combo.setEditable(True)
model_combo.setMinimumWidth(300)
- status_label = QLabel("")
+ status_label = LocalizedLabel("", self._translator)
status_label.setStyleSheet("color: #666; font-size: 11px;")
refresh_btn = QPushButton("Modelle laden")
@@ -1278,6 +1369,9 @@ def _on_worker_done(finished_worker):
_refresh_credential_status(selected_profile[0])
load_models_for_provider(app_cfg.llm_provider)
+ # Translate app-owned dialog chrome and Qt standard action buttons.
+ translate_widget_tree(dialog, self._translator)
+
# ===== Dialog Accept =====
if dialog.exec() == QDialog.DialogCode.Accepted:
new_provider = provider_combo.currentText()
@@ -1984,7 +2078,7 @@ def _export_prompt(self):
def _show_pipeline_help(self):
"""Öffnet den Pipeline-Hilfe-Dialog."""
- dialog = _PipelineHelpDialog(self)
+ dialog = _PipelineHelpDialog(self, self._translator)
dialog.exec()
def _export_workspace(self):
diff --git a/src/gui/output_panel.py b/src/gui/output_panel.py
index 1557faf..474d777 100644
--- a/src/gui/output_panel.py
+++ b/src/gui/output_panel.py
@@ -20,7 +20,7 @@
QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLabel,
QPushButton, QComboBox, QFileDialog, QGroupBox, QCheckBox,
QLineEdit, QFormLayout, QProgressBar, QTabWidget,
- QMessageBox, QSplitter
+ QMessageBox, QSplitter, QInputDialog
)
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QFont, QTextCharFormat, QSyntaxHighlighter
@@ -31,6 +31,18 @@ class QWidget: pass
class Signal:
def __init__(self, *args): pass
+if PYSIDE_AVAILABLE:
+ from .i18n import (
+ LocalizedFileDialog as QFileDialog,
+ LocalizedInputDialog as QInputDialog,
+ LocalizedLabel,
+ TranslatableLabel,
+ LocalizedMessageBox as QMessageBox,
+ mark_combo_items_translatable,
+ translate_widget_tree,
+ )
+ QLabel = TranslatableLabel
+
class MarkdownHighlighter(QSyntaxHighlighter if PYSIDE_AVAILABLE else object):
"""Simple Markdown syntax highlighter."""
@@ -94,15 +106,18 @@ class OutputPanel(QWidget if PYSIDE_AVAILABLE else object):
export_requested = Signal(list, str) # formats, directory
prompt_export_requested = Signal() # Prompt als .md exportieren
- def __init__(self, parent=None):
+ def __init__(self, parent=None, translator=None):
if not PYSIDE_AVAILABLE:
raise ImportError("PySide6 is required. Install with: pip install PySide6")
super().__init__(parent)
+ self._translator = translator
self._current_content = ""
self._output_directory = Path.home() / "Documents"
self._setup_ui()
+ if self._translator:
+ self.retranslate_ui()
def _setup_ui(self):
"""Set up the user interface."""
@@ -197,7 +212,16 @@ def _setup_ui(self):
profile_layout = QFormLayout(profile_group)
self.profile_combo = QComboBox()
- self.profile_combo.addItems(["Standard", "Vollständig", "Nur Markdown", "Nur PDF", "Akademisch"])
+ profiles = [
+ ("Standard", "standard"),
+ ("Vollständig", "complete"),
+ ("Nur Markdown", "markdown"),
+ ("Nur PDF", "pdf"),
+ ("Akademisch", "academic"),
+ ]
+ for label, profile_id in profiles:
+ self.profile_combo.addItem(label, profile_id)
+ mark_combo_items_translatable(self.profile_combo, [label for label, _ in profiles])
self.profile_combo.currentTextChanged.connect(self._on_profile_changed)
self.save_profile_btn = QPushButton("Als Profil speichern")
@@ -261,7 +285,7 @@ def _setup_ui(self):
layout.addLayout(export_bar)
# Status
- self.status_label = QLabel("Bereit zum Exportieren")
+ self.status_label = LocalizedLabel("Bereit zum Exportieren", self._translator)
self.status_label.setStyleSheet("color: #888; font-size: 10px;")
layout.addWidget(self.status_label)
@@ -270,6 +294,11 @@ def set_content(self, content: str):
self._current_content = content
self.preview_edit.setPlainText(content)
+ def retranslate_ui(self):
+ if self._translator:
+ translate_widget_tree(self, self._translator)
+ self.status_label.retranslate()
+
def append_content(self, text: str):
"""Append text to the content (for streaming)."""
self._current_content += text
@@ -310,22 +339,21 @@ def _browse_directory(self):
def _on_profile_changed(self, profile_name: str):
"""Handle profile selection change."""
profiles = {
- "Standard": ["md", "pdf"],
- "Vollständig": ["md", "pdf", "docx", "html"],
- "Nur Markdown": ["md"],
- "Nur PDF": ["pdf"],
- "Akademisch": ["md", "pdf", "docx"]
+ "standard": ["md", "pdf"],
+ "complete": ["md", "pdf", "docx", "html"],
+ "markdown": ["md"],
+ "pdf": ["pdf"],
+ "academic": ["md", "pdf", "docx"],
}
- formats = profiles.get(profile_name, ["md"])
+ profile_id = self.profile_combo.currentData()
+ formats = profiles.get(profile_id, profiles.get(profile_name, ["md"]))
for fmt, cb in self.format_checks.items():
cb.setChecked(fmt in formats)
def _save_profile(self):
"""Save current format selection as profile."""
- from PySide6.QtWidgets import QInputDialog
-
name, ok = QInputDialog.getText(self, "Profil speichern", "Profilname:")
if ok and name:
# Add to combo
diff --git a/src/gui/workflow_panel.py b/src/gui/workflow_panel.py
index 8712ec1..e6b43dd 100644
--- a/src/gui/workflow_panel.py
+++ b/src/gui/workflow_panel.py
@@ -26,6 +26,16 @@
from PySide6.QtCore import Qt, Signal, QSize
from PySide6.QtGui import QPainter, QColor, QPen, QFont
PYSIDE_AVAILABLE = True
+ from .i18n import (
+ LocalizedLabel,
+ TranslatableLabel,
+ mark_combo_items_translatable,
+ mark_not_translatable,
+ translate_ui_text,
+ translate_widget_tree,
+ tr,
+ )
+ QLabel = TranslatableLabel
except ImportError:
PYSIDE_AVAILABLE = False
class QWidget: pass
@@ -170,11 +180,12 @@ class WorkflowStepWidget(QFrame if PYSIDE_AVAILABLE else object):
if PYSIDE_AVAILABLE:
clicked = Signal(str) # step_id
- def __init__(self, step: WorkflowStep, parent=None):
+ def __init__(self, step: WorkflowStep, parent=None, translator=None):
if not PYSIDE_AVAILABLE:
return
super().__init__(parent)
self.step = step
+ self._translator = translator
self._setup_ui()
def _setup_ui(self):
@@ -188,6 +199,7 @@ def _setup_ui(self):
header = QHBoxLayout()
self.number_label = QLabel(f"{self.step.order}")
+ mark_not_translatable(self.number_label)
self.number_label.setStyleSheet("""
background-color: #3498db;
color: white;
@@ -199,6 +211,7 @@ def _setup_ui(self):
self.number_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.name_label = QLabel(self.step.name)
+ mark_not_translatable(self.name_label)
self.name_label.setStyleSheet("font-weight: bold; font-size: 12px;")
header.addWidget(self.number_label)
@@ -209,12 +222,13 @@ def _setup_ui(self):
# Description
self.desc_label = QLabel(self.step.description)
+ mark_not_translatable(self.desc_label)
self.desc_label.setWordWrap(True)
self.desc_label.setStyleSheet("color: #666; font-size: 10px;")
layout.addWidget(self.desc_label)
# Status indicator
- self.status_label = QLabel(self._status_text())
+ self.status_label = LocalizedLabel(self._status_text(), self._translator)
self.status_label.setStyleSheet(self._status_style())
layout.addWidget(self.status_label)
@@ -228,7 +242,7 @@ def _status_text(self) -> str:
"completed": "Fertig",
"error": "Fehler"
}
- return status_map.get(self.step.status, self.step.status)
+ return tr(self._translator, status_map.get(self.step.status, self.step.status))
def _status_style(self) -> str:
colors = {
@@ -276,17 +290,20 @@ class WorkflowPanel(QWidget if PYSIDE_AVAILABLE else object):
step_clicked = Signal(str) # step_id
start_requested = Signal()
- def __init__(self, parent=None):
+ def __init__(self, parent=None, translator=None):
if not PYSIDE_AVAILABLE:
raise ImportError("PySide6 is required. Install with: pip install PySide6")
super().__init__(parent)
+ self._translator = translator
self._workflows = dict(DEFAULT_WORKFLOWS)
self._current_workflow: Optional[Workflow] = None
self._step_widgets: List[WorkflowStepWidget] = []
self._setup_ui()
self._load_workflow("standard")
+ if self._translator:
+ self.retranslate_ui()
def _setup_ui(self):
"""Set up the user interface."""
@@ -298,13 +315,16 @@ def _setup_ui(self):
selection_layout = QFormLayout(selection_group)
self.report_type_combo = QComboBox()
- self.report_type_combo.addItems([
- "Analyse",
- "Zusammenfassung",
- "Forschungsbericht",
- "Vergleich"
- ])
- self.report_type_combo.currentTextChanged.connect(self._on_report_type_changed)
+ report_types = [
+ ("Analyse", "analysis"),
+ ("Zusammenfassung", "summary"),
+ ("Forschungsbericht", "research"),
+ ("Vergleich", "comparison"),
+ ]
+ for label, value in report_types:
+ self.report_type_combo.addItem(label, value)
+ mark_combo_items_translatable(self.report_type_combo, [label for label, _ in report_types])
+ self.report_type_combo.currentIndexChanged.connect(self._on_report_type_changed)
self.workflow_combo = QComboBox()
for wf in self._workflows.values():
@@ -389,7 +409,7 @@ def _load_workflow(self, workflow_id: str):
# Add step widgets
for step in sorted(self._current_workflow.steps, key=lambda s: s.order):
- widget = WorkflowStepWidget(step, self.steps_container)
+ widget = WorkflowStepWidget(step, self.steps_container, self._translator)
widget.clicked.connect(self._on_step_clicked)
self._step_widgets.append(widget)
self.steps_layout.addWidget(widget)
@@ -397,16 +417,16 @@ def _load_workflow(self, workflow_id: str):
# Add stretch at the end
self.steps_layout.addStretch()
- def _on_report_type_changed(self, text: str):
+ def _on_report_type_changed(self, _index: int):
"""Handle report type change."""
type_map = {
- "Analyse": "standard",
- "Zusammenfassung": "quick",
- "Forschungsbericht": "research",
- "Vergleich": "comparison"
+ "analysis": "standard",
+ "summary": "quick",
+ "research": "research",
+ "comparison": "comparison",
}
- workflow_id = type_map.get(text, "standard")
+ workflow_id = type_map.get(self.report_type_combo.currentData(), "standard")
idx = self.workflow_combo.findData(workflow_id)
if idx >= 0:
self.workflow_combo.setCurrentIndex(idx)
@@ -427,7 +447,7 @@ def _on_edit_workflow(self):
if not self._current_workflow:
return
- dialog = WorkflowEditorDialog(self._current_workflow, self)
+ dialog = WorkflowEditorDialog(self._current_workflow, self, self._translator)
if dialog.exec() == QDialog.DialogCode.Accepted:
# Update workflow
self._load_workflow(self._current_workflow.id)
@@ -461,6 +481,12 @@ def add_workflow(self, workflow: Workflow):
self._workflows[workflow.id] = workflow
self.workflow_combo.addItem(workflow.name, workflow.id)
+ def retranslate_ui(self):
+ if self._translator:
+ translate_widget_tree(self, self._translator)
+ for widget in self._step_widgets:
+ widget.update_status(widget.step.status)
+
def save_workflows(self, filepath: Path):
"""Save workflows to file."""
data = {wf_id: wf.to_dict() for wf_id, wf in self._workflows.items()}
@@ -488,15 +514,20 @@ def load_workflows(self, filepath: Path):
class WorkflowEditorDialog(QDialog if PYSIDE_AVAILABLE else object):
"""Dialog for editing workflow steps."""
- def __init__(self, workflow: Workflow, parent=None):
+ def __init__(self, workflow: Workflow, parent=None, translator=None):
if not PYSIDE_AVAILABLE:
return
super().__init__(parent)
self.workflow = workflow
+ self._translator = translator
self._setup_ui()
+ if translator:
+ translate_widget_tree(self, translator)
def _setup_ui(self):
- self.setWindowTitle(f"Workflow bearbeiten: {self.workflow.name}")
+ self.setWindowTitle(
+ translate_ui_text(f"Workflow bearbeiten: {self.workflow.name}", self._translator)
+ )
self.setMinimumSize(500, 400)
layout = QVBoxLayout(self)
diff --git a/tests/test_gui_i18n.py b/tests/test_gui_i18n.py
new file mode 100644
index 0000000..9f78869
--- /dev/null
+++ b/tests/test_gui_i18n.py
@@ -0,0 +1,364 @@
+import json
+from datetime import datetime
+from pathlib import Path
+
+import pytest
+from PySide6.QtCore import QSettings, QTimer, Qt
+from PySide6.QtTest import QTest
+from PySide6.QtWidgets import (
+ QApplication,
+ QCheckBox,
+ QComboBox,
+ QDialog,
+ QDialogButtonBox,
+ QLabel,
+ QLineEdit,
+ QMainWindow,
+ QMessageBox,
+ QInputDialog,
+ QPushButton,
+ QTreeWidgetItem,
+ QVBoxLayout,
+ QWidget,
+)
+
+from src.gui.i18n import (
+ LANGUAGE_NAMES,
+ LocalizedLabel,
+ LocalizedStatusBar,
+ SUPPORTED_LANGUAGES,
+ LocalizedInputDialog,
+ LocalizedMessageBox,
+ UI_LANGUAGE_KEY,
+ _translated_filters,
+ load_ui_language,
+ persist_ui_language,
+ translate_ui_text,
+ translate_widget_tree,
+)
+from src.gui.main_window import MainWindow
+from src.gui.chat_panel import ChatMessage
+from translator import TranslationSystem
+
+
+@pytest.fixture(scope="module")
+def qapp():
+ app = QApplication.instance() or QApplication([])
+ yield app
+
+
+def write_test_catalog(app_dir: Path) -> TranslationSystem:
+ entries = [
+ ("gui.language.label", "Sprache:", {"fr": "Langue :", "ar": "اللغة:"}),
+ ("gui.common.open", "Öffnen", {"en": "Open", "fr": "Ouvrir"}),
+ ("gui.common.input_placeholder", "Eingabe:", {"en": "Input:", "fr": "Saisie :"}),
+ ("gui.common.cancel", "Abbrechen", {"fr": "Annuler", "ar": "إلغاء"}),
+ ("gui.common.ok", "OK", {"fr": "OK", "ar": "موافق"}),
+ ("gui.common.yes", "Ja", {"fr": "Oui", "ar": "نعم"}),
+ ("gui.common.no", "Nein", {"fr": "Non", "ar": "لا"}),
+ ("gui.dialog.new_project", "Neues Projekt", {"fr": "Nouveau projet"}),
+ ("gui.dialog.project_name", "Projektname:", {"fr": "Nom du projet :"}),
+ ("gui.dialog.error", "Fehler", {"fr": "Erreur"}),
+ ("gui.dialog.failed", "Der Vorgang ist fehlgeschlagen.", {"fr": "L’opération a échoué."}),
+ ("gui.workflow.analysis", "Analyse", {"en": "Analysis", "fr": "Analyse"}),
+ ("gui.workflow.summary", "Zusammenfassung", {"en": "Summary", "fr": "Résumé"}),
+ ("gui.workflow.research", "Forschungsbericht", {"en": "Research report", "fr": "Rapport de recherche"}),
+ ("gui.workflow.comparison", "Vergleich", {"en": "Comparison", "fr": "Comparaison"}),
+ ("gui.files.documents", "Dokumente", {"fr": "Documents"}),
+ ("gui.files.report", "Bericht", {"en": "Report", "fr": "Rapport"}),
+ ("gui.files.text", "Text", {"en": "Text", "fr": "Texte"}),
+ ("gui.workflow.label", "Workflow", {"en": "Workflow", "fr": "Flux de travail"}),
+ ("gui.files.word", "Word", {"fr": "Document Word"}),
+ ("gui.status.project_created", "Projekt '{value1}' erstellt", {"fr": "Projet '{value1}' créé"}),
+ ("gui.chat.confidence", " (Konfidenz: {percent:.0%})", {"fr": " (Confiance : {percent:.0%})"}),
+ ("gui.status.ready", "Bereit", {"en": "Ready", "fr": "Prêt"}),
+ ("gui.status.finished", "Fertig", {"en": "Finished", "fr": "Terminé"}),
+ ]
+ records = []
+ for key, source, partial in entries:
+ translations = {"de": source, **partial}
+ records.append({"key": key, "canonical_source_value": source, "translations": translations})
+ locale_dir = app_dir / "locales"
+ locale_dir.mkdir(parents=True, exist_ok=True)
+ (locale_dir / "gui_translations.json").write_text(
+ json.dumps({"schema": "notespacellm.gui-i18n-runtime.v1", "entries": records}, ensure_ascii=False),
+ encoding="utf-8",
+ )
+ return TranslationSystem("de", app_dir)
+
+
+def test_worldset_language_codes_and_names_are_complete():
+ assert SUPPORTED_LANGUAGES == ["en", "de", "es", "zh", "ja", "ru", "fr", "hi", "ar", "bn", "pt"]
+ assert set(LANGUAGE_NAMES) == set(SUPPORTED_LANGUAGES)
+ assert len(set(LANGUAGE_NAMES.values())) == len(SUPPORTED_LANGUAGES)
+
+
+def test_widget_retranslation_preserves_user_text_and_code_values(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ root = QWidget()
+ layout = QVBoxLayout(root)
+ button = QPushButton("Öffnen")
+ path_field = QLineEdit()
+ path_field.setPlaceholderText("Eingabe:")
+ path_field.setText("C:/private/Report.txt")
+ provider_combo = QComboBox()
+ provider_combo.addItem("ollama", "ollama")
+ provider_combo.addItem("claude-code", "claude-code")
+ layout.addWidget(button)
+ layout.addWidget(path_field)
+ layout.addWidget(provider_combo)
+
+ translator.set_language("fr")
+ translate_widget_tree(root, translator)
+
+ assert button.text() == "Ouvrir"
+ assert path_field.placeholderText() == "Saisie :"
+ assert path_field.text() == "C:/private/Report.txt"
+ assert provider_combo.currentText() == "ollama"
+ assert provider_combo.currentData() == "ollama"
+
+
+def test_dynamic_localized_status_label_keeps_latest_source_across_language_changes(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ root = QWidget()
+ layout = QVBoxLayout(root)
+ status = LocalizedLabel("Bereit", translator, root)
+ layout.addWidget(status)
+
+ # Cache/replay a static tree pass, then move the runtime status forward.
+ translate_widget_tree(root, translator)
+ assert status.text() == "Bereit"
+ status.setText("Fertig")
+ assert status.text() == "Fertig"
+
+ for language, expected in (("fr", "Terminé"), ("en", "Finished"), ("de", "Fertig"), ("fr", "Terminé")):
+ translator.set_language(language)
+ translate_widget_tree(root, translator)
+ status.retranslate()
+ assert status.text() == expected
+ assert status._source_text == "Fertig"
+
+
+def test_expired_localized_statusbar_message_is_not_resurrected_on_language_change(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ window = QMainWindow()
+ statusbar = LocalizedStatusBar(translator, window)
+ window.setStatusBar(statusbar)
+ window.show()
+ qapp.processEvents()
+
+ statusbar.showMessage("Fertig", 30)
+ assert statusbar.currentMessage() == "Fertig"
+ QTest.qWait(90)
+ assert statusbar.currentMessage() == ""
+ assert statusbar._source_message == ""
+
+ translator.set_language("fr")
+ statusbar.retranslate()
+ assert statusbar.currentMessage() == ""
+
+ # A message that has not expired remains replayable in the current locale.
+ statusbar.showMessage("Fertig", 0)
+ assert statusbar.currentMessage() == "Terminé"
+ translator.set_language("en")
+ statusbar.retranslate()
+ assert statusbar.currentMessage() == "Finished"
+ window.close()
+
+
+def test_live_language_change_preserves_data_that_collides_with_ui_sources(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ settings = QSettings(str(tmp_path / "collisions.ini"), QSettings.Format.IniFormat)
+ window = MainWindow(
+ settings=settings,
+ translator=translator,
+ project_manager=object(),
+ start_background_tasks=False,
+ )
+ try:
+ # User, project, document, and workflow values intentionally equal
+ # strings that are also translated UI sources. These remain unmarked.
+ window.chat_panel._add_message(
+ ChatMessage(role="user", content="Bericht", timestamp=datetime.now())
+ )
+ user_text = window.chat_panel._message_widgets[-1].content_label
+ project_name = QLabel("Text")
+ window.document_panel.layout().insertWidget(0, project_name)
+ document_item = QTreeWidgetItem(window.document_panel.tree, ["Sprache:", "", ""])
+ workflow_name = window.workflow_panel._step_widgets[0].name_label
+ workflow_name.setText("Workflow")
+
+ expected = {
+ "user": user_text.text().encode("utf-8"),
+ "project": project_name.text().encode("utf-8"),
+ "document": document_item.text(0).encode("utf-8"),
+ "workflow": workflow_name.text().encode("utf-8"),
+ }
+ window._language_combo.setCurrentIndex(window._language_combo.findData("fr"))
+ qapp.processEvents()
+
+ assert translator.get_language() == "fr"
+ assert window._language_label.text() == "Langue :"
+ assert user_text.text().encode("utf-8") == expected["user"]
+ assert project_name.text().encode("utf-8") == expected["project"]
+ assert document_item.text(0).encode("utf-8") == expected["document"]
+ assert workflow_name.text().encode("utf-8") == expected["workflow"]
+ finally:
+ window.deleteLater()
+ qapp.processEvents()
+
+
+def test_ui_templates_keep_opaque_project_names_and_file_patterns(tmp_path):
+ translator = write_test_catalog(tmp_path)
+ translator.set_language("fr")
+ assert translate_ui_text("Projekt 'Projekt Lumière' erstellt", translator) == "Projet 'Projekt Lumière' créé"
+ assert translate_ui_text(" (Konfidenz: 83%)", translator) == " (Confiance : 83%)"
+ assert translate_ui_text("Word (.docx)", translator) == "Document Word (.docx)"
+ assert _translated_filters("Dokumente (*.pdf *.docx)", translator) == ["Documents (*.pdf *.docx)"]
+
+
+def test_dialog_standard_buttons_are_translated(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ translator.set_language("fr")
+ dialog = QDialog()
+ buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, dialog)
+ layout = QVBoxLayout(dialog)
+ layout.addWidget(buttons)
+ translate_widget_tree(dialog, translator)
+ assert buttons.button(QDialogButtonBox.StandardButton.Cancel).text() == "Annuler"
+ assert buttons.button(QDialogButtonBox.StandardButton.Ok).text() == "OK"
+
+
+def test_static_input_dialog_translates_chrome_and_returns_entered_text(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ translator.set_language("fr")
+ parent = QWidget()
+ parent._translator = translator
+ observed = {}
+
+ def complete_dialog():
+ dialog = next(widget for widget in QApplication.topLevelWidgets() if isinstance(widget, QInputDialog))
+ observed["title"] = dialog.windowTitle()
+ observed["label"] = dialog.labelText()
+ cancel = dialog.findChild(QDialogButtonBox).button(QDialogButtonBox.StandardButton.Cancel)
+ observed["cancel"] = cancel.text()
+ dialog.setTextValue("Projet expérimental")
+ dialog.accept()
+
+ QTimer.singleShot(20, complete_dialog)
+ value, accepted = LocalizedInputDialog.getText(parent, "Neues Projekt", "Projektname:")
+ assert accepted is True
+ assert value == "Projet expérimental"
+ assert observed == {"title": "Nouveau projet", "label": "Nom du projet :", "cancel": "Annuler"}
+
+
+def test_static_message_box_translates_message_and_button(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ translator.set_language("fr")
+ parent = QWidget()
+ parent._translator = translator
+ observed = {}
+
+ def complete_dialog():
+ dialog = next(widget for widget in QApplication.topLevelWidgets() if isinstance(widget, QMessageBox))
+ observed["title"] = dialog.windowTitle()
+ observed["text"] = dialog.text()
+ observed["ok"] = dialog.button(QMessageBox.StandardButton.Ok).text()
+ dialog.accept()
+
+ QTimer.singleShot(20, complete_dialog)
+ LocalizedMessageBox.warning(parent, "Fehler", "Der Vorgang ist fehlgeschlagen.")
+ assert observed == {"title": "Erreur", "text": "L’opération a échoué.", "ok": "OK"}
+
+
+def test_question_box_localizes_buttons_and_returns_the_selected_standard_button(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ translator.set_language("fr")
+ parent = QWidget()
+ parent._translator = translator
+ observed = {}
+
+ def choose_yes():
+ dialog = next(widget for widget in QApplication.topLevelWidgets() if isinstance(widget, QMessageBox))
+ observed["yes"] = dialog.button(QMessageBox.StandardButton.Yes).text()
+ observed["no"] = dialog.button(QMessageBox.StandardButton.No).text()
+ dialog.button(QMessageBox.StandardButton.Yes).click()
+
+ QTimer.singleShot(20, choose_yes)
+ result = LocalizedMessageBox.question(
+ parent,
+ "Question",
+ "Continue?",
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
+ QMessageBox.StandardButton.No,
+ )
+ assert result == QMessageBox.StandardButton.Yes
+ assert observed == {"yes": "Oui", "no": "Non"}
+
+
+def test_settings_persistence_and_invalid_code_fallback(tmp_path):
+ translator = write_test_catalog(tmp_path)
+ settings = QSettings(str(tmp_path / "settings.ini"), QSettings.Format.IniFormat)
+ assert load_ui_language(settings) == "de"
+ assert persist_ui_language(settings, translator, "ar") is True
+ assert translator.get_language() == "ar"
+ assert load_ui_language(settings) == "ar"
+ settings.setValue(UI_LANGUAGE_KEY, "xx")
+ settings.sync()
+ assert load_ui_language(settings) == "de"
+ assert persist_ui_language(settings, translator, "xx") is False
+ assert translator.get_language() == "ar"
+
+
+def test_main_window_language_selector_changes_locale_persists_and_sets_rtl(tmp_path, qapp):
+ translator = write_test_catalog(tmp_path)
+ settings = QSettings(str(tmp_path / "ui.ini"), QSettings.Format.IniFormat)
+ window = MainWindow(
+ settings=settings,
+ translator=translator,
+ project_manager=object(),
+ start_background_tasks=False,
+ )
+ try:
+ assert window._language_combo.count() == 11
+ assert window._language_combo.currentData() == "de"
+ assert window._language_label.text() == "Sprache:"
+
+ french_index = window._language_combo.findData("fr")
+ window._language_combo.setCurrentIndex(french_index)
+ qapp.processEvents()
+ assert translator.get_language() == "fr"
+ assert settings.value(UI_LANGUAGE_KEY) == "fr"
+ assert window._language_label.text() == "Langue :"
+ assert window.workflow_panel.report_type_combo.currentData() == "analysis"
+ assert window.workflow_panel.report_type_combo.currentText() == "Analyse"
+
+ arabic_index = window._language_combo.findData("ar")
+ window._language_combo.setCurrentIndex(arabic_index)
+ qapp.processEvents()
+ assert translator.get_language() == "ar"
+ assert window.layoutDirection() == Qt.LayoutDirection.RightToLeft
+ assert settings.value(UI_LANGUAGE_KEY) == "ar"
+ assert window.workflow_panel.report_type_combo.currentData() == "analysis"
+ finally:
+ window.deleteLater()
+ qapp.processEvents()
+
+
+def test_main_window_loads_saved_locale_without_project_language(tmp_path, qapp):
+ settings = QSettings(str(tmp_path / "saved-locale.ini"), QSettings.Format.IniFormat)
+ settings.setValue(UI_LANGUAGE_KEY, "ar")
+ settings.sync()
+ window = MainWindow(
+ settings=settings,
+ project_manager=object(),
+ start_background_tasks=False,
+ )
+ try:
+ assert window._translator.get_language() == "ar"
+ assert window._language_combo.currentData() == "ar"
+ assert window.layoutDirection() == Qt.LayoutDirection.RightToLeft
+ assert window._language_label.text() == "اللغة:"
+ finally:
+ window.deleteLater()
+ qapp.processEvents()
diff --git a/tests/test_packaging.py b/tests/test_packaging.py
index e526417..43824c9 100644
--- a/tests/test_packaging.py
+++ b/tests/test_packaging.py
@@ -201,6 +201,10 @@ def test_sdist_wheel_and_installed_src_runtime_contract(tmp_path: Path):
assert "src/__init__.py" in wheel_names
assert "src/rag/engine.py" in wheel_names
assert "src/core/workspace_exporter.py" in wheel_names
+ assert "translator.py" in wheel_names
+ assert "locales/__init__.py" in wheel_names
+ assert "locales/gui_translations.json" in wheel_names
+ assert "locales/translations.json" in wheel_names
assert any(name.endswith(".dist-info/licenses/LICENSE") for name in wheel_names)
metadata_path = next(name for name in wheel_names if name.endswith(".dist-info/METADATA"))
metadata = archive.read(metadata_path).decode("utf-8")
@@ -225,6 +229,9 @@ def test_sdist_wheel_and_installed_src_runtime_contract(tmp_path: Path):
"setup_environment.py",
"MANIFEST.in",
"web_companion/index.html",
+ "translator.py",
+ "locales/__init__.py",
+ "locales/gui_translations.json",
):
assert expected in sdist_names, f"sdist omitted {expected}"
assert not any("synthetic-local-state-sentinel" in name for name in sdist_names)
@@ -262,6 +269,13 @@ def test_sdist_wheel_and_installed_src_runtime_contract(tmp_path: Path):
(
"import src; import src.core.workspace_exporter; "
"import src.rag.engine; import src.rag.embeddings; "
+ "from src.gui.i18n import create_translator; "
+ "from PySide6.QtCore import QSettings; "
+ "from translator import SUPPORTED_LANGUAGES; "
+ "from pathlib import Path; import locales; "
+ "translator=create_translator(QSettings('BACH','NoteSpaceLLM'), Path(locales.__file__).parent.parent); "
+ "assert len(SUPPORTED_LANGUAGES)==11; "
+ "assert translator.has_translation_source('Sprache:'); "
"print(src.__file__)"
),
],
diff --git a/tests/test_translator.py b/tests/test_translator.py
index 70aca41..fc7f6ec 100644
--- a/tests/test_translator.py
+++ b/tests/test_translator.py
@@ -3,7 +3,7 @@
import unittest
from pathlib import Path
-from translator import TranslationSystem
+from translator import SUPPORTED_LANGUAGES, TranslationSystem
class TranslationSystemTests(unittest.TestCase):
@@ -29,9 +29,9 @@ def test_scan_and_update_only_picks_up_german_strings(self):
result = tr.scan_and_update(project_dir)
self.assertEqual(result["added"], ["Datei öffnen"])
- # missing is a per-language dict since i18n expansion (6 languages)
+ # Missing entries are reported for every supported target locale.
self.assertIsInstance(result["missing"], dict)
- for lang in ("en", "es", "zh", "ja", "ru"):
+ for lang in (code for code in SUPPORTED_LANGUAGES if code != "de"):
self.assertIn("Datei öffnen", result["missing"].get(lang, []))
self.assertEqual(result["total"], 1)
@@ -40,9 +40,40 @@ def test_scan_and_update_only_picks_up_german_strings(self):
self.assertIn("Datei öffnen", data)
entry = data["Datei öffnen"]
self.assertEqual(entry["de"], "Datei öffnen")
- for lang in ("en", "es", "zh", "ja", "ru"):
+ for lang in (code for code in SUPPORTED_LANGUAGES if code != "de"):
self.assertIn(lang, entry)
+ def test_gui_catalog_lookup_is_stable_keyed_and_read_only(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ app_dir = Path(tmp)
+ locale_dir = app_dir / "locales"
+ locale_dir.mkdir()
+ source = "{count} Dokumente geladen"
+ catalog = {
+ "schema": "notespacellm.gui-i18n-runtime.v1",
+ "entries": [{
+ "key": "gui.status.documents_loaded",
+ "canonical_source_value": source,
+ "translations": {
+ "de": source,
+ "en": "Loaded {count} documents",
+ "fr": "{count} documents chargés",
+ },
+ }],
+ }
+ (locale_dir / "gui_translations.json").write_text(
+ json.dumps(catalog, ensure_ascii=False), encoding="utf-8"
+ )
+ before = (locale_dir / "gui_translations.json").read_bytes()
+
+ tr = TranslationSystem("fr", app_dir)
+ self.assertEqual(tr.t("gui.status.documents_loaded"), "{count} documents chargés")
+ self.assertEqual(tr.t(source), "{count} documents chargés")
+ self.assertFalse(tr.has_translation_source("Nicht im Katalog"))
+
+ self.assertEqual((locale_dir / "gui_translations.json").read_bytes(), before)
+ self.assertFalse((locale_dir / "translations.json").exists())
+
if __name__ == "__main__":
unittest.main()
diff --git a/translator.py b/translator.py
index 3c23efe..2641950 100644
--- a/translator.py
+++ b/translator.py
@@ -1,7 +1,7 @@
"""
TranslationSystem - Multi-Language Support für Anwendungen
============================================================
-Version: 2.0.0 (6-Sprachen-Ausbau)
+Version: 2.0.0 (11-Sprachen-Ausbau)
Quelle: ARC_EntwicklungsschleifeAdvanced/TranslationSystem.py v2.4
Referenz: _LANG/LANGUAGE_CODES.md
@@ -19,9 +19,22 @@
from pathlib import Path
from typing import Dict, List, Set
-SUPPORTED_LANGUAGES = ['de', 'en', 'es', 'zh', 'ja', 'ru']
+SUPPORTED_LANGUAGES = ['en', 'de', 'es', 'zh', 'ja', 'ru', 'fr', 'hi', 'ar', 'bn', 'pt']
DEFAULT_LANGUAGE = 'de'
FALLBACK_CHAIN = ['en', 'de']
+LANGUAGE_NAMES = {
+ 'en': 'English',
+ 'de': 'Deutsch',
+ 'es': 'Español',
+ 'zh': '简体中文',
+ 'ja': '日本語',
+ 'ru': 'Русский',
+ 'fr': 'Français',
+ 'hi': 'हिन्दी',
+ 'ar': 'العربية',
+ 'bn': 'বাংলা',
+ 'pt': 'Português',
+}
class TranslationSystem:
@@ -42,6 +55,9 @@ def __init__(self, default_lang: str = 'de', app_dir: Path = None):
self.app_dir = Path(app_dir)
self.translations_file = self.app_dir / "locales" / "translations.json"
+ self.ui_translations_file = self.app_dir / "locales" / "gui_translations.json"
+ self.ui_translations = {}
+ self.ui_keys = {}
self.string_patterns = [
re.compile(r'setText\s*\(\s*["\']([^"\']+)["\']\s*\)'),
@@ -74,47 +90,101 @@ def _load_translations(self):
if self.translations_file.exists():
try:
with open(self.translations_file, 'r', encoding='utf-8') as f:
- self.translations = json.load(f)
+ loaded = json.load(f)
+ self.translations = loaded if isinstance(loaded, dict) else {}
except Exception:
self.translations = {}
else:
self.translations = {}
+ self._load_ui_translations()
+
+ def _load_ui_translations(self):
+ """Load the stable-key GUI catalog and index it by its German source text."""
+ self.ui_translations = {}
+ self.ui_keys = {}
+ if not self.ui_translations_file.exists():
+ return
+ try:
+ with open(self.ui_translations_file, 'r', encoding='utf-8') as f:
+ payload = json.load(f)
+ except (OSError, json.JSONDecodeError):
+ return
+
+ records = payload.get('entries', []) if isinstance(payload, dict) else []
+ if not isinstance(records, list):
+ return
+ for record in records:
+ if not isinstance(record, dict):
+ continue
+ stable_key = record.get('key')
+ source = record.get('canonical_source_value')
+ translations = record.get('translations', {})
+ if not isinstance(stable_key, str) or not isinstance(source, str):
+ continue
+ if not isinstance(translations, dict):
+ translations = {}
+ self.ui_keys[stable_key] = source
+ self.ui_translations[source] = translations
def _save_translations(self):
self.translations_file.parent.mkdir(parents=True, exist_ok=True)
with open(self.translations_file, 'w', encoding='utf-8') as f:
json.dump(self.translations, f, indent=2, ensure_ascii=False)
+ def _resolve_entry(self, entry, fallback: str) -> str:
+ if not isinstance(entry, dict):
+ return fallback
+ value = entry.get(self.current_lang)
+ if isinstance(value, str) and value:
+ return value
+ for language in FALLBACK_CHAIN:
+ value = entry.get(language)
+ if isinstance(value, str) and value:
+ return value
+ return fallback
+
+ @staticmethod
+ def _translation_for(entry, language: str) -> str:
+ if isinstance(entry, dict):
+ value = entry.get(language)
+ return value if isinstance(value, str) else ""
+ return ""
+
def t(self, key: str) -> str:
"""
- Übersetzt einen Key in die aktuelle Sprache.
- Fallback-Kette: aktuelle Sprache -> en -> de -> Key selbst.
+ Übersetzt einen Quelltext oder stabilen GUI-Schlüssel.
+
+ Fallback-Kette: aktuelle Sprache -> en -> de -> Quelltext.
+ Eine fehlende Übersetzung wird nicht automatisch in den Quellkatalog
+ geschrieben; der Translator bleibt beim bloßen Anzeigen schreibfrei.
"""
- if key in self.translations:
- entry = self.translations[key]
- value = entry.get(self.current_lang)
- if value:
- return value
- for fb in FALLBACK_CHAIN:
- value = entry.get(fb)
+ if key in self.ui_keys:
+ source = self.ui_keys[key]
+ return self._resolve_entry(self.ui_translations.get(source), source)
+ if key in self.ui_translations or key in self.translations:
+ gui_entry = self.ui_translations.get(key)
+ legacy_entry = self.translations.get(key)
+ for entry in (gui_entry, legacy_entry):
+ value = self._translation_for(entry, self.current_lang)
if value:
return value
+ for language in FALLBACK_CHAIN:
+ for entry in (gui_entry, legacy_entry):
+ value = self._translation_for(entry, language)
+ if value:
+ return value
return key
+ return key
- if self._is_german(key):
- entry = {"de": key}
- for lang in SUPPORTED_LANGUAGES:
- if lang != 'de':
- entry[lang] = ""
- self.translations[key] = entry
- try:
- self._save_translations()
- except OSError:
- # z.B. schreibgeschütztes Installationsverzeichnis -- Übersetzen
- # darf deshalb nie fehlschlagen
- pass
+ def has_translation_source(self, source: str) -> bool:
+ """Return whether a string is present in either read-only GUI catalog."""
+ return source in self.ui_translations or source in self.translations
- return key
+ def ui(self, stable_key: str, source: str) -> str:
+ """Translate a GUI string by its stable key, falling back to source text."""
+ if stable_key in self.ui_keys:
+ return self.t(stable_key)
+ return self.t(source)
def set_language(self, lang: str):
if lang in SUPPORTED_LANGUAGES:
diff --git a/web_companion/PWA_TESTPLAN.md b/web_companion/PWA_TESTPLAN.md
index 59f8743..4a79447 100644
--- a/web_companion/PWA_TESTPLAN.md
+++ b/web_companion/PWA_TESTPLAN.md
@@ -41,6 +41,9 @@ URL: `http://127.0.0.1:8765`
| I3 | iOS | Workspace importieren | Bericht und Dokumentauszüge werden lokal angezeigt |
| I4 | iOS | Workspace importieren, Safari/PWA schließen, offline erneut öffnen | PWA-Shell startet; Workspace wird nicht wiederhergestellt und muss erneut importiert werden |
| I5 | iOS | Review-Notizen exportieren | Markdown-Export wird gestartet; falls iOS Download begrenzt, Share-/Datei-Dialog prüfen |
+| L1 | Browser | Sprachwahl öffnen und alle Einträge prüfen | Deutsch, English, Español, 中文, 日本語, Русский, Français, हिन्दी, العربية, বাংলা und Português stehen zur Auswahl |
+| L2 | Browser | Sprache wechseln, neu laden und erneut öffnen | UI-Sprache bleibt gespeichert; importierte Workspace-Daten bleiben nicht erhalten |
+| L3 | Browser | Arabisch auswählen | ``; UI wird rechts nach links dargestellt |
## Checkliste pro Lauf
@@ -51,6 +54,8 @@ URL: `http://127.0.0.1:8765`
- [ ] Cache-Hinweis zeigt, dass keine Workspace-Payload dauerhaft gespeichert ist
- [ ] Review-Notizen bleiben getrennt vom Workspace lokal erhalten
- [ ] Ein erneuter Start verlangt den Dateiimport; lokale UI-Sprache bleibt erhalten
+- [ ] Sprachwahl enthält elf Oberflächensprachen; `zh` wird als `zh-Hans` geführt
+- [ ] Arabisch setzt RTL, alle anderen UI-Sprachen bleiben LTR
## Bekannte Grenzen
diff --git a/web_companion/README.md b/web_companion/README.md
index 3da4750..d63c2bb 100644
--- a/web_companion/README.md
+++ b/web_companion/README.md
@@ -12,8 +12,9 @@ Review-Notizen.
- Bericht, Dokumentmetadaten und ausgewählte Auszüge anzeigen
- importierte Workspace-Inhalte nur in der aktuellen Sitzung anzeigen; nach einem Neustart erneut importieren
- eigene Review-Notizen lokal speichern und als Markdown exportieren
-- DE/EN/ES/ZH-Hans/JA/RU-Oberfläche mit Browser-/Workspace-Locale und
- manuellem Sprachumschalter
+- DE/EN/ES/ZH-Hans/JA/RU/FR/HI/AR/BN/PT-Oberfläche mit Browser-/Workspace-
+ Locale, manuellem Sprachumschalter und dauerhafter Speicherung der Auswahl
+- Arabische Oberfläche mit RTL-Layout
## Lokaler Start
diff --git a/web_companion/app.css b/web_companion/app.css
index 345e902..cca8751 100644
--- a/web_companion/app.css
+++ b/web_companion/app.css
@@ -68,9 +68,8 @@ body {
margin: 0 auto;
max-width: 1180px;
padding-top: calc(32px + env(safe-area-inset-top, 0px));
- padding-right: 18px;
+ padding-inline: 18px;
padding-bottom: calc(48px + env(safe-area-inset-bottom, 0px));
- padding-left: 18px;
position: relative;
z-index: 1;
}
@@ -106,6 +105,11 @@ body {
text-transform: uppercase;
}
+html[dir="rtl"] .eyebrow {
+ letter-spacing: normal;
+ text-transform: none;
+}
+
h1,
h2,
h3 {
@@ -222,11 +226,11 @@ h3 {
display: grid;
gap: 10px;
margin: 0 0 16px;
- padding-left: 20px;
+ padding-inline-start: 20px;
}
.hint-list li {
- padding-left: 4px;
+ padding-inline-start: 4px;
}
input[type="file"] {
@@ -363,8 +367,11 @@ button:active,
.excerpt {
background: rgba(219, 234, 254, 0.45);
- border-left: 4px solid rgba(12, 74, 110, 0.34);
- border-radius: 0 12px 12px 0;
+ border-inline-start: 4px solid rgba(12, 74, 110, 0.34);
+ border-start-start-radius: 0;
+ border-end-start-radius: 0;
+ border-start-end-radius: 12px;
+ border-end-end-radius: 12px;
padding: 12px 14px;
}
diff --git a/web_companion/app.js b/web_companion/app.js
index ecb9bab..2685360 100644
--- a/web_companion/app.js
+++ b/web_companion/app.js
@@ -43,6 +43,7 @@ const elements = {
let currentWorkspace = null;
let currentSourceLabel = "";
+let currentSourceKind = "";
let cachedWorkspaceTitle = "";
const storedLocale = globalThis.localStorage?.getItem(UI_LOCALE_KEY) || "";
let hasUserLocaleOverride = Boolean(storedLocale);
@@ -57,6 +58,7 @@ function setStatus(message, isError = false) {
function applyStaticTranslations() {
document.documentElement.lang = activeLocale;
+ document.documentElement.dir = activeLocale === "ar" ? "rtl" : "ltr";
document.querySelectorAll("[data-i18n]").forEach((node) => {
const key = node.dataset.i18n;
@@ -65,6 +67,13 @@ function applyStaticTranslations() {
}
});
+ document.querySelectorAll("[data-i18n-aria-label]").forEach((node) => {
+ const key = node.dataset.i18nAriaLabel;
+ if (key && typeof currentText[key] === "string") {
+ node.setAttribute("aria-label", currentText[key]);
+ }
+ });
+
document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
const key = node.dataset.i18nPlaceholder;
if (key && typeof currentText[key] === "string") {
@@ -224,7 +233,12 @@ function rerenderCurrentWorkspace() {
renderSummary(currentWorkspace);
renderReport(currentWorkspace);
renderDocuments(currentWorkspace);
- setStatus(currentText.workspaceLoaded(currentSourceLabel, currentWorkspace.workspace.title));
+ const sourceLabel = currentSourceKind === "demo"
+ ? currentText.sourceLabelDemo
+ : currentSourceKind === "cached"
+ ? currentText.sourceLabelCached
+ : currentSourceLabel;
+ setStatus(currentText.workspaceLoaded(sourceLabel, currentWorkspace.workspace.title));
}
function setLocale(locale, { persist = false } = {}) {
@@ -242,7 +256,7 @@ function setLocale(locale, { persist = false } = {}) {
}
function renderWorkspace(payload, sourceLabel, options = {}) {
- const { persist = true } = options;
+ const { persist = true, sourceKind = "" } = options;
if (!hasUserLocaleOverride) {
activeLocale = resolveUiLocale(payload.workspace.locale, globalThis.navigator?.language, "de");
currentText = getUiText(activeLocale);
@@ -251,6 +265,7 @@ function renderWorkspace(payload, sourceLabel, options = {}) {
currentWorkspace = payload;
currentSourceLabel = sourceLabel;
+ currentSourceKind = sourceKind;
renderSummary(payload);
renderReport(payload);
renderDocuments(payload);
@@ -287,7 +302,7 @@ elements.fileInput.addEventListener("change", (event) => {
});
elements.loadDemo.addEventListener("click", () => {
- renderWorkspace(getDemoWorkspace(activeLocale), currentText.sourceLabelDemo);
+ renderWorkspace(getDemoWorkspace(activeLocale), currentText.sourceLabelDemo, { sourceKind: "demo" });
});
elements.exportNotes.addEventListener("click", () => {
@@ -327,11 +342,11 @@ const params = new URLSearchParams(window.location.search);
setLocale(activeLocale);
if (params.get("demo") === "1") {
- renderWorkspace(getDemoWorkspace(activeLocale), currentText.sourceLabelDemo);
+ renderWorkspace(getDemoWorkspace(activeLocale), currentText.sourceLabelDemo, { sourceKind: "demo" });
} else {
const cachedWorkspace = loadWorkspaceCache();
if (cachedWorkspace) {
- renderWorkspace(cachedWorkspace, currentText.sourceLabelCached, { persist: false });
+ renderWorkspace(cachedWorkspace, currentText.sourceLabelCached, { persist: false, sourceKind: "cached" });
}
}
diff --git a/web_companion/index.html b/web_companion/index.html
index 9ba8e41..a665f76 100644
--- a/web_companion/index.html
+++ b/web_companion/index.html
@@ -43,13 +43,18 @@ Lokaler Review-Reader für exportierte Workspaces
diff --git a/web_companion/library.js b/web_companion/library.js
index 19c74b2..ca79cee 100644
--- a/web_companion/library.js
+++ b/web_companion/library.js
@@ -1,10 +1,12 @@
+import { ADDITIONAL_UI_TEXT } from "./ui-locales.js";
+
const DEFAULT_APP = Object.freeze({
name: "NoteSpaceLLM",
version: "unbekannt",
exported_at: ""
});
-const SUPPORTED_UI_LOCALES = Object.freeze(["de", "en", "es", "zh-Hans", "ja", "ru"]);
+const SUPPORTED_UI_LOCALES = Object.freeze(["de", "en", "es", "zh-Hans", "ja", "ru", "fr", "hi", "ar", "bn", "pt"]);
const UI_TEXT = Object.freeze({
de: Object.freeze({
@@ -561,6 +563,33 @@ const UI_TEXT = Object.freeze({
})
});
+function renderUiTemplate(template, values) {
+ return template.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (placeholder, name) =>
+ Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : placeholder
+ );
+}
+
+function bindAdditionalUiText(values) {
+ return Object.freeze({
+ ...values,
+ cacheHintWithTitle: (title) => renderUiTemplate(values.cacheHintWithTitle, { title }),
+ selectedDocuments: (selected, total) => renderUiTemplate(values.selectedDocuments, { selected, total }),
+ excerptCount: (count) => renderUiTemplate(values.excerptCount, { count }),
+ documentCount: (count) => renderUiTemplate(values.documentCount, { count }),
+ workspaceLoaded: (sourceLabel, title) => renderUiTemplate(values.workspaceLoaded, { sourceLabel, title }),
+ notesExported: (filename) => renderUiTemplate(values.notesExported, { filename }),
+ workspaceLoadErrorSchema: (schemaVersion) => renderUiTemplate(values.workspaceLoadErrorSchema, { schemaVersion }),
+ untitledDocument: (index) => renderUiTemplate(values.untitledDocument, { index: index + 1 }),
+ reviewHeadingWithTitle: (title) => renderUiTemplate(values.reviewHeadingWithTitle, { title })
+ });
+}
+
+const ADDITIONAL_UI_TEXT_BOUND = Object.freeze(
+ Object.fromEntries(
+ Object.entries(ADDITIONAL_UI_TEXT).map(([locale, values]) => [locale, bindAdditionalUiText(values)])
+ )
+);
+
const DEFAULT_WORKSPACE = Object.freeze({
id: "",
title: "Unbenannter Workspace",
@@ -604,6 +633,21 @@ function normalizeUiLocaleCandidate(value) {
if (candidate.startsWith("ru")) {
return "ru";
}
+ if (candidate.startsWith("fr")) {
+ return "fr";
+ }
+ if (candidate.startsWith("hi")) {
+ return "hi";
+ }
+ if (candidate.startsWith("ar")) {
+ return "ar";
+ }
+ if (candidate.startsWith("bn")) {
+ return "bn";
+ }
+ if (candidate.startsWith("pt")) {
+ return "pt";
+ }
return "";
}
@@ -622,7 +666,8 @@ export function getSupportedUiLocales() {
}
export function getUiText(locale = "de") {
- return UI_TEXT[resolveUiLocale(locale)];
+ const resolvedLocale = resolveUiLocale(locale);
+ return ADDITIONAL_UI_TEXT_BOUND[resolvedLocale] || UI_TEXT[resolvedLocale];
}
function toDocumentId(index) {
@@ -873,6 +918,81 @@ export function getDemoWorkspace(locale = "de") {
reportTitle: "Синтетический отчет",
reportContent: "# Синтез\n\n- Ключевой тезис A подтверждается двумя источниками.\n- Открытый пробел: сравнительные данные за 2024 год все еще отсутствуют.",
assistantMessage: "Суммируй доказательную базу по главному тезису."
+ },
+ fr: {
+ title: "Démo : notes de recherche",
+ question: "Quelles affirmations centrales étayent le plus solidement le rapport ?",
+ firstName: "report-draft.md",
+ firstPath: "reports/report-draft.md",
+ firstExcerpt: "Les éléments les plus probants proviennent de la combinaison de la source primaire et d’une vérification croisée explicite.",
+ firstSource: "Section 2",
+ secondName: "literature.pdf",
+ secondPath: "sources/literature.pdf",
+ secondExcerpt: "Plusieurs études pointent le même obstacle méthodologique.",
+ secondSource: "Page 14",
+ reportTitle: "Rapport de synthèse",
+ reportContent: "# Synthèse\n\n- L’affirmation centrale A est étayée par deux sources.\n- Lacune restante : les données comparatives pour 2024 manquent encore.",
+ assistantMessage: "Résumez les éléments qui étayent l’affirmation principale."
+ },
+ hi: {
+ title: "डेमो: शोध नोट्स",
+ question: "कौन से मुख्य दावे रिपोर्ट को सबसे मज़बूती से आधार देते हैं?",
+ firstName: "report-draft.md",
+ firstPath: "reports/report-draft.md",
+ firstExcerpt: "सबसे मज़बूत साक्ष्य प्राथमिक स्रोत को स्पष्ट क्रॉस-चेक के साथ जोड़ने से मिलता है।",
+ firstSource: "अनुभाग 2",
+ secondName: "literature.pdf",
+ secondPath: "sources/literature.pdf",
+ secondExcerpt: "कई अध्ययन उसी पद्धतिगत बाधा की ओर संकेत करते हैं।",
+ secondSource: "पृष्ठ 14",
+ reportTitle: "संश्लेषण रिपोर्ट",
+ reportContent: "# संश्लेषण\n\n- मुख्य दावा A को दो स्रोतों का समर्थन प्राप्त है।\n- शेष अंतर: 2024 के तुलनात्मक आँकड़े अब भी उपलब्ध नहीं हैं।",
+ assistantMessage: "मुख्य दावे के साक्ष्य-आधार का सारांश दें।"
+ },
+ ar: {
+ title: "عرض توضيحي: ملاحظات بحثية",
+ question: "ما الادعاءات المحورية التي تدعم التقرير بأقوى صورة؟",
+ firstName: "report-draft.md",
+ firstPath: "reports/report-draft.md",
+ firstExcerpt: "تأتي أقوى الأدلة من الجمع بين المصدر الأساسي والتحقق المتقاطع الصريح.",
+ firstSource: "القسم 2",
+ secondName: "literature.pdf",
+ secondPath: "sources/literature.pdf",
+ secondExcerpt: "تشير عدة دراسات إلى العائق المنهجي نفسه.",
+ secondSource: "الصفحة 14",
+ reportTitle: "تقرير تركيبي",
+ reportContent: "# خلاصة\n\n- يدعم مصدران الادعاء المحوري A.\n- فجوة قائمة: لا تزال البيانات المقارنة لعام 2024 غير متاحة.",
+ assistantMessage: "لخّص قاعدة الأدلة للادعاء الرئيسي."
+ },
+ bn: {
+ title: "ডেমো: গবেষণা নোট",
+ question: "কোন মূল দাবিগুলো প্রতিবেদনটিকে সবচেয়ে শক্তভাবে সমর্থন করে?",
+ firstName: "report-draft.md",
+ firstPath: "reports/report-draft.md",
+ firstExcerpt: "প্রাথমিক উৎসের সঙ্গে স্পষ্টভাবে যাচাই করা তুলনা মিলিয়ে দেখলে সবচেয়ে শক্তিশালী প্রমাণ পাওয়া যায়।",
+ firstSource: "বিভাগ ২",
+ secondName: "literature.pdf",
+ secondPath: "sources/literature.pdf",
+ secondExcerpt: "একাধিক গবেষণায় একই পদ্ধতিগত বাধার কথা উঠে এসেছে।",
+ secondSource: "পৃষ্ঠা ১৪",
+ reportTitle: "সংশ্লেষ প্রতিবেদন",
+ reportContent: "# সংশ্লেষ\n\n- দুটি উৎস মূল দাবি A-কে সমর্থন করে।\n- অনিষ্পন্ন ঘাটতি: ২০২৪ সালের তুলনামূলক তথ্য এখনও অনুপস্থিত।",
+ assistantMessage: "মূল দাবির প্রমাণভিত্তি সংক্ষেপে বলুন।"
+ },
+ pt: {
+ title: "Demonstração: notas de investigação",
+ question: "Que alegações centrais sustentam mais fortemente o relatório?",
+ firstName: "report-draft.md",
+ firstPath: "reports/report-draft.md",
+ firstExcerpt: "A evidência mais forte resulta da combinação da fonte primária com uma verificação cruzada explícita.",
+ firstSource: "Secção 2",
+ secondName: "literature.pdf",
+ secondPath: "sources/literature.pdf",
+ secondExcerpt: "Vários estudos apontam para o mesmo obstáculo metodológico.",
+ secondSource: "Página 14",
+ reportTitle: "Relatório de síntese",
+ reportContent: "# Síntese\n\n- A alegação central A é sustentada por duas fontes.\n- Lacuna em aberto: continuam a faltar dados comparativos de 2024.",
+ assistantMessage: "Resuma a base de evidências da alegação principal."
}
}[resolvedLocale];
diff --git a/web_companion/sw.js b/web_companion/sw.js
index 131f5a5..aacc90c 100644
--- a/web_companion/sw.js
+++ b/web_companion/sw.js
@@ -1,10 +1,11 @@
-const CACHE_NAME = "notespacellm-companion-v4";
+const CACHE_NAME = "notespacellm-companion-v5";
const ASSETS = [
"./",
"./index.html",
"./app.css",
"./app.js",
"./library.js",
+ "./ui-locales.js",
"./manifest.webmanifest",
"./icons/Icon-192.png",
"./icons/Icon-512.png",
diff --git a/web_companion/tests/library.test.mjs b/web_companion/tests/library.test.mjs
index a905336..df5225b 100644
--- a/web_companion/tests/library.test.mjs
+++ b/web_companion/tests/library.test.mjs
@@ -53,12 +53,14 @@ function createAppHarness(storage = new Map(), { userAgent = "", onLine = true }
];
const elements = new Map(selectors.map((selector) => [selector, makeElement()]));
elements.get("#workspace-summary").summaryCells = Array.from({ length: 5 }, makeElement);
+ elements.get("#language-select").dataset.i18nAriaLabel = "languageLabel";
const document = {
body: { append() {} },
- documentElement: { lang: "" },
+ documentElement: { dir: "", lang: "" },
createElement: makeElement,
querySelector: (selector) => elements.get(selector) || makeElement(),
- querySelectorAll: () => []
+ querySelectorAll: (selector) =>
+ selector === "[data-i18n-aria-label]" ? [elements.get("#language-select")] : []
};
const localStorage = {
getItem: (key) => (storage.has(key) ? storage.get(key) : null),
@@ -80,7 +82,7 @@ function createAppHarness(storage = new Map(), { userAgent = "", onLine = true }
window,
URLSearchParams
});
- return { elements, storage };
+ return { document, elements, storage };
}
test("normalizeWorkspacePayload accepts minimal valid payload", () => {
@@ -165,11 +167,15 @@ test("resolveUiLocale supports browser-style locale tags", () => {
assert.equal(resolveUiLocale("zh-Hans"), "zh-Hans");
assert.equal(resolveUiLocale("ja-JP"), "ja");
assert.equal(resolveUiLocale("ru-RU"), "ru");
- assert.equal(resolveUiLocale("fr-FR"), "de");
+ assert.equal(resolveUiLocale("fr-FR"), "fr");
+ assert.equal(resolveUiLocale("hi-IN"), "hi");
+ assert.equal(resolveUiLocale("ar-EG"), "ar");
+ assert.equal(resolveUiLocale("bn-BD"), "bn");
+ assert.equal(resolveUiLocale("pt-BR"), "pt");
});
test("getSupportedUiLocales exposes all web companion UI locales", () => {
- assert.deepEqual(getSupportedUiLocales(), ["de", "en", "es", "zh-Hans", "ja", "ru"]);
+ assert.deepEqual(getSupportedUiLocales(), ["de", "en", "es", "zh-Hans", "ja", "ru", "fr", "hi", "ar", "bn", "pt"]);
});
test("all UI locale tables expose the same keys", () => {
@@ -197,6 +203,78 @@ test("getUiText returns premium-language web companion copy", () => {
assert.equal(getUiText("ru").clearNotes, "Очистить заметки");
});
+test("the additional locale catalogs translate templates without altering their values", () => {
+ assert.equal(getUiText("fr").workspaceLoaded("Démo", "Essai"), "Démo chargé : Essai");
+ assert.equal(getUiText("hi").untitledDocument(0), "दस्तावेज़ 1");
+ assert.equal(getUiText("ar").selectedDocuments(2, 5), "تم تحديد 2 من 5");
+ assert.match(getUiText("bn").notesExported("workspace-notes.md"), /workspace-notes\.md/);
+ assert.equal(getUiText("pt").reviewHeadingWithTitle("Análise"), "# Notas de revisão: Análise");
+});
+
+test("each newly supported locale includes translated demo prose and preserves identifiers", () => {
+ const expected = {
+ ar: "تقرير تركيبي",
+ bn: "সংশ্লেষ প্রতিবেদন",
+ fr: "Rapport de synthèse",
+ hi: "संश्लेषण रिपोर्ट",
+ pt: "Relatório de síntese"
+ };
+
+ for (const [locale, reportTitle] of Object.entries(expected)) {
+ const demo = getDemoWorkspace(locale);
+ assert.equal(demo.schema_version, "notespacellm-workspace-v1");
+ assert.equal(demo.app.name, "NoteSpaceLLM");
+ assert.equal(demo.workspace.locale, locale);
+ assert.equal(demo.workspace.workflow_type, "research");
+ assert.equal(demo.report.title, reportTitle);
+ assert.equal(demo.report.format, "markdown");
+ assert.equal(demo.documents[0].name, "report-draft.md");
+ assert.equal(demo.documents[0].path_hint, "reports/report-draft.md");
+ assert.equal(demo.documents[0].id, "doc-1");
+ assert.equal(demo.documents[0].format, "markdown");
+ assert.equal(demo.chat.messages[0].role, "assistant");
+ assert.equal(demo.provider.name, "ollama");
+ assert.equal(demo.provider.mode, "local");
+ assert.notEqual(demo.report.content, getDemoWorkspace("en").report.content);
+ }
+});
+
+test("the PWA UI locale persists across reloads and switches RTL only for Arabic", () => {
+ const storage = new Map();
+ const firstLoad = createAppHarness(storage);
+ const languageSelect = firstLoad.elements.get("#language-select");
+
+ languageSelect.listeners.get("change")({ target: { value: "ar" } });
+ assert.equal(storage.get("notespacellm-companion:locale"), "ar");
+ assert.equal(firstLoad.document.documentElement.lang, "ar");
+ assert.equal(firstLoad.document.documentElement.dir, "rtl");
+ assert.equal(languageSelect.attributes["aria-label"], getUiText("ar").languageLabel);
+ assert.equal(firstLoad.elements.get("#status-message").textContent, getUiText("ar").statusNoWorkspace);
+
+ const afterReload = createAppHarness(storage);
+ assert.equal(afterReload.document.documentElement.lang, "ar");
+ assert.equal(afterReload.document.documentElement.dir, "rtl");
+ assert.equal(afterReload.elements.get("#language-select").value, "ar");
+
+ afterReload.elements.get("#language-select").listeners.get("change")({ target: { value: "pt" } });
+ assert.equal(storage.get("notespacellm-companion:locale"), "pt");
+ assert.equal(afterReload.document.documentElement.dir, "ltr");
+});
+
+test("locale switching retranslates app-owned demo labels but preserves workspace content", () => {
+ const app = createAppHarness(new Map());
+ app.elements.get("#load-demo").listeners.get("click")();
+ const originalReportTitle = app.elements.get("#report-title").textContent;
+
+ app.elements.get("#language-select").listeners.get("change")({ target: { value: "pt" } });
+
+ assert.equal(app.elements.get("#report-title").textContent, originalReportTitle);
+ assert.match(
+ app.elements.get("#status-message").textContent,
+ /^Espaço de trabalho de demonstração carregado:/
+ );
+});
+
test("getPlatformGuide returns Android-specific install guidance", () => {
const guide = getPlatformGuide("Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 Chrome/125.0 Mobile Safari/537.36");
@@ -229,14 +307,19 @@ test("getPlatformGuide can localize mobile hints to Japanese", () => {
assert.match(guide.offline_hint, /ワークスペース JSON は保存されない.*再読み込みのたびに再インポートが必要/);
});
-test("offline guidance explains workspace re-import in all six locales", () => {
+test("offline guidance explains workspace re-import in all eleven locales", () => {
const requiredMeaning = {
de: /Workspace-JSON wird nicht gespeichert.*nach jedem Neuladen erneut importiert/i,
en: /workspace JSON is not saved.*imported again after each reload/i,
es: /JSON del espacio de trabajo no se guarda.*debe importarse de nuevo después de cada recarga/i,
"zh-Hans": /工作区 JSON 不会保存.*每次重新加载后都必须重新导入/,
ja: /ワークスペース JSON は保存されない.*再読み込みのたびに再インポートが必要/,
- ru: /JSON рабочей области не сохраняется.*после каждой перезагрузки его нужно импортировать заново/i
+ ru: /JSON рабочей области не сохраняется.*после каждой перезагрузки его нужно импортировать заново/i,
+ fr: /JSON de l'espace de travail n'est pas enregistré.*réimporté après chaque rechargement/i,
+ hi: /वर्कस्पेस JSON सहेजा नहीं जाता.*हर रीलोड के बाद फिर से आयात करना होगा/,
+ ar: /JSON الخاص بمساحة العمل.*استيراده مرة أخرى بعد كل إعادة تحميل/,
+ bn: /ওয়ার্কস্পেস JSON সংরক্ষণ করা হয় না.*প্রতিটি রিলোডের পর আবার আমদানি করতে হবে/,
+ pt: /JSON do espaço de trabalho não é guardado.*reimportado após cada recarregamento/i
};
for (const [locale, pattern] of Object.entries(requiredMeaning)) {
diff --git a/web_companion/tests/pwa.test.mjs b/web_companion/tests/pwa.test.mjs
index 1e4eeab..c324a50 100644
--- a/web_companion/tests/pwa.test.mjs
+++ b/web_companion/tests/pwa.test.mjs
@@ -116,6 +116,10 @@ describe('sw.js', () => {
assert.ok(sw.includes('apple-touch-icon-180.png'), 'apple-touch-icon-180.png fehlt in sw.js ASSETS');
});
+ it('cached the additional locale catalog for offline startup', () => {
+ assert.ok(sw.includes('./ui-locales.js'), 'ui-locales.js fehlt in sw.js ASSETS');
+ });
+
// Bug #2: caches.match ohne ignoreSearch schlägt bei ?demo=1-URLs offline fehl
it('caches.match nutzt ignoreSearch:true', () => {
assert.ok(
@@ -140,8 +144,8 @@ describe('index.html — Integration', () => {
assert.ok(html.includes('app.js'), 'app.js-Script fehlt in index.html');
});
- it('bietet alle sechs UI-Sprachen im Sprachumschalter an', () => {
- for (const locale of ['de', 'en', 'es', 'zh-Hans', 'ja', 'ru']) {
+ it('bietet alle elf UI-Sprachen im Sprachumschalter an', () => {
+ for (const locale of ['de', 'en', 'es', 'zh-Hans', 'ja', 'ru', 'fr', 'hi', 'ar', 'bn', 'pt']) {
assert.match(
html,
new RegExp(`