feat(i18n): add Uzbek and Brazilian Portuguese translations (#4061)

* feat(i18n): add Uzbek (Oʻzbek) translation

Adds `uz` as a first-class supported locale across the Readest app
and the readest.koplugin companion. Also refactors the supported
locale set to source from a single ground-truth file
(`apps/readest-app/i18n-langs.json`) consumed by both the i18next
runtime and the i18next-scanner config, and replaces a NUL-byte
sentinel in `extract-i18n.js#unescapePo` with a single-pass regex
so git no longer treats the script as binary.

Closes #4053

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(i18n): add Brazilian Portuguese (pt-BR) translation

Adds `pt-BR` as a regional variant supported alongside `pt`. Falls back
to `pt` then `en` for any future missing keys, so European Portuguese
gracefully covers gaps. Translations follow Brazilian conventions
(arquivo / tela / excluir / salvar / baixar / senha, gerundive verb
forms) rather than verbatim copying the European catalog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-05-05 15:20:44 +08:00
committed by GitHub
parent 6d29814551
commit 43f72720f2
11 changed files with 3743 additions and 69 deletions
@@ -0,0 +1,111 @@
---
name: i18n-koplugin
description: >
Extract i18n strings from readest.koplugin Lua sources and translate empty
msgstrs in apps/readest.koplugin/locales. Use when the user invokes
/i18n-koplugin or asks to extract/translate koplugin i18n strings.
Runs scripts/extract-i18n.js to sync .po catalogs from `_("...")` calls,
then fills any empty `msgstr ""` entries across all locale files.
user_invocable: true
---
Extract/translate i18n strings for `readest.koplugin`. The catalogs are gettext `.po` files (not JSON like the main app). Run from the repo root or any worktree — the script resolves paths relative to the plugin dir.
## Step 1: Determine the working directory
If currently in a PR worktree (e.g., `/Users/chrox/dev/readest-pr-*`), use that. Otherwise use the main repo. The plugin dir is `<repo-root>/apps/readest.koplugin`.
## Step 2: Extract msgids from Lua sources
```bash
cd <repo-root>/apps/readest.koplugin
node scripts/extract-i18n.js
```
This scans every `*.lua` file under `apps/readest.koplugin/` (except `spec/` and dotdirs) for `_("...")` and `_([[...]])` calls, then for each language listed in `apps/readest-app/i18next-scanner.config.cjs`:
- appends new msgids with empty `msgstr ""`
- preserves existing translations
- drops obsolete msgids
- rewrites the `.po` header (Plural-Forms etc.)
The output prints `<lang> <kept>/<total> (-<dropped> obsolete)` per locale.
## Step 3: Find untranslated entries
An untranslated entry is a non-empty `msgid` followed by an empty `msgstr ""` (the file's header pair `msgid ""` / `msgstr ""` is NOT a translation — skip it).
```bash
cd <repo-root>/apps/readest.koplugin/locales
# List locales that still have untranslated strings, with counts
for f in */translation.po; do
# Count empty msgstrs that follow a non-empty msgid
n=$(awk '
/^msgid "/ { msgid=$0; next }
/^msgstr ""$/ { if (msgid != "msgid \"\"") c++; next }
' "$f")
[ "$n" -gt 0 ] && echo "$f: $n untranslated"
done
```
If no results, report that all strings are translated and stop.
To list the actual untranslated msgids in one locale:
```bash
awk '
/^msgid "/ { msgid=$0; next }
/^msgstr ""$/ { if (msgid != "msgid \"\"") print msgid; next }
' <repo-root>/apps/readest.koplugin/locales/<lang>/translation.po
```
## Step 4: Translate empty msgstrs
For each empty `msgstr ""` found:
1. Read the preceding `msgid "..."` — that's the English source string.
2. Identify the target locale from the file path (e.g., `locales/ja/translation.po` → Japanese; see table below).
3. Provide an accurate translation. Use the locale reference table for the language; match the tone/terminology already used in the same file (check existing translated entries for context).
4. Preserve `printf`-style placeholders verbatim: `%s`, `%d`, `%1$s`, `%(name)s`, etc.
5. Preserve newlines as `\n`, tabs as `\t`, and escape `"` as `\"` and backslashes as `\\` inside the msgstr.
Edit the `.po` files directly with the Edit tool — do NOT use sed for this, because msgids may contain characters that confuse shell quoting. Each replacement should target the unique `msgid "<English>"\nmsgstr ""` block:
Old:
```
msgid "<English string>"
msgstr ""
```
New:
```
msgid "<English string>"
msgstr "<translation>"
```
Batch all locales for the same key together when possible — keeps the translation set consistent.
### Locale reference
The supported language set is **not hardcoded in this skill**. The ground truth is `apps/readest-app/i18n-langs.json` — both `i18next-scanner.config.cjs` (via `require`) and `src/i18n/i18n.ts` (via JSON import) source from it, and `extract-i18n.js` reads `lngs` from the scanner config at runtime. To list the current set:
```bash
cat <repo-root>/apps/readest-app/i18n-langs.json
```
Map each code to a language name when translating. If a code in `i18n-langs.json` is missing from the `LANG_META` table inside `extract-i18n.js`, the script prints `<code> skipped (no metadata in extract-i18n.js)` — in that case, add the metadata entry there first, then re-run extraction.
## Step 5: Verify
Re-run the count loop from Step 3 and confirm zero untranslated strings remain. Report:
- number of msgids extracted
- per-locale count of strings translated
- any locales that were already complete
Optionally, run the koplugin Lua tests if `busted`/`luajit` are installed:
```bash
cd <repo-root>/apps/readest-app
pnpm test:lua
```
+24
View File
@@ -41,6 +41,30 @@ pnpm i18n:extract # Scans codebase, adds new keys with __STRING_NOT_TRANSLATE
- Translation files: `public/locales/<locale>/translation.json`
- Only `_('KEY')` and `_('KEY', options)` patterns are recognized by i18next-scanner
### Adding a New Translation Language
The supported language set has a single ground truth: [`i18n-langs.json`](../i18n-langs.json). Both the i18next runtime (`src/i18n/i18n.ts`) and the extractor (`i18next-scanner.config.cjs`) read from it, so adding a locale is a two-file change plus a translation pass.
1. **Add the locale code** to [`i18n-langs.json`](../i18n-langs.json). Use the exact code i18next will emit (e.g. `hu`, `zh-CN`). Do not add `en` — it's the source language and lives outside this list.
2. **Add a display label** to `TRANSLATED_LANGS` in [`src/services/constants.ts`](../src/services/constants.ts). The key is the locale code, the value is the language's native name (e.g. `hu: 'Magyar'`). This is what users see in the language picker.
3. **Generate the translation file**:
```bash
pnpm i18n:extract
```
This creates `public/locales/<code>/translation.json` with every key set to `__STRING_NOT_TRANSLATED__`.
4. **Translate** every `__STRING_NOT_TRANSLATED__` placeholder in the new file. The `/i18n` skill automates this; the singular `en/translation.json` only holds plural variants and proper nouns, so use the JSON keys themselves as the English source.
5. **Verify** with `grep -r "__STRING_NOT_TRANSLATED__" public/locales/<code>/` — the result should be empty.
6. **Translate the KOReader companion plugin** (`apps/readest.koplugin`). It pulls the locale set from the same `i18n-langs.json` via the scanner config, but the catalog format is gettext `.po`, not JSON. Steps:
- Add a `LANG_META` entry (label + Plural-Forms) for the new code in [`apps/readest.koplugin/scripts/extract-i18n.js`](../../readest.koplugin/scripts/extract-i18n.js). Without it the extractor prints `<code> skipped (no metadata in extract-i18n.js)` and the catalog is never created.
- Trigger the `/i18n-koplugin` skill to run extraction and fill every empty `msgstr ""` in `apps/readest.koplugin/locales/<code>/translation.po`.
### Rules
- `stubTranslation` is for extraction only — always apply `_()` from `useTranslation` in the component for runtime translation.
+35
View File
@@ -0,0 +1,35 @@
[
"de",
"ja",
"es",
"fa",
"fr",
"it",
"el",
"ko",
"uk",
"nl",
"sl",
"sv",
"pl",
"pt",
"ru",
"tr",
"hi",
"id",
"vi",
"ms",
"he",
"ar",
"th",
"bo",
"bn",
"ta",
"si",
"ro",
"hu",
"uz",
"pt-BR",
"zh-CN",
"zh-TW"
]
+3 -33
View File
@@ -1,3 +1,5 @@
const lngs = require('./i18n-langs.json');
const options = {
debug: false,
sort: false,
@@ -5,39 +7,7 @@ const options = {
list: ['_'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
lngs: [
'de',
'ja',
'es',
'fa',
'fr',
'it',
'el',
'ko',
'uk',
'nl',
'sl',
'sv',
'pl',
'pt',
'ru',
'tr',
'hi',
'id',
'vi',
'ms',
'he',
'ar',
'th',
'bo',
'bn',
'ta',
'si',
'zh-CN',
'zh-TW',
'ro',
'hu',
],
lngs,
ns: ['translation'],
defaultNs: 'translation',
defaultValue: '__STRING_NOT_TRANSLATED__',
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5 -36
View File
@@ -1,41 +1,10 @@
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import translatableLngs from '../../i18n-langs.json';
import { initReactI18next } from 'react-i18next';
// Keep in sync with i18next-scanner.config.cjs
const SUPPORTED_LNGS = [
'en',
'de',
'ja',
'es',
'fa',
'fr',
'it',
'el',
'ko',
'uk',
'nl',
'sl',
'sv',
'pl',
'pt',
'ru',
'tr',
'hi',
'id',
'vi',
'ms',
'he',
'ar',
'th',
'bo',
'bn',
'ta',
'si',
'zh-CN',
'zh-TW',
'ro',
'hu',
];
// 'en' is the source language and not listed in the translatable set.
const SUPPORTED_LNGS = ['en', ...translatableLngs];
const isBrowser = typeof window !== 'undefined';
@@ -52,10 +21,10 @@ const initI18n = async () => {
supportedLngs: SUPPORTED_LNGS,
fallbackLng: {
'zh-HK': ['zh-TW', 'en'],
'pt-BR': ['pt', 'en'],
kk: ['ru', 'en'],
ky: ['ru', 'en'],
tk: ['ru', 'en'],
uz: ['ru', 'en'],
ug: ['ru', 'en'],
tt: ['ru', 'en'],
default: ['en'],
@@ -842,6 +842,7 @@ export const TRANSLATED_LANGS = {
ko: '한국어',
es: 'Español',
pt: 'Português',
'pt-BR': 'Português (Brasil)',
ru: 'Русский',
he: 'עברית',
ar: 'العربية',
@@ -864,6 +865,7 @@ export const TRANSLATED_LANGS = {
'zh-TW': '正體中文',
ro: 'Română',
hu: 'Magyar',
uz: 'Oʻzbek',
};
export const TRANSLATOR_LANGS: Record<string, string> = {
@@ -0,0 +1,438 @@
# Portuguese (Brazil) translation for readest.koplugin
#
msgid ""
msgstr ""
"Project-Id-Version: readest.koplugin\n"
"Last-Translator: Readest contributors\n"
"Language-Team: Portuguese (Brazil)\n"
"Language: pt-BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Readest"
msgstr "Readest"
msgid "Keeps your KOReader and Readest devices in sync."
msgstr "Mantém seus dispositivos KOReader e Readest sincronizados."
msgid "Library view"
msgstr "Visualização da biblioteca"
msgid "View"
msgstr "Visualizar"
msgid "Grid"
msgstr "Grade"
msgid "List"
msgstr "Lista"
msgid "Group by"
msgstr "Agrupar por"
msgid "Authors"
msgstr "Autores"
msgid "Books"
msgstr "Livros"
msgid "Groups"
msgstr "Grupos"
msgid "Series"
msgstr "Séries"
msgid "Sort by"
msgstr "Ordenar por"
msgid "Date Read"
msgstr "Data de leitura"
msgid "Date Added"
msgstr "Data de adição"
msgid "Title"
msgstr "Título"
msgid "Author"
msgstr "Autor"
msgid "Descending"
msgstr "Decrescente"
msgid "Ascending"
msgstr "Crescente"
msgid "Actions"
msgstr "Ações"
msgid "Rescan library"
msgstr "Reescanear biblioteca"
msgid "Download folder…"
msgstr "Pasta de download…"
msgid "Pick a folder for downloaded books"
msgstr "Escolha uma pasta para os livros baixados"
msgid "Sign in to Readest to open the Library"
msgstr "Faça login no Readest para abrir a biblioteca"
msgid "Library"
msgstr "Biblioteca"
msgid "Readest Library"
msgstr "Biblioteca Readest"
msgid "Cancel"
msgstr "Cancelar"
msgid "Clear"
msgstr "Limpar"
msgid "Search"
msgstr "Buscar"
msgid "Search library"
msgstr "Buscar na biblioteca"
msgid "Search title or author"
msgstr "Buscar título ou autor"
msgid "Cover Browser plugin required for full Library rendering. Falling back to plain list."
msgstr "Plugin Cover Browser necessário para renderização completa da biblioteca. Voltando à lista simples."
msgid "File moved or deleted. Rescan library?"
msgstr "Arquivo movido ou excluído. Reescanear biblioteca?"
msgid "Download this book from Readest?"
msgstr "Baixar este livro do Readest?"
msgid "Download"
msgstr "Baixar"
msgid "Set Home folder in File Manager first to enable downloads."
msgstr "Configure primeiro a pasta inicial no Gerenciador de Arquivos para ativar os downloads."
msgid "Downloading…"
msgstr "Baixando…"
msgid "Cloud copy unavailable."
msgstr "Cópia na nuvem indisponível."
msgid "Download failed."
msgstr "Falha no download."
msgid "No cover available on Readest."
msgstr "Nenhuma capa disponível no Readest."
msgid "Cover download failed."
msgstr "Falha ao baixar a capa."
msgid "Could not delete the file."
msgstr "Não foi possível excluir o arquivo."
msgid "Removing from cloud…"
msgstr "Removendo da nuvem…"
msgid "Cloud removal failed."
msgstr "Falha na remoção da nuvem."
msgid "Remove from Cloud & Device"
msgstr "Remover da nuvem e do dispositivo"
msgid "Remove this book from cloud and device?"
msgstr "Remover este livro da nuvem e do dispositivo?"
msgid "Remove"
msgstr "Remover"
msgid "Remove from Cloud Only"
msgstr "Remover apenas da nuvem"
msgid "Remove this book from the cloud only?"
msgstr "Remover este livro apenas da nuvem?"
msgid "Remove from Device Only"
msgstr "Remover apenas do dispositivo"
msgid "Remove the local copy of this book?"
msgstr "Remover a cópia local deste livro?"
msgid "Upload to Cloud"
msgstr "Enviar para a nuvem"
msgid "Uploading…"
msgstr "Enviando…"
msgid "Storage quota exceeded."
msgstr "Cota de armazenamento excedida."
msgid "Upload failed."
msgstr "Falha no envio."
msgid "Download Book"
msgstr "Baixar livro"
msgid "Download Cover"
msgstr "Baixar capa"
msgid "Download All"
msgstr "Baixar tudo"
msgid "Open Readest Library"
msgstr "Abrir biblioteca do Readest"
msgid "Push Readest book library"
msgstr "Enviar biblioteca do Readest"
msgid "Pull Readest book library"
msgstr "Obter biblioteca do Readest"
msgid "Set auto progress sync"
msgstr "Definir sincronização automática do progresso"
msgid "on"
msgstr "ativado"
msgid "off"
msgstr "desativado"
msgid "Toggle auto readest sync"
msgstr "Alternar sincronização automática do Readest"
msgid "Push readest progress from this device"
msgstr "Enviar progresso do Readest deste dispositivo"
msgid "Pull readest progress from other devices"
msgstr "Obter progresso do Readest de outros dispositivos"
msgid "Push readest annotations from this device"
msgstr "Enviar anotações do Readest deste dispositivo"
msgid "Pull readest annotations from other devices"
msgstr "Obter anotações do Readest de outros dispositivos"
msgid "Add to Readest"
msgstr "Adicionar ao Readest"
msgid "Sign in to Readest first."
msgstr "Faça login no Readest primeiro."
msgid "File not found."
msgstr "Arquivo não encontrado."
msgid "Unsupported book format."
msgstr "Formato de livro não suportado."
msgid "Hashing book…"
msgstr "Calculando hash do livro…"
msgid "Could not read file."
msgstr "Não foi possível ler o arquivo."
msgid "Already in your Readest library:"
msgstr "Já está na sua biblioteca do Readest:"
msgid "Added to Readest:"
msgstr "Adicionado ao Readest:"
msgid "Log in Readest Account"
msgstr "Entrar na conta Readest"
msgid "Log out as %1"
msgstr "Sair como %1"
msgid "Auto sync"
msgstr "Sincronização automática"
msgid "Readest library"
msgstr "Biblioteca Readest"
msgid "Push books now"
msgstr "Enviar livros agora"
msgid "Pull books now"
msgstr "Obter livros agora"
msgid "Push reading progress now"
msgstr "Enviar progresso de leitura agora"
msgid "Pull reading progress now"
msgstr "Obter progresso de leitura agora"
msgid "Push annotations now"
msgstr "Enviar anotações agora"
msgid "Pull annotations now"
msgstr "Obter anotações agora"
msgid "Full sync all annotations"
msgstr "Sincronizar todas as anotações por completo"
msgid "Sync info"
msgstr "Informações de sincronização"
msgid "Check for update (v%1)"
msgstr "Verificar atualização (v%1)"
msgid "Check for update"
msgstr "Verificar atualização"
msgid "Please login first"
msgstr "Faça login primeiro"
msgid "Please configure Readest settings first"
msgstr "Configure primeiro as definições do Readest"
msgid "No book is open"
msgstr "Nenhum livro aberto"
msgid "(none)"
msgstr "(nenhum)"
msgid "Never synced"
msgstr "Nunca sincronizado"
msgid "Book Fingerprint"
msgstr "Impressão digital do livro"
msgid "Identifiers"
msgstr "Identificadores"
msgid "Last Synced"
msgstr "Última sincronização"
msgid "Sync Info"
msgstr "Informações de sincronização"
msgid "Library not initialized"
msgstr "Biblioteca não inicializada"
msgid "Books synced"
msgstr "Livros sincronizados"
msgid "Books sync failed"
msgstr "Falha na sincronização de livros"
msgid "Checking for update…"
msgstr "Verificando atualização…"
msgid "Failed to check for update. Please try again later."
msgstr "Falha ao verificar atualização. Tente novamente mais tarde."
msgid "A new version is available: v%1 (current: v%2).\n\nDo you want to update now?"
msgstr "Uma nova versão está disponível: v%1 (atual: v%2).\n\nDeseja atualizar agora?"
msgid "A new version is available: v%1.\n\nDo you want to update now?"
msgstr "Uma nova versão está disponível: v%1.\n\nDeseja atualizar agora?"
msgid "Update"
msgstr "Atualizar"
msgid "You are up to date (v%1)."
msgstr "Você está atualizado (v%1)."
msgid "Downloading update…"
msgstr "Baixando atualização…"
msgid "Failed to download update. Please try again later."
msgstr "Falha ao baixar a atualização. Tente novamente mais tarde."
msgid "Readest plugin updated to v%1.\n\nPlease restart KOReader to apply the update."
msgstr "Plugin Readest atualizado para v%1.\n\nReinicie o KOReader para aplicar a atualização."
msgid "Restart now"
msgstr "Reiniciar agora"
msgid "Later"
msgstr "Mais tarde"
msgid "Failed to install update: %1"
msgstr "Falha ao instalar a atualização: %1"
msgid "unknown error"
msgstr "erro desconhecido"
msgid "No annotations to push"
msgstr "Sem anotações para enviar"
msgid "Pushing annotations..."
msgstr "Enviando anotações..."
msgid "%1 annotations pushed successfully"
msgstr "%1 anotações enviadas com sucesso"
msgid "Failed to push annotations"
msgstr "Falha ao enviar as anotações"
msgid "Annotation sync is not supported for PDF documents"
msgstr "A sincronização de anotações não é suportada para documentos PDF"
msgid "Full sync: pulling all annotations..."
msgstr "Sincronização completa: obtendo todas as anotações..."
msgid "Pulling annotations..."
msgstr "Obtendo anotações..."
msgid "Authentication failed, please login again"
msgstr "Falha de autenticação, faça login novamente"
msgid "Failed to pull annotations"
msgstr "Falha ao obter as anotações"
msgid "No new annotations found"
msgstr "Nenhuma nova anotação encontrada"
msgid "%1 annotations pulled"
msgstr "%1 anotações obtidas"
msgid "Login"
msgstr "Entrar"
msgid "Please enter both email and password"
msgstr "Insira o e-mail e a senha"
msgid "Please configure Supabase URL and API key first"
msgstr "Configure primeiro a URL do Supabase e a chave de API"
msgid "Logging in..."
msgstr "Entrando..."
msgid "Successfully logged in to Readest"
msgstr "Login no Readest realizado com sucesso"
msgid "Login failed: %1"
msgstr "Falha no login: %1"
msgid "Logged out from Readest"
msgstr "Sessão encerrada do Readest"
msgid "Cannot identify the current book"
msgstr "Não foi possível identificar o livro atual"
msgid "Progress has been synchronized."
msgstr "O progresso foi sincronizado."
msgid "Pushing reading progress..."
msgstr "Enviando progresso de leitura..."
msgid "Reading progress pushed successfully"
msgstr "Progresso de leitura enviado com sucesso"
msgid "Failed to push reading progress"
msgstr "Falha ao enviar o progresso de leitura"
msgid "Pulling reading progress..."
msgstr "Obtendo progresso de leitura..."
msgid "Failed to pull reading progress"
msgstr "Falha ao obter o progresso de leitura"
msgid "Reading progress synchronized"
msgstr "Progresso de leitura sincronizado"
msgid "No saved reading progress found for this book"
msgstr "Nenhum progresso de leitura salvo encontrado para este livro"
@@ -0,0 +1,438 @@
# Uzbek translation for readest.koplugin
#
msgid ""
msgstr ""
"Project-Id-Version: readest.koplugin\n"
"Last-Translator: Readest contributors\n"
"Language-Team: Uzbek\n"
"Language: uz\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Readest"
msgstr "Readest"
msgid "Keeps your KOReader and Readest devices in sync."
msgstr "KOReader va Readest qurilmalaringizni sinxronlashda saqlaydi."
msgid "Library view"
msgstr "Kutubxona koʻrinishi"
msgid "View"
msgstr "Koʻrinish"
msgid "Grid"
msgstr "Katak"
msgid "List"
msgstr "Roʻyxat"
msgid "Group by"
msgstr "Guruhlash"
msgid "Authors"
msgstr "Mualliflar"
msgid "Books"
msgstr "Kitoblar"
msgid "Groups"
msgstr "Guruhlar"
msgid "Series"
msgstr "Seriya"
msgid "Sort by"
msgstr "Saralash"
msgid "Date Read"
msgstr "Oʻqilgan sana"
msgid "Date Added"
msgstr "Qoʻshilgan sana"
msgid "Title"
msgstr "Sarlavha"
msgid "Author"
msgstr "Muallif"
msgid "Descending"
msgstr "Kamayish boʻyicha"
msgid "Ascending"
msgstr "Oʻsish boʻyicha"
msgid "Actions"
msgstr "Amallar"
msgid "Rescan library"
msgstr "Kutubxonani qayta skanerlash"
msgid "Download folder…"
msgstr "Yuklab olish jildi…"
msgid "Pick a folder for downloaded books"
msgstr "Yuklab olingan kitoblar uchun jildni tanlang"
msgid "Sign in to Readest to open the Library"
msgstr "Kutubxonani ochish uchun Readest-ga kiring"
msgid "Library"
msgstr "Kutubxona"
msgid "Readest Library"
msgstr "Readest kutubxonasi"
msgid "Cancel"
msgstr "Bekor qilish"
msgid "Clear"
msgstr "Tozalash"
msgid "Search"
msgstr "Qidirish"
msgid "Search library"
msgstr "Kutubxonada qidirish"
msgid "Search title or author"
msgstr "Sarlavha yoki muallif boʻyicha qidirish"
msgid "Cover Browser plugin required for full Library rendering. Falling back to plain list."
msgstr "Toʻliq kutubxona koʻrinishi uchun Cover Browser plagini talab qilinadi. Oddiy roʻyxatga oʻtilmoqda."
msgid "File moved or deleted. Rescan library?"
msgstr "Fayl koʻchirilgan yoki oʻchirilgan. Kutubxonani qayta skanerlaysizmi?"
msgid "Download this book from Readest?"
msgstr "Ushbu kitobni Readest-dan yuklab olasizmi?"
msgid "Download"
msgstr "Yuklab olish"
msgid "Set Home folder in File Manager first to enable downloads."
msgstr "Yuklab olishlarni yoqish uchun avval Fayl menejerida bosh sahifa jildini belgilang."
msgid "Downloading…"
msgstr "Yuklab olinmoqda…"
msgid "Cloud copy unavailable."
msgstr "Bulutdagi nusxa mavjud emas."
msgid "Download failed."
msgstr "Yuklab olib boʻlmadi."
msgid "No cover available on Readest."
msgstr "Readest-da muqova mavjud emas."
msgid "Cover download failed."
msgstr "Muqovani yuklab olib boʻlmadi."
msgid "Could not delete the file."
msgstr "Faylni oʻchirib boʻlmadi."
msgid "Removing from cloud…"
msgstr "Bulutdan olib tashlanmoqda…"
msgid "Cloud removal failed."
msgstr "Bulutdan olib tashlab boʻlmadi."
msgid "Remove from Cloud & Device"
msgstr "Bulut va qurilmadan olib tashlash"
msgid "Remove this book from cloud and device?"
msgstr "Ushbu kitobni bulut va qurilmadan olib tashlaysizmi?"
msgid "Remove"
msgstr "Olib tashlash"
msgid "Remove from Cloud Only"
msgstr "Faqat bulutdan olib tashlash"
msgid "Remove this book from the cloud only?"
msgstr "Ushbu kitobni faqat bulutdan olib tashlaysizmi?"
msgid "Remove from Device Only"
msgstr "Faqat qurilmadan olib tashlash"
msgid "Remove the local copy of this book?"
msgstr "Ushbu kitobning mahalliy nusxasini olib tashlaysizmi?"
msgid "Upload to Cloud"
msgstr "Bulutga yuklash"
msgid "Uploading…"
msgstr "Yuklanmoqda…"
msgid "Storage quota exceeded."
msgstr "Xotira kvotasi oshib ketdi."
msgid "Upload failed."
msgstr "Yuklab boʻlmadi."
msgid "Download Book"
msgstr "Kitobni yuklab olish"
msgid "Download Cover"
msgstr "Muqovani yuklab olish"
msgid "Download All"
msgstr "Barchasini yuklab olish"
msgid "Open Readest Library"
msgstr "Readest kutubxonasini ochish"
msgid "Push Readest book library"
msgstr "Readest kitob kutubxonasini yuborish"
msgid "Pull Readest book library"
msgstr "Readest kitob kutubxonasini olish"
msgid "Set auto progress sync"
msgstr "Avtomatik jarayon sinxronlashni belgilash"
msgid "on"
msgstr "yoqilgan"
msgid "off"
msgstr "oʻchirilgan"
msgid "Toggle auto readest sync"
msgstr "Avtomatik readest sinxronlashni ochish/yopish"
msgid "Push readest progress from this device"
msgstr "Readest jarayonini ushbu qurilmadan yuborish"
msgid "Pull readest progress from other devices"
msgstr "Readest jarayonini boshqa qurilmalardan olish"
msgid "Push readest annotations from this device"
msgstr "Readest izohlarini ushbu qurilmadan yuborish"
msgid "Pull readest annotations from other devices"
msgstr "Readest izohlarini boshqa qurilmalardan olish"
msgid "Add to Readest"
msgstr "Readest-ga qoʻshish"
msgid "Sign in to Readest first."
msgstr "Avval Readest-ga kiring."
msgid "File not found."
msgstr "Fayl topilmadi."
msgid "Unsupported book format."
msgstr "Qoʻllab-quvvatlanmaydigan kitob formati."
msgid "Hashing book…"
msgstr "Kitob xeshlanmoqda…"
msgid "Could not read file."
msgstr "Faylni oʻqib boʻlmadi."
msgid "Already in your Readest library:"
msgstr "Allaqachon Readest kutubxonangizda:"
msgid "Added to Readest:"
msgstr "Readest-ga qoʻshildi:"
msgid "Log in Readest Account"
msgstr "Readest hisobiga kirish"
msgid "Log out as %1"
msgstr "%1 sifatida chiqish"
msgid "Auto sync"
msgstr "Avtomatik sinxronlash"
msgid "Readest library"
msgstr "Readest kutubxonasi"
msgid "Push books now"
msgstr "Kitoblarni hozir yuborish"
msgid "Pull books now"
msgstr "Kitoblarni hozir olish"
msgid "Push reading progress now"
msgstr "Oʻqish jarayonini hozir yuborish"
msgid "Pull reading progress now"
msgstr "Oʻqish jarayonini hozir olish"
msgid "Push annotations now"
msgstr "Izohlarni hozir yuborish"
msgid "Pull annotations now"
msgstr "Izohlarni hozir olish"
msgid "Full sync all annotations"
msgstr "Barcha izohlarni toʻliq sinxronlash"
msgid "Sync info"
msgstr "Sinxronlash maʼlumoti"
msgid "Check for update (v%1)"
msgstr "Yangilanishni tekshirish (v%1)"
msgid "Check for update"
msgstr "Yangilanishni tekshirish"
msgid "Please login first"
msgstr "Iltimos, avval tizimga kiring"
msgid "Please configure Readest settings first"
msgstr "Iltimos, avval Readest sozlamalarini sozlang"
msgid "No book is open"
msgstr "Hech qanday kitob ochilmagan"
msgid "(none)"
msgstr "(yoʻq)"
msgid "Never synced"
msgstr "Sinxronlanmagan"
msgid "Book Fingerprint"
msgstr "Kitob barmoq izi"
msgid "Identifiers"
msgstr "Identifikatorlar"
msgid "Last Synced"
msgstr "Oxirgi sinxronlash"
msgid "Sync Info"
msgstr "Sinxronlash maʼlumoti"
msgid "Library not initialized"
msgstr "Kutubxona ishga tushirilmagan"
msgid "Books synced"
msgstr "Kitoblar sinxronlandi"
msgid "Books sync failed"
msgstr "Kitoblarni sinxronlab boʻlmadi"
msgid "Checking for update…"
msgstr "Yangilanishlar tekshirilmoqda…"
msgid "Failed to check for update. Please try again later."
msgstr "Yangilanishni tekshirib boʻlmadi. Keyinroq qayta urinib koʻring."
msgid "A new version is available: v%1 (current: v%2).\n\nDo you want to update now?"
msgstr "Yangi versiya mavjud: v%1 (joriy: v%2).\n\nHozir yangilamoqchimisiz?"
msgid "A new version is available: v%1.\n\nDo you want to update now?"
msgstr "Yangi versiya mavjud: v%1.\n\nHozir yangilamoqchimisiz?"
msgid "Update"
msgstr "Yangilash"
msgid "You are up to date (v%1)."
msgstr "Sizda eng songgi versiya (v%1)."
msgid "Downloading update…"
msgstr "Yangilanish yuklanmoqda…"
msgid "Failed to download update. Please try again later."
msgstr "Yangilanishni yuklab boʻlmadi. Keyinroq qayta urinib koʻring."
msgid "Readest plugin updated to v%1.\n\nPlease restart KOReader to apply the update."
msgstr "Readest plagini v%1 ga yangilandi.\n\nYangilanishni qoʻllash uchun KOReader-ni qayta ishga tushiring."
msgid "Restart now"
msgstr "Hozir qayta ishga tushirish"
msgid "Later"
msgstr "Keyinroq"
msgid "Failed to install update: %1"
msgstr "Yangilanishni oʻrnatib boʻlmadi: %1"
msgid "unknown error"
msgstr "nomaʼlum xato"
msgid "No annotations to push"
msgstr "Yuborish uchun izohlar yoʻq"
msgid "Pushing annotations..."
msgstr "Izohlar yuborilmoqda..."
msgid "%1 annotations pushed successfully"
msgstr "%1 ta izoh muvaffaqiyatli yuborildi"
msgid "Failed to push annotations"
msgstr "Izohlarni yuborib boʻlmadi"
msgid "Annotation sync is not supported for PDF documents"
msgstr "PDF hujjatlar uchun izoh sinxronlash qoʻllab-quvvatlanmaydi"
msgid "Full sync: pulling all annotations..."
msgstr "Toʻliq sinxronlash: barcha izohlar olinmoqda..."
msgid "Pulling annotations..."
msgstr "Izohlar olinmoqda..."
msgid "Authentication failed, please login again"
msgstr "Autentifikatsiya muvaffaqiyatsiz tugadi, qayta tizimga kiring"
msgid "Failed to pull annotations"
msgstr "Izohlarni olib boʻlmadi"
msgid "No new annotations found"
msgstr "Yangi izohlar topilmadi"
msgid "%1 annotations pulled"
msgstr "%1 ta izoh olindi"
msgid "Login"
msgstr "Kirish"
msgid "Please enter both email and password"
msgstr "Iltimos, email va parolni kiriting"
msgid "Please configure Supabase URL and API key first"
msgstr "Iltimos, avval Supabase URL va API kalitini sozlang"
msgid "Logging in..."
msgstr "Tizimga kirilmoqda..."
msgid "Successfully logged in to Readest"
msgstr "Readest-ga muvaffaqiyatli kirildi"
msgid "Login failed: %1"
msgstr "Kirish muvaffaqiyatsiz tugadi: %1"
msgid "Logged out from Readest"
msgstr "Readest-dan chiqildi"
msgid "Cannot identify the current book"
msgstr "Joriy kitobni aniqlab boʻlmadi"
msgid "Progress has been synchronized."
msgstr "Jarayon sinxronlandi."
msgid "Pushing reading progress..."
msgstr "Oʻqish jarayoni yuborilmoqda..."
msgid "Reading progress pushed successfully"
msgstr "Oʻqish jarayoni muvaffaqiyatli yuborildi"
msgid "Failed to push reading progress"
msgstr "Oʻqish jarayonini yuborib boʻlmadi"
msgid "Pulling reading progress..."
msgstr "Oʻqish jarayoni olinmoqda..."
msgid "Failed to pull reading progress"
msgstr "Oʻqish jarayonini olib boʻlmadi"
msgid "Reading progress synchronized"
msgstr "Oʻqish jarayoni sinxronlandi"
msgid "No saved reading progress found for this book"
msgstr "Ushbu kitob uchun saqlangan oʻqish jarayoni topilmadi"
Binary file not shown.