Initial commit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dinlo
2026-05-31 18:45:40 +08:00
commit e1b2485156
11 changed files with 2026 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(python *)"
]
}
}
+106
View File
@@ -0,0 +1,106 @@
# Сводка изменений: Система локализации
## ✅ Выполнено
### 1. Создана структура локализации
```
lang/
├── README.md # Инструкция по добавлению языков
├── en.json # Английский язык
├── ru.json # Русский язык
└── kz.json # Казахский язык (пример)
```
### 2. Модифицирован бэкенд (main.py)
- ✅ Добавлена константа `LANG_DIR` для папки локализаций
- ✅ Функция `get_available_languages()` — сканирует папку lang
- ✅ Функция `load_language(lang_code)` — загружает JSON локализации
- ✅ API эндпоинт `GET /api/languages` — список доступных языков
- ✅ API эндпоинт `GET /api/language/{lang_code}` — получение переводов
- ✅ Поле `language` добавлено в `config.json` и `SettingsModel`
### 3. Модифицирован фронтенд (Vue 3)
- ✅ Реактивная система локализации с функцией `t(key, params)`
- ✅ Автоматическая загрузка языка при старте приложения
- ✅ Селектор языка в настройках
- ✅ Мгновенная смена языка без перезагрузки страницы
- ✅ Все текстовые строки заменены на вызовы `t()`
- ✅ Поддержка параметров в строках: `{id}`, `{words}`, `{error}`
### 4. Документация
-`lang/README.md` — инструкция по добавлению языков
-`LOCALIZATION.md` — техническая документация
- ✅ Обновлён основной `README.md`
## 🎯 Как это работает
### Добавление нового языка
1. Создайте файл `lang/код.json` (например, `de.json` для немецкого)
2. Скопируйте содержимое из `en.json` или `ru.json`
3. Переведите значения (не меняя ключи)
4. Перезапустите приложение
5. **Готово!** Язык автоматически появится в настройках
### Смена языка пользователем
1. Открыть настройки (⚙️)
2. Выбрать язык в выпадающем списке "🌍 Язык интерфейса"
3. Интерфейс мгновенно переключится
4. Сохранить настройки
## 📊 Статистика
- **Строк кода в main.py:** 1375 (было ~1246)
- **Файлов локализации:** 3 (en, ru, kz)
- **Локализованных строк:** ~50 ключей
- **API эндпоинтов:** +2 новых
## 🌍 Доступные языки
- 🇬🇧 **English** (`en.json`)
- 🇷🇺 **Русский** (`ru.json`)
- 🇰🇿 **Қазақша** (`kz.json`) — пример для демонстрации
## 🔧 Технические особенности
- **Динамическое обнаружение:** Новые языки обнаруживаются автоматически
- **Горячая замена:** Язык меняется без перезагрузки страницы
- **Параметризация:** Поддержка подстановки значений в строки
- **Fallback:** Если перевод не найден, показывается ключ
- **UTF-8:** Полная поддержка Unicode для всех языков
- **Реактивность:** Vue 3 автоматически обновляет интерфейс
## 🎨 Пример использования в коде
### Простая строка
```javascript
{{ t('app_title') }} // → "Readeck Importer"
```
### Строка с параметрами
```javascript
{{ t('content_stats', { words: wordCount }) }} // → "симв. · 42 слов"
t('success_bookmark_created', { id: '123' }) // → "Успешно! Закладка создана (ID: 123)"
```
## 📝 Структура файла локализации
```json
{
"lang_name": "Название языка на этом языке",
"lang_code": "код_языка",
"ключ": "Переведённое значение",
"ключ_с_параметром": "Текст с {параметром}"
}
```
## ✨ Преимущества реализации
1. **Простота** — добавить язык = создать один JSON файл
2. **Автоматизация** — не нужно менять код для добавления языка
3. **Масштабируемость** — можно добавить неограниченное количество языков
4. **UX** — мгновенная смена языка без перезагрузки
5. **Поддержка** — легко обновлять и исправлять переводы
---
**Система готова к использованию!** 🚀
+88
View File
@@ -0,0 +1,88 @@
# Обновление: Добавлена система локализации
## Что изменилось
### 1. Создана папка `lang` с файлами локализации
- `en.json` — английский язык
- `ru.json` — русский язык
- `kz.json` — казахский язык (пример)
- `README.md` — инструкция по добавлению новых языков
### 2. Изменения в `main.py`
#### Бэкенд:
- Добавлена константа `LANG_DIR` для папки с локализациями
- Добавлена функция `get_available_languages()` — сканирует папку lang и возвращает список доступных языков
- Добавлена функция `load_language(lang_code)` — загружает файл локализации
- Добавлен эндпоинт `GET /api/languages` — возвращает список доступных языков
- Добавлен эндпоинт `GET /api/language/{lang_code}` — возвращает строки локализации для языка
- В `config.json` добавлено поле `language` (по умолчанию "ru")
- Обновлена модель `SettingsModel` с полем `language`
#### Фронтенд:
- Добавлены реактивные переменные:
- `availableLanguages` — список доступных языков
- `translations` — текущие строки локализации
- `currentLang` — текущий выбранный язык
- Добавлена функция `t(key, params)` — возвращает локализованную строку с подстановкой параметров
- Добавлена функция `loadLanguage(langCode)` — загружает локализацию
- Добавлена функция `changeLanguage(langCode)` — меняет язык интерфейса
- Все текстовые строки в интерфейсе заменены на вызовы `t('ключ')`
- В настройках добавлен селектор языка интерфейса
- При загрузке приложения автоматически загружается язык из настроек
### 3. Изменения в `config.json`
Теперь файл содержит дополнительное поле:
```json
{
"readeck_url": "...",
"readeck_token": "...",
"public_host": "...",
"language": "ru"
}
```
## Как использовать
### Смена языка интерфейса
1. Откройте настройки (кнопка ⚙️)
2. Выберите язык в выпадающем списке "🌍 Язык интерфейса"
3. Интерфейс сразу переключится на выбранный язык
4. Нажмите "Сохранить" чтобы сохранить выбор
### Добавление нового языка
1. Создайте файл `lang/код_языка.json` (например, `de.json` для немецкого)
2. Скопируйте структуру из `en.json` или `ru.json`
3. Переведите все значения (не меняя ключи)
4. Укажите `lang_name` и `lang_code`
5. Перезапустите приложение
6. Новый язык автоматически появится в настройках!
## Пример файла локализации
```json
{
"lang_name": "Deutsch",
"lang_code": "de",
"app_title": "Readeck Importer",
"app_subtitle": "Lokale Artikel mit Übersetzung in Readeck importieren",
"settings": "Einstellungen",
...
}
```
## Технические детали
- Локализация загружается асинхронно при старте приложения
- Язык сохраняется в `config.json` и восстанавливается при следующем запуске
- Система поддерживает параметры в строках: `{id}`, `{words}`, `{error}` и т.д.
- Если файл локализации не найден, используются ключи как fallback
- Все файлы должны быть в кодировке UTF-8
## Преимущества реализации
**Простота добавления языков** — просто положите JSON-файл в папку `lang`
**Автоматическое обнаружение** — новые языки появляются в интерфейсе без изменения кода
**Горячая замена** — язык меняется мгновенно без перезагрузки страницы
**Расширяемость** — легко добавить любое количество языков
**Централизация** — все переводы в одном месте
+83
View File
@@ -0,0 +1,83 @@
# Readeck Local Importer
Локальный веб-сервис для импорта статей в [Readeck](https://readeck.org/) (self-hosted сервис «прочитать позже»). Позволяет загрузить текст, файл или статью по ссылке, перевести её, отредактировать метаданные и одной кнопкой создать закладку в Readeck.
Readeck умеет сохранять закладки только по URL, поэтому приложение поднимает временную ссылку на ваш контент в локальной сети и передаёт её Readeck — так локальный текст попадает в библиотеку как обычная статья.
## Возможности
- **Импорт по ссылке** — скачивает страницу и извлекает чистый текст статьи (`trafilatura`).
- **Загрузка файлов** `.txt`, `.html`, `.md` (+ drag & drop), автоопределение кодировки.
- **Форматы контента** — HTML, Markdown, простой текст.
- **Перевод** через Google (22 языка) с учётом лимитов на длину запроса.
- **Автозаполнение метаданных** из HTML-метатегов (заголовок, автор, описание, дата, сайт).
- **Предпросмотр** статьи ровно в том виде, в каком её увидит Readeck.
- **Санитизация HTML** перед публикацией (`bleach`).
- **Тест подключения** к Readeck прямо из настроек.
- **Локализация интерфейса** — поддержка нескольких языков с возможностью добавления новых.
- Тёмная тема, счётчик символов/слов, автосохранение черновика.
## Требования
- Python 3.9+
- Доступный сервер Readeck и API-токен к нему
## Установка
```bash
pip install fastapi uvicorn pydantic beautifulsoup4 lxml httpx deep-translator markdown bleach trafilatura
```
## Запуск
```bash
python main.py
```
Сервер стартует на `http://0.0.0.0:8142`, браузер откроется автоматически на `http://127.0.0.1:8142`.
При первом запуске откроется окно настроек — укажите:
- **Readeck URL** — адрес вашего сервера Readeck (например `http://192.168.1.10:8000`)
- **API Токен** — токен из настроек Readeck (`Bearer`)
- **LAN IP** — IP этой машины в локальной сети (для callback-ссылки, по которой Readeck заберёт контент)
Нажмите «Проверить подключение», чтобы убедиться, что сервер и токен валидны, затем сохраните. Настройки записываются в `config.json`.
## Использование
1. Вставьте текст, загрузите/перетащите файл или импортируйте статью по ссылке.
2. При необходимости переведите контент и выберите его формат.
3. Заполните или автозаполните метаданные, добавьте теги.
4. Посмотрите предпросмотр и нажмите «Создать закладку».
## Файлы
- `main.py` — всё приложение (бэкенд FastAPI + фронтенд на Vue 3 / Tailwind).
- `config.json` — настройки подключения к Readeck и выбранный язык интерфейса.
- `lang/` — папка с файлами локализации интерфейса.
## Локализация
Приложение поддерживает несколько языков интерфейса. Доступные языки:
- 🇬🇧 English
- 🇷🇺 Русский
- 🇰🇿 Қазақша (Казахский)
### Смена языка
1. Откройте настройки (⚙️)
2. Выберите язык в списке "🌍 Язык интерфейса"
3. Язык изменится мгновенно
### Добавление нового языка
1. Создайте файл `lang/код_языка.json` (например, `de.json`)
2. Скопируйте структуру из `lang/en.json` или `lang/ru.json`
3. Переведите все значения (не меняя ключи)
4. Перезапустите приложение — новый язык появится автоматически!
Подробнее см. `lang/README.md` и `LOCALIZATION.md`.
## Примечания по безопасности
- `config.json` хранит API-токен в открытом виде. Не коммитьте файл в git; при необходимости перевыпустите токен.
- Сервис слушает `0.0.0.0:8142` **без аутентификации** и доступен всем в локальной сети. Эндпоинт импорта по URL скачивает произвольные адреса (потенциальный SSRF). Для домашней сети это обычно приемлемо; не выставляйте сервис в интернет без авторизации.
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
{
"readeck_url": "http://192.168.1.182:8000",
"readeck_token": "Ra5nCVmru35waFbZE9jDrNnYLbkptHc952qtMFPprJDyJCHZ",
"public_host": "192.168.1.151",
"language": "ru"
}
+93
View File
@@ -0,0 +1,93 @@
# Локализация / Localization
Эта папка содержит файлы локализации для интерфейса приложения.
## Как добавить новый язык
1. Создайте новый JSON-файл в этой папке с кодом языка в качестве имени файла, например: `de.json` для немецкого языка.
2. Скопируйте содержимое из `en.json` или `ru.json` в качестве шаблона.
3. Переведите все значения (правая часть после двоеточия) на ваш язык. **Не изменяйте ключи** (левая часть).
4. Обязательно укажите:
- `lang_name` — название языка на этом языке (например, "Deutsch" для немецкого)
- `lang_code` — код языка (должен совпадать с именем файла)
5. Сохраните файл в кодировке UTF-8.
6. Перезапустите приложение — новый язык автоматически появится в настройках!
## Структура файла локализации
```json
{
"lang_name": "Название языка",
"lang_code": "код",
"ключ": "Переведённое значение",
...
}
```
### Параметры в строках
Некоторые строки содержат параметры в фигурных скобках, например:
- `{id}` — будет заменён на ID закладки
- `{words}` — будет заменён на количество слов
- `{error}` — будет заменён на текст ошибки
**Не удаляйте эти параметры** при переводе, просто переместите их в нужное место в предложении.
## Доступные языки
- `en.json` — English (Английский)
- `ru.json` — Русский
- `kz.json` — Қазақша (Казахский)
---
# Localization
This folder contains localization files for the application interface.
## How to add a new language
1. Create a new JSON file in this folder with the language code as the filename, e.g., `de.json` for German.
2. Copy the content from `en.json` or `ru.json` as a template.
3. Translate all values (right side after the colon) to your language. **Do not change the keys** (left side).
4. Make sure to specify:
- `lang_name` — the name of the language in that language (e.g., "Deutsch" for German)
- `lang_code` — language code (must match the filename)
5. Save the file in UTF-8 encoding.
6. Restart the application — the new language will automatically appear in settings!
## Localization file structure
```json
{
"lang_name": "Language name",
"lang_code": "code",
"key": "Translated value",
...
}
```
### Parameters in strings
Some strings contain parameters in curly braces, for example:
- `{id}` — will be replaced with bookmark ID
- `{words}` — will be replaced with word count
- `{error}` — will be replaced with error text
**Do not remove these parameters** when translating, just move them to the appropriate place in the sentence.
## Available languages
- `en.json` — English
- `ru.json` — Русский (Russian)
- `kz.json` — Қазақша (Kazakh)
+89
View File
@@ -0,0 +1,89 @@
{
"lang_name": "English",
"lang_code": "en",
"app_title": "Readeck Importer",
"app_subtitle": "Import local articles to Readeck with translation",
"settings": "Settings",
"theme_light": "Light theme",
"theme_dark": "Dark theme",
"section_upload": "Upload and Content",
"section_metadata": "Metadata",
"section_readeck": "Readeck Options",
"import_url": "Import from URL",
"import_url_placeholder": "https://example.com/article",
"import_url_button": "⬇️ Download",
"import_url_loading": "Loading...",
"file_upload": "File (.txt, .html, .md)",
"content_format": "Content Format",
"format_html": "HTML",
"format_markdown": "Markdown",
"format_text": "Text",
"content_label": "Content",
"content_placeholder": "Upload a file, drag it here, paste text, or import from URL...",
"content_stats": "chars · {words} words",
"translation": "Translation:",
"translate_button": "🔄 Translate",
"translating": "Translating...",
"preview_button": "👁️ Preview",
"preview_title": "Preview",
"preview_new_tab": "↗️ Open in new tab",
"preview_close": "✕ Close",
"autofill_button": "✨ Autofill",
"autofill_tooltip": "Fill from HTML meta tags",
"meta_title": "Title *",
"meta_title_placeholder": "My article",
"meta_authors": "Authors",
"meta_authors_placeholder": "John Doe",
"meta_date": "Date (ISO)",
"meta_date_placeholder": "2023-10-01",
"meta_description": "Description",
"meta_description_placeholder": "Brief description...",
"meta_site_name": "Site Name",
"meta_site_name_placeholder": "Local Source",
"tags_label": "🏷️ Tags",
"tags_placeholder": "tech, news, translated",
"favorite": "⭐ Add to Favorites",
"archive": "📦 Archive",
"submit_button": "🚀 Create Bookmark",
"submitting": "⏳ Sending to Readeck...",
"settings_title": "Settings",
"settings_readeck_url": "🌐 Readeck URL",
"settings_readeck_url_placeholder": "http://192.168.1.10:8000",
"settings_token": "🔑 API Token",
"settings_token_placeholder": "rdk_...",
"settings_lan_ip": "📡 Your LAN IP",
"settings_lan_ip_placeholder": "192.168.x.x",
"settings_language": "🌍 Interface Language",
"settings_test": "🔌 Test Connection",
"settings_testing": "Testing...",
"settings_cancel": "Cancel",
"settings_save": "💾 Save",
"error_title_required": "Error: Title and Content are required!",
"success_bookmark_created": "Success! Bookmark created (ID: {id})",
"success_article_loaded": "✅ Article loaded and metadata filled",
"error_loading": "Loading error:\\n{error}",
"error_translation": "Translation error:\\n{error}",
"error_settings_save": "Failed to save settings:\\n{error}",
"error_network": "Network error while saving:\\n{error}",
"error_file_read": "Failed to read file:\\n{error}",
"error_metadata": "Failed to extract metadata:\\n{error}",
"error_preview": "Failed to build preview:\\n{error}",
"error_submit": "Error:\\n{error}",
"test_success": "✅ Connection successful",
"test_error": "❌ {error}"
}
+89
View File
@@ -0,0 +1,89 @@
{
"lang_name": "Қазақша",
"lang_code": "kz",
"app_title": "Readeck Importer",
"app_subtitle": "Readeck-ке аудармамен жергілікті мақалаларды импорттау",
"settings": "Баптаулар",
"theme_light": "Ашық тақырып",
"theme_dark": "Қараңғы тақырып",
"section_upload": "Жүктеу және Мазмұн",
"section_metadata": "Метадеректер",
"section_readeck": "Readeck опциялары",
"import_url": "Сілтеме бойынша импорттау",
"import_url_placeholder": "https://example.com/article",
"import_url_button": "⬇️ Жүктеу",
"import_url_loading": "Жүктелуде...",
"file_upload": "Файл (.txt, .html, .md)",
"content_format": "Мазмұн форматы",
"format_html": "HTML",
"format_markdown": "Markdown",
"format_text": "Мәтін",
"content_label": "Мазмұн",
"content_placeholder": "Файлды жүктеңіз, мұнда апарыңыз, мәтінді қойыңыз немесе сілтеме бойынша импорттаңыз...",
"content_stats": "таңба · {words} сөз",
"translation": "Аударма:",
"translate_button": "🔄 Аудару",
"translating": "Аударылуда...",
"preview_button": "👁️ Алдын ала қарау",
"preview_title": "Алдын ала қарау",
"preview_new_tab": "↗️ Жаңа қойындыда ашу",
"preview_close": "✕ Жабу",
"autofill_button": "✨ Автотолтыру",
"autofill_tooltip": "HTML мета тегтерінен толтыру",
"meta_title": "Тақырып *",
"meta_title_placeholder": "Менің мақалам",
"meta_authors": "Авторлар",
"meta_authors_placeholder": "Иван Иванов",
"meta_date": "Күні (ISO)",
"meta_date_placeholder": "2023-10-01",
"meta_description": "Сипаттама",
"meta_description_placeholder": "Қысқаша сипаттама...",
"meta_site_name": "Сайт атауы",
"meta_site_name_placeholder": "Жергілікті көз",
"tags_label": "🏷️ Тегтер",
"tags_placeholder": "tech, news, translated",
"favorite": "⭐ Таңдаулыларға қосу",
"archive": "📦 Мұрағатқа",
"submit_button": "🚀 Бетбелгі жасау",
"submitting": "⏳ Readeck-ке жіберілуде...",
"settings_title": "Баптаулар",
"settings_readeck_url": "🌐 Readeck URL",
"settings_readeck_url_placeholder": "http://192.168.1.10:8000",
"settings_token": "🔑 API токені",
"settings_token_placeholder": "rdk_...",
"settings_lan_ip": "📡 Сіздің LAN IP",
"settings_lan_ip_placeholder": "192.168.x.x",
"settings_language": "🌍 Интерфейс тілі",
"settings_test": "🔌 Қосылымды тексеру",
"settings_testing": "Тексерілуде...",
"settings_cancel": "Болдырмау",
"settings_save": "💾 Сақтау",
"error_title_required": "Қате: Тақырып және Мазмұн міндетті!",
"success_bookmark_created": "Сәтті! Бетбелгі жасалды (ID: {id})",
"success_article_loaded": "✅ Мақала жүктелді және метадеректер толтырылды",
"error_loading": "Жүктеу қатесі:\\n{error}",
"error_translation": "Аударма қатесі:\\n{error}",
"error_settings_save": "Баптауларды сақтау мүмкін болмады:\\n{error}",
"error_network": "Сақтау кезінде желі қатесі:\\n{error}",
"error_file_read": "Файлды оқу мүмкін болмады:\\n{error}",
"error_metadata": "Метадеректерді алу мүмкін болмады:\\n{error}",
"error_preview": "Алдын ала қарауды құру мүмкін болмады:\\n{error}",
"error_submit": "Қате:\\n{error}",
"test_success": "✅ Қосылым сәтті",
"test_error": "❌ {error}"
}
+89
View File
@@ -0,0 +1,89 @@
{
"lang_name": "Русский",
"lang_code": "ru",
"app_title": "Readeck Importer",
"app_subtitle": "Импорт локальных статей в Readeck с переводом",
"settings": "Настройки",
"theme_light": "Светлая тема",
"theme_dark": "Тёмная тема",
"section_upload": "Загрузка и Контент",
"section_metadata": "Метаданные",
"section_readeck": "Опции Readeck",
"import_url": "Импорт по ссылке",
"import_url_placeholder": "https://example.com/article",
"import_url_button": "⬇️ Загрузить",
"import_url_loading": "Загрузка...",
"file_upload": "Файл (.txt, .html, .md)",
"content_format": "Формат контента",
"format_html": "HTML",
"format_markdown": "Markdown",
"format_text": "Текст",
"content_label": "Контент",
"content_placeholder": "Загрузите файл, перетащите его сюда, вставьте текст или импортируйте по ссылке...",
"content_stats": "симв. · {words} слов",
"translation": "Перевод:",
"translate_button": "🔄 Перевести",
"translating": "Перевод...",
"preview_button": "👁️ Предпросмотр",
"preview_title": "Предпросмотр",
"preview_new_tab": "↗️ В новой вкладке",
"preview_close": "✕ Закрыть",
"autofill_button": "✨ Автозаполнить",
"autofill_tooltip": "Заполнить из HTML-метатегов контента",
"meta_title": "Заголовок *",
"meta_title_placeholder": "Моя статья",
"meta_authors": "Авторы",
"meta_authors_placeholder": "Иван Иванов",
"meta_date": "Дата (ISO)",
"meta_date_placeholder": "2023-10-01",
"meta_description": "Описание",
"meta_description_placeholder": "Краткое описание...",
"meta_site_name": "Название сайта",
"meta_site_name_placeholder": "Local Source",
"tags_label": "🏷️ Теги",
"tags_placeholder": "tech, news, translated",
"favorite": "⭐ В Избранное",
"archive": "📦 В Архив",
"submit_button": "🚀 Создать закладку",
"submitting": "⏳ Отправка в Readeck...",
"settings_title": "Настройки",
"settings_readeck_url": "🌐 Readeck URL",
"settings_readeck_url_placeholder": "http://192.168.1.10:8000",
"settings_token": "🔑 API Токен",
"settings_token_placeholder": "rdk_...",
"settings_lan_ip": "📡 Ваш LAN IP",
"settings_lan_ip_placeholder": "192.168.x.x",
"settings_language": "🌍 Язык интерфейса",
"settings_test": "🔌 Проверить подключение",
"settings_testing": "Проверка...",
"settings_cancel": "Отмена",
"settings_save": "💾 Сохранить",
"error_title_required": "Ошибка: Заголовок и Контент обязательны!",
"success_bookmark_created": "Успешно! Закладка создана (ID: {id})",
"success_article_loaded": "✅ Статья загружена и метаданные заполнены",
"error_loading": "Ошибка загрузки:\\n{error}",
"error_translation": "Ошибка перевода:\\n{error}",
"error_settings_save": "Не удалось сохранить настройки:\\n{error}",
"error_network": "Сетевая ошибка при сохранении:\\n{error}",
"error_file_read": "Не удалось прочитать файл:\\n{error}",
"error_metadata": "Не удалось извлечь метаданные:\\n{error}",
"error_preview": "Не удалось построить предпросмотр:\\n{error}",
"error_submit": "Ошибка:\\n{error}",
"test_success": "✅ Подключение успешно",
"test_error": "❌ {error}"
}
+1376
View File
File diff suppressed because it is too large Load Diff