Compare commits

..

6 Commits

Author SHA1 Message Date
Rajarshee Chatterjee 1e478b2cc8 chore: Release v2.0.1
[skip ci]
2025-11-15 14:52:54 +05:30
Rajarshee Chatterjee dcf667b176 fix: Rerun Migrations and Update Insert Chapter Queries 2025-11-15 14:36:57 +05:30
Rajarshee Chatterjee 3a313b9441 feat: Add Progress Notifications for Local Backup Creation and Restore 2025-11-15 11:07:48 +05:30
Rajarshee Chatterjee ae8211a4c0 fix: Use INSERT OR IGNORE for Library Updates 2025-11-15 10:08:55 +05:30
Rajarshee Chatterjee 3452478aa1 fix: Create Default Category Only If It Does Not Exist
[skip ci]
2025-11-14 21:32:01 +05:30
Rajarshee Chatterjee 0c14018996 chore: Release v2.0.0
[skip ci]
2025-11-14 21:22:04 +05:30
17 changed files with 318 additions and 133 deletions
+2 -2
View File
@@ -45,7 +45,7 @@ body:
label: LNReader version
description: You can find your LNReader version in **More → About**.
placeholder: |
Example: "2.0.0"
Example: "2.0.1"
validations:
required: true
@@ -88,7 +88,7 @@ body:
required: true
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/lnreader/lnreader-sources/issues/new/choose).
required: true
- label: I have updated the app to version **[2.0.0](https://github.com/lnreader/lnreader/releases/latest)**.
- label: I have updated the app to version **[2.0.1](https://github.com/lnreader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+1 -1
View File
@@ -34,7 +34,7 @@ body:
required: true
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/LNReader/lnreader-sources/issues/new/choose).
required: true
- label: I have updated the app to version **[2.0.0](https://github.com/LNReader/lnreader/releases/latest)**.
- label: I have updated the app to version **[2.0.1](https://github.com/LNReader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "lnreader",
"version": "2.0.0",
"version": "2.0.1",
"private": true,
"scripts": {
"dev:android": "pnpm run generate:env:debug && react-native run-android --appIdSuffix \"debug\" --active-arch-only",
@@ -84,7 +84,7 @@
"react-native-draggable-flatlist": "^4.0.3",
"react-native-drawer-layout": "^4.1.13",
"react-native-edge-to-edge": "^1.7.0",
"react-native-error-boundary": "^2.0.0",
"react-native-error-boundary": "^2.0.1",
"react-native-file-access": "^3.2.0",
"react-native-gesture-handler": "^2.28.0",
"react-native-lottie-splash-screen": "^1.1.2",
@@ -97,12 +97,12 @@
"react-native-screens": "^4.16.0",
"react-native-shimmer-placeholder": "^2.0.9",
"react-native-tab-view": "^4.1.3",
"react-native-url-polyfill": "^2.0.0",
"react-native-url-polyfill": "^2.0.1",
"react-native-webview": "13.15.0",
"react-native-worklets": "^0.5.1",
"react-native-zip-archive": "^7.0.2",
"sanitize-html": "^2.17.0",
"urlencode": "^2.0.0"
"urlencode": "^2.0.1"
},
"devDependencies": {
"@babel/core": "^7.28.4",
@@ -1,35 +1,62 @@
import { SQLiteDatabase } from 'expo-sqlite';
import { Migration } from '../types/migration';
const columnExists = (
db: SQLiteDatabase,
tableName: string,
columnName: string,
): boolean => {
const columns = db.getAllSync<{ name: string }>(
`PRAGMA table_info(${tableName})`,
);
return columns.some(col => col.name === columnName);
};
/**
* Migration 1: Add counter columns to Novel table
* Migration 2: Add counter columns to Novel table
* - Adds chaptersDownloaded, chaptersUnread, totalChapters columns
* - Adds lastReadAt, lastUpdatedAt timestamp columns
* - Populates columns with existing data
*/
export const migration001: Migration = {
version: 1,
export const migration002: Migration = {
version: 2,
description: 'Add counter columns and triggers to Novel table',
migrate: db => {
db.runSync(`
ALTER TABLE Novel
ADD COLUMN chaptersDownloaded INTEGER DEFAULT 0
`);
db.runSync(`
ALTER TABLE Novel
ADD COLUMN chaptersUnread INTEGER DEFAULT 0
`);
db.runSync(`
ALTER TABLE Novel
ADD COLUMN totalChapters INTEGER DEFAULT 0
`);
db.runSync(`
ALTER TABLE Novel
ADD COLUMN lastReadAt TEXT
`);
db.runSync(`
ALTER TABLE Novel
ADD COLUMN lastUpdatedAt TEXT
`);
if (!columnExists(db, 'Novel', 'chaptersDownloaded')) {
db.runSync(`
ALTER TABLE Novel
ADD COLUMN chaptersDownloaded INTEGER DEFAULT 0
`);
}
if (!columnExists(db, 'Novel', 'chaptersUnread')) {
db.runSync(`
ALTER TABLE Novel
ADD COLUMN chaptersUnread INTEGER DEFAULT 0
`);
}
if (!columnExists(db, 'Novel', 'totalChapters')) {
db.runSync(`
ALTER TABLE Novel
ADD COLUMN totalChapters INTEGER DEFAULT 0
`);
}
if (!columnExists(db, 'Novel', 'lastReadAt')) {
db.runSync(`
ALTER TABLE Novel
ADD COLUMN lastReadAt TEXT
`);
}
if (!columnExists(db, 'Novel', 'lastUpdatedAt')) {
db.runSync(`
ALTER TABLE Novel
ADD COLUMN lastUpdatedAt TEXT
`);
}
db.runSync(`
UPDATE Novel
+2 -2
View File
@@ -1,5 +1,5 @@
import { Migration } from '../types/migration';
import { migration001 } from './001_add_novel_counters';
import { migration002 } from './002_add_novel_counters';
/**
* Registry of all database migrations
@@ -10,4 +10,4 @@ import { migration001 } from './001_add_novel_counters';
* 3. Import and add it to the migrations array below
* 4. Ensure version numbers are sequential
*/
export const migrations: Migration[] = [migration001];
export const migrations: Migration[] = [migration002];
+2 -2
View File
@@ -104,7 +104,7 @@ export const getAllNovelCategories = () =>
export const _restoreCategory = (category: BackupCategory) => {
const d = category.novelIds.map(novelId => [
'INSERT INTO NovelCategory (categoryId, novelId) VALUES (?, ?)',
'INSERT OR IGNORE INTO NovelCategory (categoryId, novelId) VALUES (?, ?)',
[category.id, novelId],
]) as Array<[string, SQLite.SQLiteBindParams]>;
@@ -114,7 +114,7 @@ export const _restoreCategory = (category: BackupCategory) => {
[category.id, category.sort],
],
[
'INSERT INTO Category (id, name, sort) VALUES (?, ?, ?)',
'INSERT OR IGNORE INTO Category (id, name, sort) VALUES (?, ?, ?)',
[category.id, category.name, category.sort],
],
...d,
+46 -26
View File
@@ -22,34 +22,54 @@ export const insertChapters = async (
return;
}
await db
.withTransactionAsync(async () => {
const statement = db.prepareSync(`
INSERT INTO Chapter (path, name, releaseTime, novelId, chapterNumber, page, position)
VALUES (?, ?, ?, ${novelId}, ?, ?, ?)
ON CONFLICT(path, novelId) DO UPDATE SET
page = excluded.page,
position = excluded.position,
name = excluded.name,
releaseTime = excluded.releaseTime,
chapterNumber = excluded.chapterNumber;
`);
try {
chapters.map((chapter, index) =>
statement.executeSync(
chapter.path,
chapter.name ?? 'Chapter ' + (index + 1),
chapter.releaseTime || '',
chapter.chapterNumber || null,
chapter.page || '1',
index,
),
await db.withExclusiveTransactionAsync(async tx => {
for (let index = 0; index < chapters.length; index++) {
const chapter = chapters[index];
const chapterName = chapter.name ?? 'Chapter ' + (index + 1);
const chapterPage = chapter.page || '1';
const result = await tx.runAsync(
`
INSERT INTO Chapter (path, name, releaseTime, novelId, chapterNumber, page, position)
SELECT ?, ?, ?, ?, ?, ?, ?
WHERE NOT EXISTS (SELECT id FROM Chapter WHERE path = ? AND novelId = ?);
`,
chapter.path,
chapterName,
chapter.releaseTime || '',
novelId,
chapter.chapterNumber || null,
chapterPage,
index,
chapter.path,
novelId,
);
const insertId = result.lastInsertRowId;
if (!insertId || insertId < 0) {
await tx.runAsync(
`
UPDATE Chapter SET
page = ?, position = ?, name = ?, releaseTime = ?, chapterNumber = ?
WHERE path = ? AND novelId = ? AND (page != ? OR position != ? OR name != ? OR releaseTime != ? OR chapterNumber != ?);
`,
chapterPage,
index,
chapterName,
chapter.releaseTime || '',
chapter.chapterNumber || null,
chapter.path,
novelId,
chapterPage,
index,
chapterName,
chapter.releaseTime || '',
chapter.chapterNumber || null,
);
} finally {
statement.finalizeSync();
}
})
.catch();
}
});
};
export const markChapterRead = (chapterId: number) =>
+1 -1
View File
@@ -26,7 +26,7 @@ export const insertNovelAndChapters = async (
sourceNovel: SourceNovel,
): Promise<number | undefined> => {
const insertNovelQuery =
'INSERT INTO Novel (path, pluginId, name, cover, summary, author, artist, status, genres, totalPages) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
'INSERT OR IGNORE INTO Novel (path, pluginId, name, cover, summary, author, artist, status, genres, totalPages) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
const novelId: number | undefined = db.runSync(insertNovelQuery, [
sourceNovel.path,
pluginId,
+1 -1
View File
@@ -17,7 +17,7 @@ export const createCategoryTriggerQuery = `
// if category with id = 2 exists, nothing in db.ts file is executed
export const createCategoryDefaultQuery = `
INSERT INTO Category (id, name, sort) VALUES
INSERT OR IGNORE INTO Category (id, name, sort) VALUES
(1, "${getString('categories.default')}", 1),
(2, "${getString('categories.local')}", 2)
`;
+25 -1
View File
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import { getCategoriesFromDb } from '@database/queries/CategoryQueries';
@@ -9,6 +9,8 @@ import { Category, NovelInfo } from '@database/types';
import { useLibrarySettings } from '@hooks/persisted';
import { LibrarySortOrder } from '../constants/constants';
import { switchNovelToLibraryQuery } from '@database/queries/NovelQueries';
import ServiceManager, { BackgroundTask } from '@services/ServiceManager';
import { useMMKVObject } from 'react-native-mmkv';
// type Library = Category & { novels: LibraryNovelInfo[] };
export type ExtendedCategory = Category & { novelIds: number[] };
@@ -104,6 +106,28 @@ export const useLibrary = (): UseLibraryReturnType => {
getLibrary();
});
const [taskQueue] = useMMKVObject<BackgroundTask[]>(
ServiceManager.manager.STORE_KEY,
);
const restoreTasksCount = useMemo(
() =>
taskQueue?.filter(
t =>
t.name === 'LOCAL_RESTORE' ||
t.name === 'DRIVE_RESTORE' ||
t.name === 'SELF_HOST_RESTORE',
).length || 0,
[taskQueue],
);
const prevRestoreTasksCountRef = useRef(restoreTasksCount);
useEffect(() => {
if (prevRestoreTasksCountRef.current > 0 && restoreTasksCount === 0) {
getLibrary();
}
prevRestoreTasksCountRef.current = restoreTasksCount;
}, [restoreTasksCount]);
return {
library,
categories,
@@ -329,7 +329,7 @@ const NovelScreenList = ({
ListFooterComponent={
!fetching ? (
hasMultiplePages ? (
<View style={styles.pageNavigationWrapper}>
<View>
<PagePaginationControl
currentPage={pageIndex + 1}
totalPages={pages.length}
@@ -5,10 +5,7 @@ import { useBoolean } from '@hooks';
import { BackupSettingsScreenProps } from '@navigators/types';
import GoogleDriveModal from './Components/GoogleDriveModal';
import SelfHostModal from './Components/SelfHostModal';
import {
createBackup as createLocalBackup,
restoreBackup as restoreLocalBackup,
} from '@services/backup/local';
import ServiceManager from '@services/ServiceManager';
import { ScrollView } from 'react-native-gesture-handler';
import { getString } from '@strings/translations';
import { StyleSheet } from 'react-native';
@@ -58,13 +55,17 @@ const BackupSettings = ({ navigation }: BackupSettingsScreenProps) => {
<List.Item
title={getString('backupScreen.createBackup')}
description={getString('backupScreen.createBackupDesc')}
onPress={createLocalBackup}
onPress={() => {
ServiceManager.manager.addTask({ name: 'LOCAL_BACKUP' });
}}
theme={theme}
/>
<List.Item
title={getString('backupScreen.restoreBackup')}
description={getString('backupScreen.restoreBackupDesc')}
onPress={restoreLocalBackup}
onPress={() => {
ServiceManager.manager.addTask({ name: 'LOCAL_RESTORE' });
}}
theme={theme}
/>
<List.InfoItem
+33 -10
View File
@@ -12,6 +12,7 @@ import {
SelfHostData,
selfHostRestore,
} from './backup/selfhost';
import { createBackup, restoreBackup } from './backup/local';
import { migrateNovel, MigrateNovelData } from './migrate/migrateNovel';
import { downloadChapter } from './download/downloadChapter';
import { askForPostNotificationsPermission } from '@utils/askForPostNoftificationsPermission';
@@ -23,6 +24,8 @@ type taskNames =
| 'DRIVE_RESTORE'
| 'SELF_HOST_BACKUP'
| 'SELF_HOST_RESTORE'
| 'LOCAL_BACKUP'
| 'LOCAL_RESTORE'
| 'MIGRATE_NOVEL'
| 'DOWNLOAD_CHAPTER';
@@ -45,6 +48,8 @@ export type BackgroundTask =
| { name: 'DRIVE_RESTORE'; data: DriveFile }
| { name: 'SELF_HOST_BACKUP'; data: SelfHostData }
| { name: 'SELF_HOST_RESTORE'; data: SelfHostData }
| { name: 'LOCAL_BACKUP' }
| { name: 'LOCAL_RESTORE' }
| { name: 'MIGRATE_NOVEL'; data: MigrateNovelData }
| DownloadChapterTask;
export type DownloadChapterTask = {
@@ -233,6 +238,10 @@ export default class ServiceManager {
return createSelfHostBackup(task.task.data, this.setMeta.bind(this));
case 'SELF_HOST_RESTORE':
return selfHostRestore(task.task.data, this.setMeta.bind(this));
case 'LOCAL_BACKUP':
return createBackup(this.setMeta.bind(this));
case 'LOCAL_RESTORE':
return restoreBackup(this.setMeta.bind(this));
case 'MIGRATE_NOVEL':
return migrateNovel(task.task.data, this.setMeta.bind(this));
case 'DOWNLOAD_CHAPTER':
@@ -250,6 +259,8 @@ export default class ServiceManager {
'DRIVE_RESTORE': 0,
'SELF_HOST_BACKUP': 0,
'SELF_HOST_RESTORE': 0,
'LOCAL_BACKUP': 0,
'LOCAL_RESTORE': 0,
'MIGRATE_NOVEL': 0,
'DOWNLOAD_CHAPTER': 0,
};
@@ -286,7 +297,7 @@ export default class ServiceManager {
if (manager.getTaskList().length === 0) {
await Notifications.scheduleNotificationAsync({
content: {
title: 'Background tasks done',
title: getString('common.done'),
body: Object.keys(doneTasks)
.filter(key => doneTasks[key as BackgroundTask['name']] > 0)
.map(
@@ -305,24 +316,36 @@ export default class ServiceManager {
getTaskName(task: BackgroundTask) {
switch (task.name) {
case 'DOWNLOAD_CHAPTER':
return 'Download ' + task.data.novelName;
return `${getString('notifications.DOWNLOAD_CHAPTER')}: ${
task.data.novelName
}`;
case 'IMPORT_EPUB':
return 'Import Epub ' + task.data.filename;
return `${getString('notifications.IMPORT_EPUB')}: ${
task.data.filename
}`;
case 'MIGRATE_NOVEL':
return 'Migrate Novel ' + task.data.fromNovel.name;
return `${getString('notifications.MIGRATE_NOVEL')}: ${
task.data.fromNovel.name
}`;
case 'UPDATE_LIBRARY':
if (task.data !== undefined) {
return 'Update Category ' + task.data.categoryName;
return `${getString('notifications.UPDATE_LIBRARY')}: ${
task.data.categoryName
}`;
}
return 'Update Library';
return getString('notifications.UPDATE_LIBRARY');
case 'DRIVE_BACKUP':
return 'Drive Backup';
return getString('notifications.DRIVE_BACKUP');
case 'DRIVE_RESTORE':
return 'Drive Restore';
return getString('notifications.DRIVE_RESTORE');
case 'SELF_HOST_BACKUP':
return 'Self Host Backup';
return getString('notifications.SELF_HOST_BACKUP');
case 'SELF_HOST_RESTORE':
return 'Self Host Restore';
return getString('notifications.SELF_HOST_RESTORE');
case 'LOCAL_BACKUP':
return getString('notifications.LOCAL_BACKUP');
case 'LOCAL_RESTORE':
return getString('notifications.LOCAL_RESTORE');
default:
return 'Unknown Task';
}
+94 -4
View File
@@ -12,17 +12,53 @@ import { ROOT_STORAGE } from '@utils/Storages';
import { ZipBackupName } from '../types';
import NativeFile from '@specs/NativeFile';
import { getString } from '@strings/translations';
import { BackgroundTaskMetadata } from '@services/ServiceManager';
import { sleep } from '@utils/sleep';
export const createBackup = async () => {
export const createBackup = async (
setMeta?: (
transformer: (meta: BackgroundTaskMetadata) => BackgroundTaskMetadata,
) => void,
) => {
try {
setMeta?.(meta => ({
...meta,
isRunning: true,
progress: 0 / 4,
progressText: getString('backupScreen.preparingData'),
}));
await prepareBackupData(CACHE_DIR_PATH);
setMeta?.(meta => ({
...meta,
progress: 1 / 4,
progressText: getString('backupScreen.uploadingDownloadedFiles'),
}));
await sleep(200);
await NativeZipArchive.zip(
ROOT_STORAGE,
CACHE_DIR_PATH + '/' + ZipBackupName.DOWNLOAD,
);
setMeta?.(meta => ({
...meta,
progress: 2 / 4,
progressText: getString('backupScreen.uploadingData'),
}));
await sleep(200);
await NativeZipArchive.zip(CACHE_DIR_PATH, CACHE_DIR_PATH + '.zip');
setMeta?.(meta => ({
...meta,
progress: 3 / 4,
progressText: getString('backupScreen.savingBackup'),
}));
const datetime = dayjs().format('YYYY-MM-DD_HH_mm');
const fileName = 'lnreader_backup_' + datetime + '.zip';
@@ -33,14 +69,35 @@ export const createBackup = async () => {
fileName,
});
setMeta?.(meta => ({
...meta,
progress: 4 / 4,
isRunning: false,
}));
showToast(getString('backupScreen.backupCreated'));
} catch (error: any) {
setMeta?.(meta => ({
...meta,
isRunning: false,
}));
showToast(error.message);
}
};
export const restoreBackup = async () => {
export const restoreBackup = async (
setMeta?: (
transformer: (meta: BackgroundTaskMetadata) => BackgroundTaskMetadata,
) => void,
) => {
try {
setMeta?.(meta => ({
...meta,
isRunning: true,
progress: 0 / 4,
progressText: getString('backupScreen.downloadingData'),
}));
const [result] = await pick({
mode: 'import',
type: [types.zip],
@@ -66,19 +123,52 @@ export const restoreBackup = async () => {
const localPath = localRes.localUri.replace(/^file:(\/\/)?\//, '/');
// TODO: error handling
setMeta?.(meta => ({
...meta,
progress: 1 / 4,
progressText: getString('backupScreen.restoringData'),
}));
await sleep(200);
await NativeZipArchive.unzip(localPath, CACHE_DIR_PATH);
setMeta?.(meta => ({
...meta,
progress: 2 / 4,
progressText: getString('backupScreen.restoringData'),
}));
await sleep(200);
await restoreData(CACHE_DIR_PATH);
setMeta?.(meta => ({
...meta,
progress: 3 / 4,
progressText: getString('backupScreen.downloadingDownloadedFiles'),
}));
await sleep(200);
// TODO: unlink here too?
await NativeZipArchive.unzip(
CACHE_DIR_PATH + '/' + ZipBackupName.DOWNLOAD,
ROOT_STORAGE,
);
await restoreData(CACHE_DIR_PATH);
setMeta?.(meta => ({
...meta,
progress: 4 / 4,
isRunning: false,
}));
showToast(getString('backupScreen.backupRestored'));
} catch (error: any) {
setMeta?.(meta => ({
...meta,
isRunning: false,
}));
showToast(error.message);
}
};
+28 -34
View File
@@ -69,30 +69,25 @@ const updateNovelChapters = (
chapterNumber,
} = chapters[position];
const chapterPage = page || customPage || '1';
const st = await tx.prepareAsync(
const result = await tx.runAsync(
`
INSERT INTO Chapter (path, name, releaseTime, novelId, updatedTime, chapterNumber, page, position)
SELECT ?, ?, ?, ?, datetime('now','localtime'), ?, ?, ?
WHERE NOT EXISTS (SELECT id FROM Chapter WHERE path = ? AND novelId = ?);
`,
path,
name,
releaseTime || null,
novelId,
chapterNumber || null,
chapterPage,
position,
path,
novelId,
);
let insertId = -1;
try {
const result = await st.executeAsync([
path,
name,
releaseTime || null,
novelId,
chapterNumber || null,
chapterPage,
position,
path,
novelId,
]);
insertId = result.lastInsertRowId;
} finally {
await st.finalizeAsync();
}
const insertId = result.lastInsertRowId;
if (insertId && insertId >= 0) {
if (downloadNewChapters) {
@@ -108,26 +103,25 @@ const updateNovelChapters = (
} else {
await tx.runAsync(
`
UPDATE Chapter SET
name = ?, releaseTime = ?, updatedTime = datetime('now','localtime'), page = ?, position = ?
WHERE path = ? AND novelId = ? AND (name != ? OR releaseTime != ? OR page != ? OR position != ?);
`,
[
name,
releaseTime || null,
chapterPage,
position,
path,
novelId,
name,
releaseTime || null,
chapterPage,
position,
],
UPDATE Chapter SET
name = ?, releaseTime = ?, updatedTime = datetime('now','localtime'), page = ?, position = ?
WHERE path = ? AND novelId = ? AND (name != ? OR releaseTime != ? OR page != ? OR position != ?);
`,
name,
releaseTime || null,
chapterPage,
position,
path,
novelId,
name,
releaseTime || null,
chapterPage,
position,
);
}
}
});
export interface UpdateNovelOptions {
downloadNewChapters?: boolean;
refreshNovelMetadata?: boolean;
+18 -15
View File
@@ -74,24 +74,25 @@
"backupName": "Backup name",
"backupCreated": "Backup created successfully",
"backupRestored": "Backup restored successfully",
"savingBackup": "Saving Backup",
"categoriesRestored": "Restored %{count} categories",
"createBackup": "Create backup",
"createBackupDesc": "Can be used to restore current library",
"createBackupWarning": "Create backup may not work on devices with Android 9 or lower.",
"downloadingData": "Downloading Data",
"downloadingDownloadedFiles": "Downloading Downloaded files",
"downloadingDownloadedFiles": "Downloading Downloaded Files",
"failed": "Backup failed",
"novelsRestored": "Restored %{count} novels",
"restoringCategories": "Restoring categories...",
"restoringNovels": "Restoring novels...",
"restoringSettings": "Restoring settings...",
"restoringCategories": "Restoring Categories",
"restoringNovels": "Restoring Novels",
"restoringSettings": "Restoring Settings",
"settingsRestored": "Settings restored",
"drive": {
"backup": "Drive Backup",
"backupInterruped": "Drive Backup Interruped",
"backupInterruped": "Drive Backup Interrupted",
"googleDriveBackup": "Google Drive Backup",
"restore": "Drive Restore",
"restoreInterruped": "Drive Restore Interruped"
"restoreInterruped": "Drive Restore Interrupted"
},
"googeDrive": "Googe Drive",
"googeDriveDesc": "Backup to your Google Drive",
@@ -113,7 +114,7 @@
"selfHost": "Self Host",
"selfHostDesc": "Backup to your server",
"uploadingData": "Uploading Data",
"uploadingDownloadedFiles": "Uploading Downloaded files"
"uploadingDownloadedFiles": "Uploading Downloaded Files"
},
"browse": "Browse",
"browseScreen": {
@@ -545,13 +546,15 @@
"complete": "Complete"
},
"notifications": {
"IMPORT_EPUB": "Imported epub",
"UPDATE_LIBRARY": "Updated library",
"DRIVE_BACKUP": "Drive backup",
"DRIVE_RESTORE": "Drive restore",
"SELF_HOST_BACKUP": "Self host backup",
"SELF_HOST_RESTORE": "Self host restore",
"MIGRATE_NOVEL": "Migrated novel",
"DOWNLOAD_CHAPTER": "Downloaded chapter"
"IMPORT_EPUB": "Importing EPUB",
"UPDATE_LIBRARY": "Updating Library",
"DRIVE_BACKUP": "Google Drive Backup",
"DRIVE_RESTORE": "Google Drive Restore",
"SELF_HOST_BACKUP": "Self-Host Backup",
"SELF_HOST_RESTORE": "Self-Host Restore",
"LOCAL_BACKUP": "Local Backup",
"LOCAL_RESTORE": "Local Restore",
"MIGRATE_NOVEL": "Migrating Novel",
"DOWNLOAD_CHAPTER": "Downloading Chapter"
}
}
+3
View File
@@ -67,6 +67,7 @@ export interface StringMap {
'backupScreen.backupName': 'string';
'backupScreen.backupCreated': 'string';
'backupScreen.backupRestored': 'string';
'backupScreen.savingBackup': 'string';
'backupScreen.categoriesRestored': 'string';
'backupScreen.createBackup': 'string';
'backupScreen.createBackupDesc': 'string';
@@ -452,6 +453,8 @@ export interface StringMap {
'notifications.DRIVE_RESTORE': 'string';
'notifications.SELF_HOST_BACKUP': 'string';
'notifications.SELF_HOST_RESTORE': 'string';
'notifications.LOCAL_BACKUP': 'string';
'notifications.LOCAL_RESTORE': 'string';
'notifications.MIGRATE_NOVEL': 'string';
'notifications.DOWNLOAD_CHAPTER': 'string';
}