├── .watchmanconfig
├── .eslintignore
├── android
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── play
│ │ │ │ ├── contact-email.txt
│ │ │ │ ├── listings
│ │ │ │ │ ├── en-US
│ │ │ │ │ │ ├── title.txt
│ │ │ │ │ │ ├── short-description.txt
│ │ │ │ │ │ ├── graphics
│ │ │ │ │ │ │ ├── icon
│ │ │ │ │ │ │ │ └── icon.png
│ │ │ │ │ │ │ └── phone-screenshots
│ │ │ │ │ │ │ │ ├── 1.png
│ │ │ │ │ │ │ │ ├── 2.png
│ │ │ │ │ │ │ │ ├── 3.png
│ │ │ │ │ │ │ │ ├── 4.png
│ │ │ │ │ │ │ │ ├── 5.png
│ │ │ │ │ │ │ │ ├── 6.png
│ │ │ │ │ │ │ │ ├── 7.png
│ │ │ │ │ │ │ │ ├── 8.png
│ │ │ │ │ │ │ │ ├── 9.png
│ │ │ │ │ │ │ │ ├── 10.png
│ │ │ │ │ │ │ │ └── 11.png
│ │ │ │ │ │ └── full-description.txt
│ │ │ │ │ └── zh-CN
│ │ │ │ │ │ ├── title.txt
│ │ │ │ │ │ ├── short-description.txt
│ │ │ │ │ │ ├── graphics
│ │ │ │ │ │ ├── icon
│ │ │ │ │ │ │ └── icon.png
│ │ │ │ │ │ └── phone-screenshots
│ │ │ │ │ │ │ ├── 1.png
│ │ │ │ │ │ │ ├── 2.png
│ │ │ │ │ │ │ ├── 3.png
│ │ │ │ │ │ │ ├── 4.png
│ │ │ │ │ │ │ ├── 5.png
│ │ │ │ │ │ │ ├── 6.png
│ │ │ │ │ │ │ ├── 7.png
│ │ │ │ │ │ │ ├── 8.png
│ │ │ │ │ │ │ ├── 9.png
│ │ │ │ │ │ │ └── 10.png
│ │ │ │ │ │ └── full-description.txt
│ │ │ │ ├── contact-website.txt
│ │ │ │ └── release-notes
│ │ │ │ │ └── en-US
│ │ │ │ │ └── default.txt
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ └── ic_launcher.xml
│ │ │ │ └── drawable
│ │ │ │ │ └── rn_edit_text_material.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── gmail
│ │ │ │ └── blandilyte
│ │ │ │ └── silverdict
│ │ │ │ ├── MainApplication.kt
│ │ │ │ └── MainActivity.kt
│ │ └── debug
│ │ │ └── AndroidManifest.xml
│ ├── debug.keystore
│ ├── proguard-rules.pro
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── settings.gradle
├── build.gradle
├── gradle.properties
└── gradlew.bat
├── .bundle
└── config
├── app.json
├── tsconfig.json
├── ios
├── SilverDict
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── AppDelegate.h
│ ├── main.m
│ ├── AppDelegate.mm
│ ├── PrivacyInfo.xcprivacy
│ ├── Info.plist
│ └── LaunchScreen.storyboard
├── .xcode.env
├── SilverDictTests
│ ├── Info.plist
│ └── SilverDictTests.m
├── Podfile
└── SilverDict.xcodeproj
│ └── xcshareddata
│ └── xcschemes
│ └── SilverDict.xcscheme
├── babel.config.js
├── crowdin.yml
├── metro.config.js
├── Gemfile
├── src
├── translations
│ ├── l10n.js
│ ├── zh-CN.json
│ ├── zh-TW.json
│ ├── ar-SA.json
│ ├── en-US.json
│ ├── es-ES.json
│ ├── de-DE.json
│ ├── el-GR.json
│ └── en-GB.json
├── components
│ ├── LibraryScreen
│ │ ├── GroupManager
│ │ │ ├── GroupDictionaryEditorDialogueItem.js
│ │ │ ├── ConfirmDeleteDialogue.js
│ │ │ ├── GroupNameEditorDialogue.js
│ │ │ ├── GroupLanguageEditorDialogue.js
│ │ │ ├── GroupDictionaryEditorDialogue.js
│ │ │ ├── AddGroupDialogue.js
│ │ │ └── GroupCard.js
│ │ ├── DictionaryManager
│ │ │ ├── EditMenu.js
│ │ │ ├── ConfirmDeleteDialogue.js
│ │ │ ├── DictionaryNameEditorDialogue.js
│ │ │ └── AddDictionaryDialogue.js
│ │ ├── LibraryNavigationBar.js
│ │ ├── SourceManager.js
│ │ ├── GroupManager.js
│ │ ├── SourceManager
│ │ │ ├── ConfirmDeleteDialogue.js
│ │ │ └── AddSourceDialogue.js
│ │ ├── LibraryAppbar.js
│ │ └── DictionaryManager.js
│ ├── common
│ │ ├── InProgressDialogue.js
│ │ ├── ConfirmDialogue.js
│ │ └── TextEditorDialogue.js
│ ├── QueryScreen.js
│ ├── QueryScreen
│ │ ├── QueryBar.js
│ │ ├── Input.js
│ │ ├── QueryContent.js
│ │ ├── DictionarySelection.js
│ │ └── ArticleBottomBar.js
│ ├── SettingsScreen
│ │ ├── ChangeColourDialogue.js
│ │ ├── ConfirmClearHistoryDialogue.js
│ │ ├── ChangeFontDialogue.js
│ │ ├── ChangeAddressDialogue.js
│ │ ├── ConfirmRecreateNgramDialogue.js
│ │ ├── ChangeSizeSuggestionDialogue.js
│ │ ├── ChangeSizeHistoryDialogue.js
│ │ ├── AdditionalFontsDialogue.js
│ │ └── DarkReaderConfigDialogue.js
│ ├── LibraryScreen.js
│ ├── DrawerContent.js
│ └── SettingsScreen.js
├── App.js
├── utils.js
├── config.js
└── AppContext.js
├── .eslintrc.json
├── index.js
├── .gitignore
├── package.json
└── README.md
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /src/components/QueryScreen/darkreader.js
--------------------------------------------------------------------------------
/android/app/src/main/play/contact-email.txt:
--------------------------------------------------------------------------------
1 | blandilyte@gmail.com
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/title.txt:
--------------------------------------------------------------------------------
1 | SilverDict
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/title.txt:
--------------------------------------------------------------------------------
1 | SilverDict
--------------------------------------------------------------------------------
/.bundle/config:
--------------------------------------------------------------------------------
1 | BUNDLE_PATH: "vendor/bundle"
2 | BUNDLE_FORCE_RUBY_PLATFORM: 1
3 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SilverDict",
3 | "displayName": "SilverDict"
4 | }
5 |
--------------------------------------------------------------------------------
/android/app/src/main/play/contact-website.txt:
--------------------------------------------------------------------------------
1 | https://silverdict.lecoteauverdoyant.co.uk/
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@react-native/typescript-config/tsconfig.json"
3 | }
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/short-description.txt:
--------------------------------------------------------------------------------
1 | SilverDict 词典服务器 Android 客户端
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/short-description.txt:
--------------------------------------------------------------------------------
1 | Android client of the SilverDict dictionary server
--------------------------------------------------------------------------------
/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/debug.keystore
--------------------------------------------------------------------------------
/android/app/src/main/play/release-notes/en-US/default.txt:
--------------------------------------------------------------------------------
1 | Allow launching from the text selection menu in other apps.
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SilverDict
3 |
4 |
--------------------------------------------------------------------------------
/ios/SilverDict/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ios/SilverDict/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : RCTAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/icon/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/icon/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:@react-native/babel-preset'],
3 | plugins: [
4 | 'react-native-paper/babel',
5 | 'react-native-reanimated/plugin'
6 | ]
7 | };
8 |
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/8.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/9.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/1.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/2.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/3.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/4.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/5.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/6.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/7.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/8.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/9.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/10.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/11.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crissium/SilverDict-mobile/HEAD/android/app/src/main/play/listings/zh-CN/graphics/phone-screenshots/10.png
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | files:
2 | - source: /src/translations/en-GB.json
3 | translation: /src/translations/%locale%.json
4 | pull_request_title: "l10n: new translations from Crowdin"
5 | commit_message: "l10n: new translations {languageName}"
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/zh-CN/full-description.txt:
--------------------------------------------------------------------------------
1 | 这是 SilverDict 词典服务器的 Android 客户端,是基于 Web 技术的 GoldenDict 替代品。 该移动客户端具有词典应用程序的所有预期功能,包括查找、历史记录、输入建议、词典管理等。
2 |
3 | 您可以在其 GitHub 仓库中阅读有关该服务器的更多信息:https://github.com/Crissium/SilverDict
4 |
--------------------------------------------------------------------------------
/ios/SilverDict/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | @autoreleasepool {
8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'SilverDict'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 | includeBuild('../node_modules/@react-native/gradle-plugin')
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
2 |
3 | /**
4 | * Metro configuration
5 | * https://reactnative.dev/docs/metro
6 | *
7 | * @type {import('metro-config').MetroConfig}
8 | */
9 | const config = {};
10 |
11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config);
12 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | # Cocoapods 1.15 introduced a bug which break the build. We will remove the upper
7 | # bound in the template on Cocoapods with next React Native release.
8 | gem 'cocoapods', '>= 1.13', '< 1.15'
9 | gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
10 |
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/full-description.txt:
--------------------------------------------------------------------------------
1 | This is the Android client of the SilverDict dictionary server, a web-based alternative to GoldenDict. This mobile client has all the expected features of a dictionary app, including lookup, history, suggest-as-you-type, dictionary management, and more.
2 |
3 | You can read more about the server at its repository: https://github.com/Crissium/SilverDict
--------------------------------------------------------------------------------
/src/translations/l10n.js:
--------------------------------------------------------------------------------
1 | import LocalizedStrings from 'react-native-localization';
2 | import { RTL_LANGS } from '../config';
3 |
4 | export const localisedStrings = new LocalizedStrings({
5 | 'en-GB': require('./en-GB.json'),
6 | 'en-US': require('./en-US.json'),
7 | 'ru-RU': require('./ru-RU.json'),
8 | 'zh-CN': require('./zh-CN.json')
9 | });
10 |
11 | export const interfaceLangIsRTL = RTL_LANGS.includes(localisedStrings.getLanguage());
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager/GroupDictionaryEditorDialogueItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Checkbox } from 'react-native-paper';
3 |
4 | export default function GroupDictionaryEditorDialogueItem(props) {
5 | const { name, displayName, included, handleToggle } = props;
6 |
7 | return (
8 | handleToggle(name)} />
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/src/components/common/InProgressDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Dialog, Portal, ProgressBar } from 'react-native-paper';
3 |
4 | export default function InProgressDialogue(props) {
5 | const { visible, title } = props;
6 |
7 | return (
8 |
9 |
18 |
19 | );
20 | }
--------------------------------------------------------------------------------
/src/components/QueryScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { QueryProvider } from './QueryScreen/QueryContext';
4 | import QueryBar from './QueryScreen/QueryBar';
5 | import QueryContent from './QueryScreen/QueryContent';
6 |
7 | export default function QueryScreen({ navigation }) {
8 |
9 | return (
10 |
11 |
12 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | buildToolsVersion = "34.0.0"
4 | minSdkVersion = 23
5 | compileSdkVersion = 34
6 | targetSdkVersion = 34
7 | ndkVersion = "26.1.10909125"
8 | kotlinVersion = "1.9.22"
9 | }
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle")
16 | classpath("com.facebook.react:react-native-gradle-plugin")
17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
18 | }
19 | }
20 |
21 | apply plugin: "com.facebook.react.rootproject"
22 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/DictionaryManager/EditMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Menu } from 'react-native-paper';
3 | import { localisedStrings } from '../../../translations/l10n';
4 |
5 | export default function EditMenu(props) {
6 | const { visible, setVisible, anchor, onEdit, onDelete } = props;
7 |
8 | return (
9 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2020": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "globals": {
9 | "Atomics": "readonly",
10 | "SharedArrayBuffer": "readonly"
11 | },
12 | "parserOptions": {
13 | "ecmaFeatures": {
14 | "jsx": true
15 | },
16 | "ecmaVersion": 14,
17 | "sourceType": "module"
18 | },
19 | "plugins": [
20 | "react"
21 | ],
22 | "rules": {
23 | "indent": [
24 | "error",
25 | "tab"
26 | ],
27 | "linebreak-style": [
28 | "error",
29 | "unix"
30 | ],
31 | "quotes": [
32 | "error",
33 | "single"
34 | ],
35 | "semi": [
36 | "error",
37 | "always"
38 | ],
39 | "no-unused-vars": "off",
40 | "no-inner-declarations": "off",
41 | "no-empty": "off"
42 | }
43 | }
--------------------------------------------------------------------------------
/ios/SilverDictTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/QueryScreen/QueryBar.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Keyboard } from 'react-native';
3 | import { Appbar } from 'react-native-paper';
4 | import Input from './Input';
5 | import DictionarySelection from './DictionarySelection';
6 |
7 | export default function QueryBar(props) {
8 | const [dictionarySelectionVisible, setDictionarySelectionVisible] = useState(false);
9 |
10 | return (
11 |
14 | { Keyboard.dismiss(); props.openDrawer(); }} />
17 |
18 | { Keyboard.dismiss(); setDictionarySelectionVisible(true); }} />
21 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/common/ConfirmDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Dialog, Portal, Text } from 'react-native-paper';
3 | import { localisedStrings } from '../../translations/l10n';
4 |
5 | export default function ConfirmDialogue(props) {
6 | const { visible, setVisible, title, content, onConfirm } = props;
7 |
8 | return (
9 |
10 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/ios/SilverDict/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 |
5 | @implementation AppDelegate
6 |
7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8 | {
9 | self.moduleName = @"SilverDict";
10 | // You can add your custom initial props in the dictionary below.
11 | // They will be passed down to the ViewController used by React Native.
12 | self.initialProps = @{};
13 |
14 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
15 | }
16 |
17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
18 | {
19 | return [self bundleURL];
20 | }
21 |
22 | - (NSURL *)bundleURL
23 | {
24 | #if DEBUG
25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
26 | #else
27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
28 | #endif
29 | }
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/src/components/SettingsScreen/ChangeColourDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TextEditorDialogue from '../common/TextEditorDialogue';
3 | import { localisedStrings } from '../../translations/l10n';
4 |
5 | export default function ChangeColourDialogue(props) {
6 | const { visible, setVisible, darkTextColour, setDarkTextColour } = props;
7 |
8 | function handleSubmit(colourBuffer) {
9 | if (colourBuffer.length > 0) {
10 | setDarkTextColour(colourBuffer);
11 | } else {
12 | setDarkTextColour('white');
13 | }
14 | setVisible(false);
15 | }
16 |
17 | return (
18 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/SettingsScreen/ConfirmClearHistoryDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ConfirmDialogue from '../common/ConfirmDialogue';
3 | import { loadDataFromJsonResponse } from '../../utils';
4 | import { localisedStrings } from '../../translations/l10n';
5 |
6 | export default function ConfirmClearHistoryDialogue(props) {
7 | const { visible, setVisible, apiPrefix, setHistory } = props;
8 |
9 | function handleConfirm() {
10 | fetch(`${apiPrefix}/management/history`, {
11 | method: 'DELETE'
12 | })
13 | .then(loadDataFromJsonResponse)
14 | .then((data) => {
15 | setHistory(data);
16 | setVisible(false);
17 | })
18 | .catch((error) => {
19 | alert(localisedStrings['clear-history-dialogue-message-failure']);
20 | });
21 | }
22 |
23 | return (
24 |
31 | );
32 | }
--------------------------------------------------------------------------------
/src/components/LibraryScreen/LibraryNavigationBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { Appbar, Button, useTheme } from 'react-native-paper';
4 | import { localisedStrings } from '../../translations/l10n';
5 |
6 | export default function LibraryNavigationBar(props) {
7 | const textColour = useTheme().colors.onSurface;
8 | return (
9 |
10 |
11 |
17 |
23 |
29 |
30 |
31 | );
32 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import 'react-native-gesture-handler';
2 | import { AppRegistry, useColorScheme } from 'react-native';
3 | import { PaperProvider, MD2LightTheme, MD2DarkTheme } from 'react-native-paper';
4 | import { name as appName } from './app.json';
5 | import App from './src/App';
6 |
7 | const lightTheme = {
8 | ...MD2LightTheme,
9 | colors: {
10 | ...MD2LightTheme.colors,
11 | primary: 'grey',
12 | accent: '#483d8b',
13 | background: '#ffffff',
14 | surface: '#eaeaea',
15 | error: '#ffb6c1',
16 | onSurface: '#1d1d1d',
17 | }
18 | };
19 |
20 | const darkTheme = {
21 | ...MD2DarkTheme,
22 | colors: {
23 | ...MD2DarkTheme.colors,
24 | primary: 'slategrey',
25 | accent: '#008080',
26 | background: '#000000',
27 | surface: '#111111',
28 | error: '#ffb6c1',
29 | onSurface: '#cccccc',
30 | }
31 | };
32 |
33 | function Main() {
34 | const isDarkMode = useColorScheme() === 'dark';
35 | const theme = isDarkMode ? darkTheme : lightTheme;
36 |
37 | return (
38 |
39 |
40 |
41 | );
42 | }
43 |
44 | AppRegistry.registerComponent(appName, () => Main);
45 |
--------------------------------------------------------------------------------
/ios/SilverDict/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "scale" : "1x",
46 | "size" : "1024x1024"
47 | }
48 | ],
49 | "info" : {
50 | "author" : "xcode",
51 | "version" : 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/SourceManager.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { FlatList } from 'react-native';
3 | import { IconButton, List } from 'react-native-paper';
4 | import ConfirmDeleteDialogue from './SourceManager/ConfirmDeleteDialogue';
5 | import { useAppContext } from '../../AppContext';
6 |
7 | export default function SourceManager() {
8 | const { sources } = useAppContext();
9 | const [sourceToDelete, setSourceToDelete] = useState('');
10 | const [confirmDeleteVisible, setConfirmDeleteVisible] = useState(false);
11 |
12 | function handleDelete(source) {
13 | setSourceToDelete(source);
14 | setConfirmDeleteVisible(true);
15 | }
16 |
17 | return (
18 | <>
19 |
22 | handleDelete(item)} />}
25 | />
26 | }
27 | keyExtractor={(item) => item}
28 | />
29 |
34 | >
35 | );
36 | }
--------------------------------------------------------------------------------
/src/components/SettingsScreen/ChangeFontDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Dialog, Portal, RadioButton } from 'react-native-paper';
3 | import { localisedStrings } from '../../translations/l10n';
4 |
5 | export default function ChangeFontDialogue(props) {
6 | const { visible, setVisible, fontFamily, setFontFamily } = props;
7 |
8 | return (
9 |
10 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/SettingsScreen/ChangeAddressDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TextEditorDialogue from '../common/TextEditorDialogue';
3 | import { loadDataFromJsonResponse } from '../../utils';
4 | import { localisedStrings } from '../../translations/l10n';
5 |
6 | export default function ChangeAddressDialogue(props) {
7 | const { visible, setVisible, setServerAddress } = props;
8 |
9 | function handleSubmit(addressBuffer) {
10 | fetch(`${addressBuffer}/api/validator/test_connection`)
11 | .then(loadDataFromJsonResponse)
12 | .then((data) => {
13 | if (data['success']) {
14 | setServerAddress(addressBuffer);
15 | setVisible(false);
16 | }
17 | })
18 | .catch((error) => {
19 | alert(localisedStrings['change-address-dialogue-message-unable-to-connect']);
20 | });
21 | }
22 |
23 | return (
24 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/ios/SilverDict/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyCollectedDataTypes
6 |
7 |
8 | NSPrivacyAccessedAPITypes
9 |
10 |
11 | NSPrivacyAccessedAPIType
12 | NSPrivacyAccessedAPICategoryFileTimestamp
13 | NSPrivacyAccessedAPITypeReasons
14 |
15 | C617.1
16 |
17 |
18 |
19 | NSPrivacyAccessedAPIType
20 | NSPrivacyAccessedAPICategoryUserDefaults
21 | NSPrivacyAccessedAPITypeReasons
22 |
23 | CA92.1
24 |
25 |
26 |
27 | NSPrivacyAccessedAPIType
28 | NSPrivacyAccessedAPICategorySystemBootTime
29 | NSPrivacyAccessedAPITypeReasons
30 |
31 | 35F9.1
32 |
33 |
34 |
35 | NSPrivacyTracking
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
4 | import { getFocusedRouteNameFromRoute } from '@react-navigation/native';
5 | import LibraryAppbar from './LibraryScreen/LibraryAppbar';
6 | import LibraryNavigationBar from './LibraryScreen/LibraryNavigationBar';
7 | import DictionaryManager from './LibraryScreen/DictionaryManager';
8 | import GroupManager from './LibraryScreen/GroupManager';
9 | import SourceManager from './LibraryScreen/SourceManager';
10 |
11 | const Tab = createMaterialTopTabNavigator();
12 |
13 | export default function LibraryScreen({ navigation, route }) {
14 | const nameActiveTab = getFocusedRouteNameFromRoute(route) ?? 'Dictionaries';
15 | return (
16 |
17 | navigation.openDrawer()} nameActiveTab={nameActiveTab} />
18 | }>
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DraggableFlatList from 'react-native-draggable-flatlist';
3 | import GroupCard from './GroupManager/GroupCard';
4 | import { useAppContext } from '../../AppContext';
5 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../utils';
6 | import { localisedStrings } from '../../translations/l10n';
7 |
8 | export default function GroupManager() {
9 | const { serverAddress, groups, setGroups } = useAppContext();
10 |
11 | function handleReorder(newGroups) {
12 | fetch(`${serverAddress}/api/management/groups`, {
13 | method: 'PUT',
14 | headers: JSON_HEADER,
15 | body: JSON.stringify(newGroups)
16 | })
17 | .then(loadDataFromJsonResponse)
18 | .then((data) => {
19 | setGroups(data);
20 | })
21 | .catch((error) => {
22 | alert(localisedStrings['group-manager-alert-failure-reordering-groups']);
23 | });
24 | }
25 |
26 | return (
27 | handleReorder(data)}
30 | renderItem={({ item, drag }) =>
31 |
36 | }
37 | keyExtractor={(item) => item.name}
38 | />
39 | );
40 | }
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Resolve react_native_pods.rb with node to allow for hoisting
2 | require Pod::Executable.execute_command('node', ['-p',
3 | 'require.resolve(
4 | "react-native/scripts/react_native_pods.rb",
5 | {paths: [process.argv[1]]},
6 | )', __dir__]).strip
7 |
8 | platform :ios, min_ios_version_supported
9 | prepare_react_native_project!
10 |
11 | linkage = ENV['USE_FRAMEWORKS']
12 | if linkage != nil
13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
14 | use_frameworks! :linkage => linkage.to_sym
15 | end
16 |
17 | target 'SilverDict' do
18 | config = use_native_modules!
19 |
20 | use_react_native!(
21 | :path => config[:reactNativePath],
22 | # An absolute path to your application root.
23 | :app_path => "#{Pod::Config.instance.installation_root}/.."
24 | )
25 |
26 | target 'SilverDictTests' do
27 | inherit! :complete
28 | # Pods for testing
29 | end
30 |
31 | post_install do |installer|
32 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
33 | react_native_post_install(
34 | installer,
35 | config[:reactNativePath],
36 | :mac_catalyst_enabled => false,
37 | # :ccache_enabled => true
38 | )
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/SourceManager/ConfirmDeleteDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ConfirmDialogue from '../../common/ConfirmDialogue';
3 | import { useAppContext } from '../../../AppContext';
4 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
5 | import { localisedStrings } from '../../../translations/l10n';
6 |
7 | export default function ConfirmDeleteDialogue(props) {
8 | const { visible, setVisible, source } = props;
9 | const { serverAddress, setSources } = useAppContext();
10 |
11 | function handleDelete() {
12 | fetch(`${serverAddress}/api/management/sources`, {
13 | method: 'DELETE',
14 | headers: JSON_HEADER,
15 | body: JSON.stringify({ source: source })
16 | })
17 | .then(loadDataFromJsonResponse)
18 | .then((data) => {
19 | setSources(data);
20 | })
21 | .catch((error) => {
22 | alert(localisedStrings['source-manager-alert-failure-deleting-source']);
23 | })
24 | .finally(() => {
25 | setVisible(false);
26 | });
27 | }
28 |
29 | return (
30 |
37 | );
38 | }
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager/ConfirmDeleteDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ConfirmDialogue from '../../common/ConfirmDialogue';
3 | import { useAppContext } from '../../../AppContext';
4 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
5 | import { localisedStrings } from '../../../translations/l10n';
6 |
7 | export default function ConfirmDeleteDialogue(props) {
8 | const { visible, setVisible, name } = props;
9 | const { serverAddress, setGroups, setGroupings } = useAppContext();
10 |
11 | function handleDelete() {
12 | fetch(`${serverAddress}/api/management/groups`, {
13 | method: 'DELETE',
14 | headers: JSON_HEADER,
15 | body: JSON.stringify({ name: name })
16 | })
17 | .then(loadDataFromJsonResponse)
18 | .then((data) => {
19 | setGroups(data['groups']);
20 | setGroupings(data['groupings']);
21 | })
22 | .catch((error) => {
23 | alert(localisedStrings['group-manager-alert-failure-removing-group']);
24 | })
25 | .finally(() => {
26 | setVisible(false);
27 | });
28 | }
29 |
30 | return (
31 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # My scripts
2 | release.py
3 |
4 | # OSX
5 | #
6 | .DS_Store
7 |
8 | # Xcode
9 | #
10 | build/
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata
20 | *.xccheckout
21 | *.moved-aside
22 | DerivedData
23 | *.hmap
24 | *.ipa
25 | *.xcuserstate
26 | **/.xcode.env.local
27 |
28 | # Android/IntelliJ
29 | #
30 | build/
31 | .idea
32 | .gradle
33 | local.properties
34 | *.iml
35 | *.hprof
36 | .cxx/
37 | *.keystore
38 | !debug.keystore
39 |
40 | # node.js
41 | #
42 | node_modules/
43 | npm-debug.log
44 | yarn-error.log
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/
52 |
53 | **/fastlane/report.xml
54 | **/fastlane/Preview.html
55 | **/fastlane/screenshots
56 | **/fastlane/test_output
57 |
58 | # Bundle artifact
59 | *.jsbundle
60 |
61 | # Ruby / CocoaPods
62 | **/Pods/
63 | /vendor/bundle/
64 |
65 | # Temporary files created by Metro to check the health of the file watcher
66 | .metro-health-check*
67 |
68 | # testing
69 | /coverage
70 |
71 | # Yarn
72 | .yarn/*
73 | !.yarn/patches
74 | !.yarn/plugins
75 | !.yarn/releases
76 | !.yarn/sdks
77 | !.yarn/versions
78 |
--------------------------------------------------------------------------------
/src/components/DrawerContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { DrawerContentScrollView } from '@react-navigation/drawer';
4 | import { Drawer } from 'react-native-paper';
5 | import { useAppContext } from '../AppContext';
6 | import { localisedStrings } from '../translations/l10n';
7 |
8 | export default function DrawerContent(props) {
9 | const { fetchInitialData, serverAddress, setDictionaries, setGroups, setGroupings, setHistory, setSizeHistory, setSizeSuggestion } = useAppContext();
10 |
11 | return (
12 |
13 |
14 | props.navigation.navigate('Query')}
18 | />
19 | props.navigation.navigate('Library')}
23 | />
24 | props.navigation.navigate('Settings')}
28 | />
29 | fetchInitialData(`${serverAddress}/api`, setDictionaries, setGroups, setGroupings, setHistory, setSizeHistory, setSizeSuggestion)}
33 | />
34 |
35 |
36 | );
37 | }
--------------------------------------------------------------------------------
/src/components/QueryScreen/Input.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { TextInput, useTheme } from 'react-native-paper';
3 | import { isRTL } from '../../utils';
4 | import { useQueryContext } from './QueryContext';
5 | import { localisedStrings } from '../../translations/l10n';
6 |
7 | export default function Input() {
8 | const { query, setQuery, textInputRef, handleInputSubmit, clearArticle } = useQueryContext();
9 |
10 | function handleInputFocus(e) {
11 | clearArticle();
12 | }
13 |
14 | useEffect(function () {
15 | if (isRTL(query))
16 | textInputRef.current.setNativeProps({ style: { direction: 'rtl', textAlign: 'right' } });
17 | else
18 | textInputRef.current.setNativeProps({ style: { direction: 'ltr', textAlign: 'left' } });
19 | }, [query]);
20 |
21 | return (
22 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/QueryScreen/QueryContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FlatList, Keyboard, View } from 'react-native';
3 | import { List } from 'react-native-paper';
4 | import ArticleView from './ArticleView';
5 | import ArticleBottomBar from './ArticleBottomBar';
6 | import { useAppContext } from '../../AppContext';
7 | import { useQueryContext } from './QueryContext';
8 | import { isRTL } from '../../utils';
9 |
10 | function WordItem(props) {
11 | const { word } = props;
12 | const wordIsRTL = isRTL(word);
13 | const { search } = useQueryContext();
14 |
15 | return (
16 | { Keyboard.dismiss(); search(word); }} />
20 | );
21 | }
22 |
23 | export default function QueryContent() {
24 | const { history } = useAppContext();
25 | const { query, article, suggestions } = useQueryContext();
26 | const wordsToDisplay = query.length === 0 ? history : suggestions;
27 |
28 | if (article.length === 0)
29 | return (
30 |
31 | }
35 | keyExtractor={(item) => item}
36 | />
37 |
38 | );
39 | else
40 | return (
41 |
44 |
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/SettingsScreen/ConfirmRecreateNgramDialogue.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import ConfirmDialogue from '../common/ConfirmDialogue';
3 | import InProgressDialogue from '../common/InProgressDialogue';
4 | import { loadDataFromJsonResponse } from '../../utils';
5 | import { localisedStrings } from '../../translations/l10n';
6 |
7 | export default function ConfirmRecreateNgramDialogue(props) {
8 | const { visible, setVisible, apiPrefix } = props;
9 | const [inProgress, setInProgress] = useState(false);
10 |
11 | function handleConfirm() {
12 | setInProgress(true);
13 | fetch(`${apiPrefix}/management/create_ngram_table`)
14 | .then(loadDataFromJsonResponse)
15 | .then((data) => {
16 | if (data['success']) {
17 | alert(localisedStrings['confirm-recreate-ngram-dialogue-message-success']);
18 | }
19 | })
20 | .catch((error) => {
21 | alert(localisedStrings['confirm-recreate-ngram-dialogue-message-failure']);
22 | })
23 | .finally(() => {
24 | setInProgress(false);
25 | setVisible(false);
26 | });
27 | }
28 |
29 | return (
30 | <>
31 |
35 |
42 | >
43 | );
44 | }
--------------------------------------------------------------------------------
/src/components/SettingsScreen/ChangeSizeSuggestionDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TextEditorDialogue from '../common/TextEditorDialogue';
3 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../utils';
4 | import { localisedStrings } from '../../translations/l10n';
5 |
6 | export default function ChangeSizeSuggestionDialogue(props) {
7 | const { visible, setVisible, apiPrefix, sizeSuggestion, setSizeSuggestion } = props;
8 |
9 | function handleSubmit(sizeBuffer) {
10 | let newSize;
11 | try {
12 | newSize = parseInt(sizeBuffer);
13 | newSize = Math.max(newSize, 1);
14 | } catch (error) {
15 | alert(localisedStrings['generic-alert-invalid-number']);
16 | return;
17 | }
18 |
19 | fetch(`${apiPrefix}/management/num_suggestions`, {
20 | method: 'PUT',
21 | headers: JSON_HEADER,
22 | body: JSON.stringify({ size: newSize })
23 | })
24 | .then(loadDataFromJsonResponse)
25 | .then((data) => {
26 | setSizeSuggestion(data['size']);
27 | setVisible(false);
28 | })
29 | .catch((error) => {
30 | alert(localisedStrings['change-size-suggestion-dialogue-message-failure']);
31 | });
32 | }
33 |
34 | return (
35 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/SettingsScreen/ChangeSizeHistoryDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TextEditorDialogue from '../common/TextEditorDialogue';
3 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../utils';
4 | import { localisedStrings } from '../../translations/l10n';
5 |
6 | export default function ChangeSizeHistoryDialogue(props) {
7 | const { visible, setVisible, apiPrefix, setHistory, sizeHistory, setSizeHistory } = props;
8 |
9 | function handleSubmit(sizeBuffer) {
10 | let newSize;
11 | try {
12 | newSize = parseInt(sizeBuffer);
13 | newSize = Math.max(newSize, 0);
14 | } catch (error) {
15 | alert(localisedStrings['generic-alert-invalid-number']);
16 | return;
17 | }
18 |
19 | fetch(`${apiPrefix}/management/history_size`, {
20 | method: 'PUT',
21 | headers: JSON_HEADER,
22 | body: JSON.stringify({ size: newSize })
23 | })
24 | .then(loadDataFromJsonResponse)
25 | .then((data) => {
26 | setHistory(data);
27 | setSizeHistory(newSize);
28 | setVisible(false);
29 | })
30 | .catch((error) => {
31 | alert(localisedStrings['change-size-history-dialogue-message-failure']);
32 | });
33 | }
34 |
35 | return (
36 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/common/TextEditorDialogue.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Dialog, Text, TextInput, Portal } from 'react-native-paper';
3 | import { localisedStrings } from '../../translations/l10n';
4 |
5 | export default function TextEditorDialogue(props) {
6 | const { visible, setVisible, title, content, originalValue, handleSubmit, inputMode, placeholder, autoCapitalize, autoCorrect } = props;
7 | const [buffer, setBuffer] = useState(originalValue);
8 |
9 | return (
10 |
11 |
48 |
49 | );
50 | }
--------------------------------------------------------------------------------
/android/app/src/main/java/com/gmail/blandilyte/silverdict/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package com.gmail.blandilyte.silverdict
2 |
3 | import android.app.Application
4 | import com.facebook.react.PackageList
5 | import com.facebook.react.ReactApplication
6 | import com.facebook.react.ReactHost
7 | import com.facebook.react.ReactNativeHost
8 | import com.facebook.react.ReactPackage
9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
11 | import com.facebook.react.defaults.DefaultReactNativeHost
12 | import com.facebook.soloader.SoLoader
13 |
14 | class MainApplication : Application(), ReactApplication {
15 |
16 | override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) {
17 | override fun getPackages(): List = PackageList(this).packages.apply {
18 | // Packages that cannot be autolinked yet can be added manually here, for example:
19 | // add(MyReactNativePackage())
20 | }
21 |
22 | override fun getJSMainModuleName(): String = "index"
23 |
24 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
25 |
26 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
27 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
28 | }
29 |
30 | override val reactHost: ReactHost
31 | get() = getDefaultReactHost(applicationContext, reactNativeHost)
32 |
33 | override fun onCreate() {
34 | super.onCreate()
35 | SoLoader.init(this, false)
36 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
37 | // If you opted-in for the New Architecture, we load the native entry point for this app.
38 | load()
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/SettingsScreen/AdditionalFontsDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FlatList } from 'react-native';
3 | import { Button, Checkbox, Dialog, Portal, Text } from 'react-native-paper';
4 | import { localisedStrings } from '../../translations/l10n';
5 |
6 | function Item(props) {
7 | const { scriptName, enabled, setEnabled } = props;
8 |
9 | return (
10 | setEnabled(!enabled)}
14 | />
15 | );
16 | }
17 |
18 | export default function AdditionalFontsDialogue(props) {
19 | const { visible, setVisible, scriptsWithAdditionalFonts, setScriptsWithAdditionalFonts } = props;
20 |
21 | return (
22 |
23 |
46 |
47 | );
48 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createDrawerNavigator } from '@react-navigation/drawer';
3 | import { NavigationContainer, DefaultTheme, DarkTheme } from '@react-navigation/native';
4 | import { I18nManager } from 'react-native';
5 | import { useTheme } from 'react-native-paper';
6 | import { AppProvider } from './AppContext';
7 | import { interfaceLangIsRTL } from './translations/l10n';
8 | import QueryScreen from './components/QueryScreen';
9 | import LibraryScreen from './components/LibraryScreen';
10 | import SettingsScreen from './components/SettingsScreen';
11 | import DrawerContent from './components/DrawerContent';
12 |
13 | I18nManager.forceRTL(interfaceLangIsRTL);
14 |
15 | const Drawer = createDrawerNavigator();
16 |
17 | export default function App() {
18 | return (
19 |
20 |
33 | }
36 | >
37 |
41 |
45 |
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager/GroupNameEditorDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TextEditorDialogue from '../../common/TextEditorDialogue';
3 | import { useAppContext } from '../../../AppContext';
4 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
5 | import { localisedStrings } from '../../../translations/l10n';
6 |
7 | export default function GroupNameEditorDialogue(props) {
8 | const { visible, setVisible, name } = props;
9 | const { serverAddress, setGroups, groupings, setGroupings } = useAppContext();
10 |
11 | function handleSubmit(nameBuffer) {
12 | if (nameBuffer.length === 0) {
13 | alert(localisedStrings['group-manager-alert-empty-name']);
14 | return;
15 | }
16 |
17 | if (nameBuffer === name) {
18 | setVisible(false);
19 | return;
20 | }
21 |
22 | if (groupings[nameBuffer]) {
23 | alert(localisedStrings['group-manager-alert-name-exists']);
24 | return;
25 | }
26 |
27 | fetch(`${serverAddress}/api/management/group_name`, {
28 | method: 'PUT',
29 | headers: JSON_HEADER,
30 | body: JSON.stringify({ old: name, new: nameBuffer })
31 | })
32 | .then(loadDataFromJsonResponse)
33 | .then((data) => {
34 | setGroups(data['groups']);
35 | setGroupings(data['groupings']);
36 | setVisible(false);
37 | })
38 | .catch((error) => {
39 | alert(localisedStrings['group-manager-alert-failure-renaming-group']);
40 | });
41 | }
42 |
43 | return (
44 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SilverDict",
3 | "version": "1.5.0",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "start": "react-native start"
9 | },
10 | "dependencies": {
11 | "@react-native-async-storage/async-storage": "^1.23.1",
12 | "@react-native-community/slider": "^4.5.2",
13 | "@react-native-picker/picker": "^2.7.7",
14 | "@react-navigation/drawer": "^6.6.15",
15 | "@react-navigation/material-top-tabs": "^6.6.13",
16 | "@react-navigation/native": "^6.1.17",
17 | "iso-639-1": "^3.1.2",
18 | "react": "18.2.0",
19 | "react-native": "0.74.2",
20 | "react-native-autoheight-webview": "^1.6.5",
21 | "react-native-dialog": "^9.3.0",
22 | "react-native-draggable-flatlist": "^4.0.1",
23 | "react-native-gesture-handler": "^2.16.2",
24 | "react-native-localization": "^2.3.2",
25 | "react-native-pager-view": "^6.3.1",
26 | "react-native-paper": "^5.12.3",
27 | "react-native-picker-select": "^9.1.3",
28 | "react-native-reanimated": "^3.12.0",
29 | "react-native-safe-area-context": "^4.10.5",
30 | "react-native-screens": "^3.31.1",
31 | "react-native-tab-view": "^3.5.2",
32 | "react-native-vector-icons": "^10.1.0",
33 | "react-native-webview": "^13.10.3"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.20.0",
37 | "@babel/preset-env": "^7.20.0",
38 | "@babel/runtime": "^7.20.0",
39 | "@react-native/babel-preset": "0.74.84",
40 | "@react-native/eslint-config": "0.74.84",
41 | "@react-native/metro-config": "0.74.84",
42 | "@react-native/typescript-config": "0.74.84",
43 | "@types/react": "^18.2.6",
44 | "@types/react-test-renderer": "^18.0.0",
45 | "prettier": "2.8.8",
46 | "react-test-renderer": "18.2.0",
47 | "typescript": "5.0.4"
48 | },
49 | "engines": {
50 | "node": ">=18"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ios/SilverDict/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | SilverDict
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 |
30 | NSAllowsArbitraryLoads
31 |
32 | NSAllowsLocalNetworking
33 |
34 |
35 | NSLocationWhenInUseUsageDescription
36 |
37 | UILaunchStoryboardName
38 | LaunchScreen
39 | UIRequiredDeviceCapabilities
40 |
41 | arm64
42 |
43 | UISupportedInterfaceOrientations
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UIViewControllerBasedStatusBarAppearance
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/DictionaryManager/ConfirmDeleteDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useAppContext } from '../../../AppContext';
3 | import { JSON_HEADER, convertDictionarySnakeCaseToCamelCase, loadDataFromJsonResponse } from '../../../utils';
4 | import ConfirmDialogue from '../../common/ConfirmDialogue';
5 | import { localisedStrings } from '../../../translations/l10n';
6 |
7 | export default function ConfirmDeleteDialogue(props) {
8 | const { visible, setVisible, editedDictionaryIndex, setEditedDictionaryIndex } = props;
9 | const { serverAddress, dictionaries, setDictionaries, setGroupings } = useAppContext();
10 |
11 | function onConfirm() {
12 | fetch(`${serverAddress}/api/management/dictionaries`, {
13 | method: 'DELETE',
14 | headers: JSON_HEADER,
15 | body: JSON.stringify({ name: dictionaries[editedDictionaryIndex].name })
16 | })
17 | .then(loadDataFromJsonResponse)
18 | .then((data) => {
19 | setEditedDictionaryIndex(-1); // This has to happen here, otherwise the edited dictionary would be deleted before the dialogue is closed.
20 | setVisible(false);
21 | setDictionaries(data['dictionaries'].map(convertDictionarySnakeCaseToCamelCase));
22 | setGroupings(data['groupings']);
23 | })
24 | .catch((error) => {
25 | setEditedDictionaryIndex(-1);
26 | setVisible(false);
27 | alert(localisedStrings['dictionary-manager-alert-delete-dictionary-failure']);
28 | });
29 | }
30 |
31 | return (
32 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager/GroupLanguageEditorDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ISO6391 from 'iso-639-1';
3 | import TextEditorDialogue from '../../common/TextEditorDialogue';
4 | import { useAppContext } from '../../../AppContext';
5 | import { getSetFromLangString, JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
6 | import { localisedStrings } from '../../../translations/l10n';
7 |
8 | export default function GroupLanguageEditorDialogue(props) {
9 | const { visible, setVisible, name, languagesString } = props;
10 | const { serverAddress, setGroups } = useAppContext();
11 |
12 | function handleSubmit(languagesBufferString) {
13 | const newLangs = getSetFromLangString(languagesBufferString);
14 | for (const lang of newLangs) {
15 | if (!ISO6391.validate(lang)) {
16 | alert(localisedStrings['group-manager-alert-invalid-language-code']);
17 | return;
18 | }
19 | }
20 |
21 | fetch(`${serverAddress}/api/management/group_lang`, {
22 | method: 'PUT',
23 | headers: JSON_HEADER,
24 | body: JSON.stringify({ name: name, lang: Array.from(newLangs) })
25 | })
26 | .then(loadDataFromJsonResponse)
27 | .then((data) => {
28 | setGroups(data);
29 | setVisible(false);
30 | })
31 | .catch((error) => {
32 | alert(localisedStrings['group-manager-alert-failure-changing-languages']);
33 | });
34 | }
35 |
36 | return (
37 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/DictionaryManager/DictionaryNameEditorDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useAppContext } from '../../../AppContext';
3 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
4 | import TextEditorDialogue from '../../common/TextEditorDialogue';
5 | import { localisedStrings } from '../../../translations/l10n';
6 |
7 | export default function DictionaryNameEditorDialogue(props) {
8 | const { visible, setVisible, editedDictionaryIndex, setEditedDictionaryIndex } = props;
9 | const { serverAddress, dictionaries, setDictionaries } = useAppContext();
10 |
11 | function handleSubmit(nameBuffer) {
12 | const newDisplayName = nameBuffer.length > 0 ? nameBuffer : dictionaries[editedDictionaryIndex].displayName;
13 | fetch(`${serverAddress}/api/management/dictionary_name`, {
14 | method: 'PUT',
15 | headers: JSON_HEADER,
16 | body: JSON.stringify({ name: dictionaries[editedDictionaryIndex].name, display: newDisplayName })
17 | })
18 | .then(loadDataFromJsonResponse)
19 | .then((data) => {
20 | if (data['success']) {
21 | let newDictionaries = [...dictionaries];
22 | newDictionaries[editedDictionaryIndex].displayName = newDisplayName;
23 | setDictionaries(newDictionaries);
24 | } else {
25 | alert(localisedStrings['dictionary-manager-alert-edit-name-failure']);
26 | }
27 | setEditedDictionaryIndex(-1);
28 | setVisible(false);
29 | });
30 | }
31 |
32 | return (
33 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 |
27 | # Use this property to specify which architecture you want to build.
28 | # You can also override it from the CLI using
29 | # ./gradlew -PreactNativeArchitectures=x86_64
30 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
31 |
32 | # Use this property to enable support to the new architecture.
33 | # This will allow you to use TurboModules and the Fabric render in
34 | # your application. You should enable this flag either if you want
35 | # to write custom TurboModules/Fabric components OR use libraries that
36 | # are providing them.
37 | newArchEnabled=false
38 |
39 | # Use this property to enable or disable the Hermes JS engine.
40 | # If set to false, you will be using JSC instead.
41 | hermesEnabled=true
42 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/SourceManager/AddSourceDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TextEditorDialogue from '../../common/TextEditorDialogue';
3 | import { useAppContext } from '../../../AppContext';
4 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
5 | import { localisedStrings } from '../../../translations/l10n';
6 |
7 | export default function AddSourceDialogue(props) {
8 | const { serverAddress, sources, setSources } = useAppContext();
9 | const { visible, setVisible } = props;
10 |
11 | function handleSubmit(newSource) {
12 | if (newSource.length === 0) {
13 | alert(localisedStrings['source-manager-alert-empty-source']);
14 | return;
15 | }
16 |
17 | if (sources.includes(newSource)) {
18 | alert(localisedStrings['source-manager-alert-duplicate']);
19 | return;
20 | }
21 |
22 | fetch(`${serverAddress}/api/validator/source`, {
23 | method: 'POST',
24 | headers: JSON_HEADER,
25 | body: JSON.stringify({ source: newSource })
26 | })
27 | .then(loadDataFromJsonResponse)
28 | .then((data) => {
29 | if (data['valid']) {
30 | fetch(`${serverAddress}/api/management/sources`, {
31 | method: 'POST',
32 | headers: JSON_HEADER,
33 | body: JSON.stringify({ source: newSource })
34 | })
35 | .then(loadDataFromJsonResponse)
36 | .then((data) => {
37 | setSources(data);
38 | setVisible(false);
39 | })
40 | .catch((error) => {
41 | alert(localisedStrings['source-manager-alert-failure-adding-source']);
42 | });
43 | } else {
44 | alert(localisedStrings['source-manager-alert-invalid-source']);
45 | }
46 | });
47 | }
48 |
49 | return (
50 |
62 | );
63 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ios/SilverDictTests/SilverDictTests.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import
5 | #import
6 |
7 | #define TIMEOUT_SECONDS 600
8 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
9 |
10 | @interface SilverDictTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation SilverDictTests
15 |
16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test
17 | {
18 | if (test(view)) {
19 | return YES;
20 | }
21 | for (UIView *subview in [view subviews]) {
22 | if ([self findSubviewInView:subview matching:test]) {
23 | return YES;
24 | }
25 | }
26 | return NO;
27 | }
28 |
29 | - (void)testRendersWelcomeScreen
30 | {
31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
33 | BOOL foundElement = NO;
34 |
35 | __block NSString *redboxError = nil;
36 | #ifdef DEBUG
37 | RCTSetLogFunction(
38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
39 | if (level >= RCTLogLevelError) {
40 | redboxError = message;
41 | }
42 | });
43 | #endif
44 |
45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
48 |
49 | foundElement = [self findSubviewInView:vc.view
50 | matching:^BOOL(UIView *view) {
51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
52 | return YES;
53 | }
54 | return NO;
55 | }];
56 | }
57 |
58 | #ifdef DEBUG
59 | RCTSetLogFunction(RCTDefaultLogFunction);
60 | #endif
61 |
62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 | import { interfaceLangIsRTL } from './translations/l10n';
3 |
4 | export const JSON_CONTENT_TYPE = 'application/json';
5 |
6 | export const JSON_HEADER = {
7 | 'Content-Type': JSON_CONTENT_TYPE
8 | };
9 |
10 | export function loadDataFromJsonResponse(response) {
11 | return response.json();
12 | }
13 |
14 | export function convertDictionarySnakeCaseToCamelCase(dictionary) {
15 | return {
16 | displayName: dictionary.dictionary_display_name,
17 | name: dictionary.dictionary_name,
18 | format: dictionary.dictionary_format,
19 | filename: dictionary.dictionary_filename
20 | };
21 | }
22 |
23 | export function convertDictionaryCamelCaseToSnakeCase(dictionary) {
24 | return {
25 | dictionary_display_name: dictionary.displayName,
26 | dictionary_name: dictionary.name,
27 | dictionary_format: dictionary.format,
28 | dictionary_filename: dictionary.filename
29 | };
30 | }
31 |
32 | export async function storePersistentData(key, value) {
33 | try {
34 | if (value && value.length > 0 && value !== '""' && value !== '{}') { // empty string and empty object
35 | await AsyncStorage.setItem(key, value);
36 | }
37 | } catch (error) {
38 | alert(`Failed to store persistent data ${key}. Error: ${error}`);
39 | }
40 | }
41 |
42 | export async function loadPersistentData(key, defaultValue) {
43 | try {
44 | const value = await AsyncStorage.getItem(key);
45 | if (value !== null) {
46 | return value;
47 | } else {
48 | return defaultValue;
49 | }
50 | } catch (error) {
51 | alert(`Failed to load persistent data ${key}. Error: ${error}`);
52 | }
53 | }
54 |
55 | export function getSetFromLangString(lang) {
56 | lang = lang.replace(/\s/g, '');
57 | lang = lang.toLowerCase();
58 | const set = new Set();
59 | for (const l of lang.split(',')) {
60 | set.add(l);
61 | }
62 | return set;
63 | }
64 |
65 | export function isRTL(s) {
66 | if (interfaceLangIsRTL) {
67 | return false; // RTL + RTL = weird RTL
68 | }
69 |
70 | var ltrChars =
71 | 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
72 | '\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
73 | var rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
74 | var rtlDirCheck = new RegExp('^[^' + ltrChars + ']*[' + rtlChars + ']');
75 |
76 | return rtlDirCheck.test(s);
77 | }
--------------------------------------------------------------------------------
/src/components/SettingsScreen/DarkReaderConfigDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { Button, Dialog, Portal, Switch, Text } from 'react-native-paper';
4 | import Slider from '@react-native-community/slider';
5 | import { useAppContext } from '../../AppContext';
6 | import { localisedStrings } from '../../translations/l10n';
7 |
8 | export default function DarkReaderConfigDialogue(props) {
9 | const { visible, setVisible } = props;
10 | const { darkReaderEnabled, setDarkReaderEnabled, darkReaderBrightness, setDarkReaderBrightness, darkReaderContrast, setDarkReaderContrast, darkReaderSepia, setDarkReaderSepia } = useAppContext();
11 |
12 | return (
13 |
14 |
61 |
62 | );
63 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | [](https://crowdin.com/project/silverdict-mobile)
4 |
5 | [
](https://f-droid.org/packages/com.gmail.blandilyte.silverdict)
8 |
9 | [Guide](https://github.com/Crissium/SilverDict/wiki/android)
10 |
11 | This is the mobile (Android ~~& iOS~~) client of [SilverDict](https://github.com/Crissium/SilverDict), considered feature-complete now.
12 |
13 | I cannot develop the iOS version because I don't have a Mac or a 99 USD/year Apple Developer account. If you would like to help, please contact me.
14 |
15 | # Roadmap
16 |
17 | ~~Perhaps before I do anything else, I should switch to Kotlin (which would make so many things easier) since I won't develop for iOS.~~
18 |
19 | Kotlin's all Greek to me, so I'll stick with JavaScript for now.
20 |
21 | - [x] Basic UI (Search, History/Suggestion, Settings, etc.)
22 | - [x] Go back/forwards in local history
23 | - [x] Zoom in/out and remember the zoom level
24 | - [x] Find in page
25 | - [x] Tap to search
26 | - [x] Dictionary/Group management
27 | - [ ] Two-column view on wider screens
28 | - [x] Localisation
29 | - [x] Launch SilverDict from context menu (that is, when you select a word in another app, you can choose to search for it in SilverDict. Also this would make it integrate better with e-book readers.)
30 |
31 | ## Issues
32 |
33 | - [ ] Dynamic screen width in WebView (if you rotate from portrait to landscape, the WebView will not be resized)
34 | - [ ] The WebView takes too long to load (1 - 300 ms). When the entire Dark Reader library is loaded, the minimum could be as long as 100 ms.
35 |
36 | # Development
37 |
38 | ## Prerequisites
39 |
40 | - React Native (without Expo), Android SDK, Xcode, etc.
41 | - A working SilverDict server for testing
42 | - React Native Paper
43 | - React Navigation
44 |
45 | ## Build
46 |
47 | ```bash
48 | yarn install
49 | yarn start
50 | yarn android
51 | ```
52 |
53 | ## Note
54 |
55 | The [Dark Reader extension](/src/components/QueryScreen/darkreader.js) is taken from the NPM package, uglified and turned into a string to make it injectable into the WebView.
56 |
57 | There has been a huge update to the web UI recently, which might supersede this project completely, since it doesn't have any Android system integration yet…
58 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager/GroupDictionaryEditorDialogue.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FlatList } from 'react-native';
3 | import { Button, Dialog, Portal } from 'react-native-paper';
4 | import GroupDictionaryEditorDialogueItem from './GroupDictionaryEditorDialogueItem';
5 | import { useAppContext } from '../../../AppContext';
6 | import { JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
7 | import { localisedStrings } from '../../../translations/l10n';
8 |
9 | export default function GroupDictionaryEditorDialogue(props) {
10 | const { visible, setVisible, groupName } = props;
11 | const { serverAddress, dictionaries, groupings, setGroupings } = useAppContext();
12 |
13 | function dictionaryIncluded(dictionaryName) {
14 | return groupings[groupName].includes(dictionaryName);
15 | }
16 |
17 | function handleToggle(dictionaryName) {
18 | const included = dictionaryIncluded(dictionaryName);
19 | const requestMethod = included ? 'DELETE' : 'POST';
20 |
21 | fetch(`${serverAddress}/api/management/dictionary_groupings`, {
22 | method: requestMethod,
23 | headers: JSON_HEADER,
24 | body: JSON.stringify({ dictionary_name: dictionaryName, group_name: groupName })
25 | })
26 | .then(loadDataFromJsonResponse)
27 | .then((data) => {
28 | setGroupings(data);
29 | })
30 | .catch((error) => {
31 | if (included) {
32 | alert(localisedStrings['group-manager-alert-failure-removing-dictionary']);
33 | } else {
34 | alert(localisedStrings['group-manager-alert-failure-adding-dictionary']);
35 | }
36 | });
37 | }
38 |
39 | return (
40 |
41 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/gmail/blandilyte/silverdict/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.gmail.blandilyte.silverdict
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.util.Log
6 | import com.facebook.react.ReactActivity
7 | import com.facebook.react.ReactActivityDelegate
8 | import com.facebook.react.bridge.Arguments
9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
10 | import com.facebook.react.defaults.DefaultReactActivityDelegate
11 | import com.facebook.react.modules.core.DeviceEventManagerModule
12 |
13 | class MainActivity : ReactActivity() {
14 |
15 | override fun getMainComponentName(): String = "SilverDict"
16 |
17 | override fun createReactActivityDelegate(): ReactActivityDelegate =
18 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
19 |
20 | // Define PROCESS_TEXT intent action and send it to the React Native app
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | handleIntent(intent)
24 | }
25 |
26 | override fun onNewIntent(intent: Intent?) {
27 | super.onNewIntent(intent)
28 | handleIntent(intent)
29 | }
30 |
31 | private fun handleIntent(intent: Intent?) {
32 | if (intent?.action == Intent.ACTION_PROCESS_TEXT) {
33 | val selectedText = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)
34 | Log.d("MainActivity", "Selected text: $selectedText")
35 |
36 | if (reactInstanceManager.currentReactContext != null) {
37 | sendTextToReactNative(selectedText.toString())
38 | } else {
39 | Log.d("MainActivity", "ReactContext is null, polling")
40 | pollReactContextAndSendText(selectedText.toString())
41 | }
42 | }
43 | }
44 |
45 | private fun pollReactContextAndSendText(selectedText: String) {
46 | Thread {
47 | while (reactInstanceManager.currentReactContext == null) {
48 | Thread.sleep(50)
49 | }
50 | // The processing code is nested very deep inside the React Native part,
51 | // So sleep for a bit more.
52 | Thread.sleep(150)
53 | sendTextToReactNative(selectedText)
54 | Log.d("MainActivity", "ReactContext is no longer null, text sent")
55 | }.start()
56 | }
57 |
58 | private fun sendTextToReactNative(selectedText: String) {
59 | val params = Arguments.createMap()
60 | params.putString("selectedText", selectedText)
61 |
62 | reactInstanceManager.currentReactContext
63 | ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
64 | ?.emit("onTextSelected", params)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/QueryScreen/DictionarySelection.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FlatList } from 'react-native';
3 | import { List, Modal, Portal, useTheme } from 'react-native-paper';
4 | import RNPickerSelect from 'react-native-picker-select';
5 | import { useAppContext } from '../../AppContext';
6 | import { useQueryContext } from './QueryContext';
7 |
8 | function DictionaryItem(props) {
9 | const { name, displayName, setVisible, jumpToDictionaryRef } = props;
10 |
11 | return (
12 | {
15 | if (jumpToDictionaryRef.current) {
16 | try {
17 | jumpToDictionaryRef.current(name);
18 | } catch (error) {
19 | }
20 | setVisible(false);
21 | }
22 | }}
23 | />
24 | );
25 | }
26 |
27 | export default function DictionarySelection(props) {
28 | const theme = useTheme();
29 | const { dictionaries, groups, groupings } = useAppContext();
30 | const { nameActiveGroup, setNameActiveGroup, namesActiveDictionaries, jumpToDictionaryRef } = useQueryContext();
31 | const { visible, setVisible } = props;
32 |
33 | return (
34 |
35 | setVisible(false)}
38 | style={{ backgroundColor: theme.colors.background }}
39 | >
40 | {groupings && groupings[nameActiveGroup] && namesActiveDictionaries && (
41 | <>
42 | {
58 | return {
59 | label: group.name,
60 | value: group.name
61 | };
62 | })}
63 | onValueChange={(value) => {
64 | setNameActiveGroup(value);
65 | setVisible(false);
66 | }}
67 | value={nameActiveGroup}
68 | />
69 | groupings[nameActiveGroup].includes(dictionary.name) && namesActiveDictionaries.includes(dictionary.name))}
71 | renderItem={({ item }) =>
72 | }
77 | keyExtractor={(item) => item.name}
78 | />
79 | >
80 | )}
81 |
82 |
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export const DEFAULT_SERVER_ADDRESS = 'http://localhost:2628';
2 | export const DEFAULT_FONT_FAMILY = 'serif';
3 | export const DEFAULT_DARK_TEXT_COLOUR = 'grey';
4 | export const DEFAULT_TEXT_ZOOM = 100;
5 | export const TEXT_ZOOM_MAX = 200;
6 | export const TEXT_ZOOM_MIN = 40;
7 | export const RTL_LANGS = ['ar', 'dv', 'fa', 'ha', 'he', 'ks', 'ku', 'ps', 'ug', 'ur', 'yi'];
8 | export const ADDITIONAL_GOOGLE_FONTS = {
9 | 'serif': {
10 | 'Arabic': 'Noto Naskh Arabic',
11 | 'Bengali': 'Noto Serif Bengali',
12 | 'Chinese Simplified': 'Noto Serif SC',
13 | 'Chinese Traditional': 'Noto Serif TC',
14 | 'Cyrillic': 'Noto Serif',
15 | 'Devanagari': 'Noto Serif Devanagari',
16 | 'Greek': 'Noto Serif',
17 | 'Gujarati': 'Noto Serif Gujarati',
18 | 'Gurmukhi': 'Noto Serif Gurmukhi',
19 | 'Hebrew': 'Noto Serif Hebrew',
20 | 'Japanese': 'Noto Serif JP',
21 | 'Kannada': 'Noto Serif Kannada',
22 | 'Khmer': 'Noto Serif Khmer',
23 | 'Korean': 'Noto Serif KR',
24 | 'Latin': 'Noto Serif',
25 | 'Malayalam': 'Noto Serif Malayalam',
26 | 'Myanmar': 'Noto Serif Myanmar',
27 | 'Oriya': 'Noto Serif Oriya',
28 | 'Sinhala': 'Noto Serif Sinhala',
29 | 'Tamil': 'Noto Serif Tamil',
30 | 'Telugu': 'Noto Serif Telugu',
31 | 'Thai': 'Noto Serif Thai',
32 | 'Tibetan': 'Noto Serif Tibetan',
33 | 'Vietnamese': 'Noto Serif'
34 | },
35 | 'sans-serif': {
36 | 'Arabic': 'Noto Sans Arabic',
37 | 'Bengali': 'Noto Sans Bengali',
38 | 'Chinese Simplified': 'Noto Sans SC',
39 | 'Chinese Traditional': 'Noto Sans TC',
40 | 'Cyrillic': 'Noto Sans',
41 | 'Devanagari': 'Noto Sans',
42 | 'Greek': 'Noto Sans',
43 | 'Gujarati': 'Noto Sans Gujarati',
44 | 'Gurmukhi': 'Noto Sans Gurmukhi',
45 | 'Hebrew': 'Noto Sans Hebrew',
46 | 'Japanese': 'Noto Sans JP',
47 | 'Kannada': 'Noto Sans Kannada',
48 | 'Khmer': 'Noto Sans Khmer',
49 | 'Korean': 'Noto Sans KR',
50 | 'Latin': 'Noto Sans',
51 | 'Malayalam': 'Noto Sans Malayalam',
52 | 'Myanmar': 'Noto Sans Myanmar',
53 | 'Oriya': 'Noto Sans Oriya',
54 | 'Sinhala': 'Noto Sans Sinhala',
55 | 'Tamil': 'Noto Sans Tamil',
56 | 'Telugu': 'Noto Sans Telugu',
57 | 'Thai': 'Noto Sans Thai',
58 | 'Tibetan': 'Noto Serif Tibetan', // Tibetan always uses serif font?
59 | 'Vietnamese': 'Noto Sans'
60 | }
61 | };
62 | export const DEFAULT_ADDITIONAL_GOOGLE_FONTS_ENABLED_STATUS =
63 | Object.keys(ADDITIONAL_GOOGLE_FONTS.serif).reduce((status, script) => {
64 | status[script] = false;
65 | return status;
66 | }, {});
67 | export const SEPARATOR = '\x10'; // ASCII Data Link Escape, chosen because it is unlikely to appear in dictionary headwords
68 | export const DEFAULT_DARK_READER_ENABLED = false;
69 | export const DEFAULT_DARK_READER_BRIGHTNESS = 70;
70 | export const DEFAULT_DARK_READER_CONTRAST = 100;
71 | export const DEFAULT_DARK_READER_SEPIA = 10;
72 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/LibraryAppbar.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Appbar } from 'react-native-paper';
3 | import AddDictionaryDialogue from './DictionaryManager/AddDictionaryDialogue';
4 | import AddGroupDialogue from './GroupManager/AddGroupDialogue';
5 | import AddSourceDialogue from './SourceManager/AddSourceDialogue';
6 | import InProgressDialogue from '../common/InProgressDialogue';
7 | import { useAppContext } from '../../AppContext';
8 | import { convertDictionarySnakeCaseToCamelCase, loadDataFromJsonResponse } from '../../utils';
9 | import { localisedStrings } from '../../translations/l10n';
10 |
11 | export default function LibraryAppbar(props) {
12 | const { openDrawer, nameActiveTab } = props;
13 | const [addDictionaryDialogueVisible, setAddDictionaryDialogueVisible] = useState(false);
14 | const [addGroupDialogueVisible, setAddGroupDialogueVisible] = useState(false);
15 | const [addSourceDialogueVisible, setAddSourceDialogueVisible] = useState(false);
16 | const [rescaningSources, setRescaningSources] = useState(false);
17 |
18 | const { serverAddress, setDictionaries, setGroupings } = useAppContext();
19 | function handleRescan() {
20 | setRescaningSources(true);
21 | fetch(`${serverAddress}/api/management/scan`)
22 | .then(loadDataFromJsonResponse)
23 | .then((data) => {
24 | setDictionaries(data['dictionaries'].map(convertDictionarySnakeCaseToCamelCase));
25 | setGroupings(data['groupings']);
26 | })
27 | .catch((error) => {
28 | alert(localisedStrings['source-manager-alert-failure-rescaning-sources']);
29 | })
30 | .finally(() => {
31 | setRescaningSources(false);
32 | });
33 | }
34 |
35 | const actionsOnPress = {
36 | 'Dictionaries': () => setAddDictionaryDialogueVisible(true),
37 | 'Groups': () => setAddGroupDialogueVisible(true),
38 | 'Sources': () => setAddSourceDialogueVisible(true)
39 | };
40 |
41 | return (
42 |
43 | openDrawer()}
46 | />
47 |
50 | {nameActiveTab === 'Sources' &&
51 | handleRescan()}
54 | />}
55 |
59 |
63 |
67 |
71 |
75 |
76 | );
77 | }
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager/AddGroupDialogue.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Dialog, Text, TextInput, Portal } from 'react-native-paper';
3 | import ISO6391 from 'iso-639-1';
4 | import { useAppContext } from '../../../AppContext';
5 | import { getSetFromLangString, JSON_HEADER, loadDataFromJsonResponse } from '../../../utils';
6 | import { localisedStrings } from '../../../translations/l10n';
7 |
8 | export default function AddGroupDialogue(props) {
9 | const { visible, setVisible } = props;
10 | const { serverAddress, setGroups, groupings, setGroupings } = useAppContext();
11 |
12 | const [newGroupName, setNewGroupName] = useState('');
13 | const [newGroupLangsString, setNewGroupLangsString] = useState('');
14 |
15 | function handleSubmit() {
16 | if (newGroupName.length === 0) {
17 | alert(localisedStrings['group-manager-alert-empty-name']);
18 | return;
19 | }
20 |
21 | if (groupings[newGroupName]) {
22 | alert(localisedStrings['group-manager-alert-name-exists']);
23 | return;
24 | }
25 |
26 | const langs = getSetFromLangString(newGroupLangsString);
27 | for (const lang of langs) {
28 | if (!ISO6391.validate(lang)) {
29 | alert(localisedStrings['group-manager-alert-invalid-language-code']);
30 | return;
31 | }
32 | }
33 |
34 | fetch(`${serverAddress}/api/management/groups`, {
35 | method: 'POST',
36 | headers: JSON_HEADER,
37 | body: JSON.stringify({
38 | name: newGroupName,
39 | lang: Array.from(langs)
40 | })
41 | })
42 | .then(loadDataFromJsonResponse)
43 | .then((data) => {
44 | setGroups(data['groups']);
45 | setGroupings(data['groupings']);
46 | setVisible(false);
47 | })
48 | .catch((error) => {
49 | alert(localisedStrings['group-manager-alert-failure-adding-group']);
50 | });
51 | }
52 |
53 | return (
54 |
55 |
91 |
92 | );
93 | }
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/GroupManager/GroupCard.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { FlatList } from 'react-native';
3 | import { Button, Card, List } from 'react-native-paper';
4 | import ISO6391 from 'iso-639-1';
5 | import ConfirmDeleteDialogue from './ConfirmDeleteDialogue';
6 | import GroupNameEditorDialogue from './GroupNameEditorDialogue';
7 | import GroupLanguageEditorDialogue from './GroupLanguageEditorDialogue';
8 | import GroupDictionaryEditorDialogue from './GroupDictionaryEditorDialogue';
9 | import { useAppContext } from '../../../AppContext';
10 | import { localisedStrings } from '../../../translations/l10n';
11 |
12 | export default function GroupCard(props) {
13 | const { name, lang, onLongPress } = props;
14 | const { dictionaries, groupings } = useAppContext();
15 | const isDefaultGroup = name === 'Default Group';
16 | const [confirmDeleteDialogueVisible, setConfirmDeleteDialogueVisible] = useState(false);
17 | const [groupNameEditorDialogueVisible, setGroupNameEditorDialogueVisible] = useState(false);
18 | const [groupLanguageEditorDialogueVisible, setGroupLanguageEditorDialogueVisible] = useState(false);
19 | const [groupDictionaryEditorDialogueVisible, setGroupDictionaryEditorDialogueVisible] = useState(false);
20 |
21 | return (
22 |
23 |
25 | {!isDefaultGroup &&
26 |
29 |
34 |
39 | }
40 |
41 | setGroupLanguageEditorDialogueVisible(true)}
44 | >
45 | }
49 | keyExtractor={(item) => item} />
50 |
51 | setGroupDictionaryEditorDialogueVisible(true)}
54 | >
55 | {groupings[name] && groupings[name].includes(dictionary.name))}
57 | renderItem={({ item }) => }
58 | keyExtractor={(item) => item.name} />}
59 |
60 |
61 |
65 |
69 |
74 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/DictionaryManager.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import DraggableFlatList from 'react-native-draggable-flatlist';
3 | import { List } from 'react-native-paper';
4 | import EditMenu from './DictionaryManager/EditMenu';
5 | import DictionaryNameEditorDialogue from './DictionaryManager/DictionaryNameEditorDialogue';
6 | import ConfirmDeleteDialogue from './DictionaryManager/ConfirmDeleteDialogue';
7 | import { useAppContext } from '../../AppContext';
8 | import { convertDictionaryCamelCaseToSnakeCase, convertDictionarySnakeCaseToCamelCase, JSON_HEADER, loadDataFromJsonResponse } from '../../utils';
9 | import { localisedStrings } from '../../translations/l10n';
10 |
11 | export default function DictionaryManager() {
12 | const { serverAddress, dictionaries, setDictionaries } = useAppContext();
13 | const [editedDictionaryIndex, setEditedDictionaryIndex] = useState(-1);
14 | const [menuVisible, setMenuVisible] = useState(false);
15 | const [menuAnchor, setMenuAnchor] = useState({ x: 0, y: 0 });
16 | const [nameEditorVisible, setNameEditorVisible] = useState(false);
17 | const [confirmDeleteVisible, setConfirmDeleteVisible] = useState(false);
18 |
19 | function handleEdit(index) {
20 | setEditedDictionaryIndex(index);
21 | setNameEditorVisible(true);
22 | }
23 |
24 | function handleDelete(index) {
25 | setEditedDictionaryIndex(index);
26 | setConfirmDeleteVisible(true);
27 | }
28 |
29 | function handlePress(event, dictionaryName) {
30 | setMenuAnchor({ x: event.nativeEvent.pageX, y: event.nativeEvent.pageY });
31 | setMenuVisible(true);
32 | setEditedDictionaryIndex(dictionaries.findIndex((dictionary) => dictionary.name === dictionaryName));
33 | }
34 |
35 | function handleReorder(newDictionaries) {
36 | fetch(`${serverAddress}/api/management/dictionaries`, {
37 | method: 'PUT',
38 | headers: JSON_HEADER,
39 | body: JSON.stringify(newDictionaries.map(convertDictionaryCamelCaseToSnakeCase))
40 | })
41 | .then(loadDataFromJsonResponse)
42 | .then((data) => {
43 | setDictionaries(data.map(convertDictionarySnakeCaseToCamelCase));
44 | })
45 | .catch((error) => {
46 | alert(localisedStrings['dictionary-manager-alert-failure-reordering']);
47 | });
48 | }
49 |
50 | return (
51 | <>
52 | handleReorder(data)}
55 | renderItem={({ item, drag }) =>
56 | handlePress(event, item.name)}
59 | onLongPress={drag}
60 | />
61 | }
62 | keyExtractor={(item) => item.name}
63 | />
64 | handleEdit(editedDictionaryIndex)}
69 | onDelete={() => handleDelete(editedDictionaryIndex)}
70 | />
71 | {editedDictionaryIndex >= 0 && }
77 | {editedDictionaryIndex >= 0 && }
83 | >
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/ios/SilverDict.xcodeproj/xcshareddata/xcschemes/SilverDict.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/components/QueryScreen/ArticleBottomBar.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { View } from 'react-native';
3 | import { Appbar, TextInput, useTheme } from 'react-native-paper';
4 | import { TEXT_ZOOM_MAX, TEXT_ZOOM_MIN } from '../../config';
5 | import { useQueryContext } from './QueryContext';
6 | import { localisedStrings } from '../../translations/l10n';
7 |
8 | const TEXT_ZOOM_STEP = 10;
9 |
10 | export default function ArticleBottomBar() {
11 | const onSurfaceColour = useTheme().colors.onSurface;
12 | const { ableToGoBackInHistory, ableToGoForwardInHistory, searchInLocalHistory, textZoom, setTextZoom, findInPageRef } = useQueryContext();
13 |
14 | const [findBarActive, setFindBarActive] = useState(false);
15 | const [wordToFind, setWordToFind] = useState('');
16 |
17 | function handleTextZoomDecrease() {
18 | setTextZoom(Math.max(textZoom - TEXT_ZOOM_STEP, TEXT_ZOOM_MIN));
19 | }
20 |
21 | function handleTextZoomIncrease() {
22 | setTextZoom(Math.min(textZoom + TEXT_ZOOM_STEP, TEXT_ZOOM_MAX));
23 | }
24 |
25 | if (findBarActive)
26 | return (
27 |
28 |
29 | setWordToFind(text)}
43 | onSubmitEditing={(e) => {
44 | findInPageRef.current(wordToFind, false);
45 | }}
46 | />
47 | {
51 | findInPageRef.current(wordToFind, true);
52 | }} />
53 | {
57 | findInPageRef.current(wordToFind, false);
58 | }} />
59 | {
63 | setFindBarActive(false);
64 | setWordToFind('');
65 | }} />
66 |
67 |
68 | );
69 | else
70 | return (
71 |
72 |
73 | {
74 | ableToGoBackInHistory ?
75 | searchInLocalHistory(-1)} /> :
79 |
82 | }
83 | {
84 | ableToGoForwardInHistory ?
85 | searchInLocalHistory(+1)} /> :
89 |
92 | }
93 | handleTextZoomDecrease()} />
97 | handleTextZoomIncrease()} />
101 | setFindBarActive(true)} />
105 |
106 |
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/ios/SilverDict/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 | apply plugin: "org.jetbrains.kotlin.android"
3 | apply plugin: "com.facebook.react"
4 |
5 | /**
6 | * This is the configuration block to customize your React Native Android app.
7 | * By default you don't need to apply any configuration, just uncomment the lines you need.
8 | */
9 | react {
10 | /* Folders */
11 | // The root of your project, i.e. where "package.json" lives. Default is '..'
12 | // root = file("../")
13 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native
14 | // reactNativeDir = file("../node_modules/react-native")
15 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
16 | // codegenDir = file("../node_modules/@react-native/codegen")
17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
18 | // cliFile = file("../node_modules/react-native/cli.js")
19 |
20 | /* Variants */
21 | // The list of variants to that are debuggable. For those we're going to
22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
24 | // debuggableVariants = ["liteDebug", "prodDebug"]
25 |
26 | /* Bundling */
27 | // A list containing the node command and its flags. Default is just 'node'.
28 | // nodeExecutableAndArgs = ["node"]
29 | //
30 | // The command to run when bundling. By default is 'bundle'
31 | // bundleCommand = "ram-bundle"
32 | //
33 | // The path to the CLI configuration file. Default is empty.
34 | // bundleConfig = file(../rn-cli.config.js)
35 | //
36 | // The name of the generated asset file containing your JS bundle
37 | // bundleAssetName = "MyApplication.android.bundle"
38 | //
39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
40 | // entryFile = file("../js/MyApplication.android.js")
41 | //
42 | // A list of extra flags to pass to the 'bundle' commands.
43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
44 | // extraPackagerArgs = []
45 |
46 | /* Hermes Commands */
47 | // The hermes compiler command to run. By default it is 'hermesc'
48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
49 | //
50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
51 | // hermesFlags = ["-O", "-output-source-map"]
52 | }
53 |
54 | /**
55 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
56 | */
57 | def enableProguardInReleaseBuilds = true
58 |
59 | /**
60 | * The preferred build flavor of JavaScriptCore (JSC)
61 | *
62 | * For example, to use the international variant, you can use:
63 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
64 | *
65 | * The international variant includes ICU i18n library and necessary data
66 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
67 | * give correct results when using with locales other than en-US. Note that
68 | * this variant is about 6MiB larger per architecture than default.
69 | */
70 | def jscFlavor = 'org.webkit:android-jsc:+'
71 |
72 | android {
73 | ndkVersion rootProject.ext.ndkVersion
74 | buildToolsVersion rootProject.ext.buildToolsVersion
75 | compileSdk rootProject.ext.compileSdkVersion
76 |
77 | namespace "com.gmail.blandilyte.silverdict"
78 | defaultConfig {
79 | applicationId "com.gmail.blandilyte.silverdict"
80 | minSdkVersion rootProject.ext.minSdkVersion
81 | targetSdkVersion rootProject.ext.targetSdkVersion
82 | versionCode 13
83 | versionName "1.5.0"
84 | }
85 | signingConfigs {
86 | debug {
87 | storeFile file('debug.keystore')
88 | storePassword 'android'
89 | keyAlias 'androiddebugkey'
90 | keyPassword 'android'
91 | }
92 |
93 | release {
94 | storeFile file('release_key.keystore')
95 | storePassword '!!REPLACE_ME!!'
96 | keyAlias 'silverdict'
97 | keyPassword '!!REPLACE_ME!!'
98 | }
99 | }
100 | buildTypes {
101 | debug {
102 | signingConfig signingConfigs.debug
103 | }
104 | release {
105 | // Caution! In production, you need to generate your own keystore file.
106 | // see https://reactnative.dev/docs/signed-apk-android.
107 | signingConfig signingConfigs.debug
108 | minifyEnabled enableProguardInReleaseBuilds
109 | shrinkResources enableProguardInReleaseBuilds
110 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
111 | }
112 | }
113 |
114 | splits {
115 | abi {
116 | reset()
117 | enable true
118 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
119 | universalApk false
120 | }
121 | }
122 |
123 | project.ext.versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a":3, "x86_64":4]
124 |
125 | android.applicationVariants.all { variant ->
126 | variant.outputs.each { output ->
127 | output.versionCodeOverride =
128 | project.ext.versionCodes.get(output.getFilter(
129 | com.android.build.OutputFile.ABI), 0) + android.defaultConfig.versionCode * 10
130 | }
131 | }
132 | }
133 |
134 | dependencies {
135 | // The version of react-native is set by the React Native Gradle Plugin
136 | implementation("com.facebook.react:react-android")
137 |
138 | if (hermesEnabled.toBoolean()) {
139 | implementation("com.facebook.react:hermes-android")
140 | } else {
141 | implementation jscFlavor
142 | }
143 | }
144 |
145 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
146 |
147 | project.ext.vectoricons = [
148 | iconFontNames: [ 'MaterialCommunityIcons.ttf' ] // Name of the font files you want to copy
149 | ]
150 |
151 | apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle");
152 |
--------------------------------------------------------------------------------
/src/components/LibraryScreen/DictionaryManager/AddDictionaryDialogue.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Dialog, Text, TextInput, Portal, useTheme } from 'react-native-paper';
3 | import RNPickerSelect from 'react-native-picker-select';
4 | import { useAppContext } from '../../../AppContext';
5 | import { JSON_HEADER, loadDataFromJsonResponse, convertDictionarySnakeCaseToCamelCase } from '../../../utils';
6 | import { localisedStrings } from '../../../translations/l10n';
7 |
8 | export default function AddDictionaryDialogue(props) {
9 | const theme = useTheme();
10 | const { visible, setVisible } = props;
11 | const { serverAddress, formats, dictionaries, setDictionaries, groups, setGroupings } = useAppContext();
12 | const [newDictionaryDisplayName, setNewDictionaryDisplayName] = useState('');
13 | const [newDictionaryFilename, setNewDictionaryFilename] = useState('');
14 | const [newDictionaryFormat, setNewDictionaryFormat] = useState(formats[0]);
15 | const [newDictionaryGroup, setNewDictionaryGroup] = useState('Default Group');
16 |
17 | function handleSubmit() {
18 | if (newDictionaryDisplayName.length === 0) {
19 | alert(localisedStrings['add-dictionary-dialogue-alert-empty-name']);
20 | return;
21 | }
22 |
23 | if (newDictionaryFilename.length === 0) {
24 | alert(localisedStrings['add-dictionary-dialogue-alert-empty-filename']);
25 | return;
26 | }
27 |
28 | const pathSep = newDictionaryFilename.includes('/') ? '/' : '\\';
29 | const newDictionaryName = newDictionaryFilename.split(pathSep).pop().split('.')[0];
30 | if (dictionaries.map((dictionary) => dictionary.name).includes(newDictionaryName)) {
31 | alert(localisedStrings['add-dictionary-dialogue-alert-duplicate']);
32 | return;
33 | }
34 |
35 | const newDictionaryInfo = {
36 | dictionary_display_name: newDictionaryDisplayName,
37 | dictionary_name: newDictionaryName,
38 | dictionary_format: newDictionaryFormat,
39 | dictionary_filename: newDictionaryFilename
40 | };
41 |
42 | fetch(`${serverAddress}/api/validator/dictionary_info`, {
43 | method: 'POST',
44 | headers: JSON_HEADER,
45 | body: JSON.stringify(newDictionaryInfo)
46 | })
47 | .then(loadDataFromJsonResponse)
48 | .then((data) => {
49 | if (data['valid']) {
50 | newDictionaryInfo['group_name'] = newDictionaryGroup;
51 | fetch(`${serverAddress}/api/management/dictionaries`, {
52 | method: 'POST',
53 | headers: JSON_HEADER,
54 | body: JSON.stringify(newDictionaryInfo)
55 | })
56 | .then(loadDataFromJsonResponse)
57 | .then((data) => {
58 | setDictionaries(data['dictionaries'].map(convertDictionarySnakeCaseToCamelCase));
59 | setGroupings(data['groupings']);
60 | setVisible(false);
61 | })
62 | .catch((error) => {
63 | alert(localisedStrings['add-dictionary-dialogue-alert-failure-adding']);
64 | });
65 | } else {
66 | alert(localisedStrings['add-dictionary-dialogue-alert-invalid']);
67 | }
68 | })
69 | .catch((error) => {
70 | alert(localisedStrings['add-dictionary-dialogue-alert-failure-validating']);
71 | });
72 | }
73 |
74 | return (
75 |
76 |
160 |
161 | );
162 | }
--------------------------------------------------------------------------------
/src/translations/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "从 Google Fonts 下载附加字体。\n有些字体文件很大,请仅当看到小方框或问号时使用。",
3 | "additional-fonts-dialogue-title": "附加字体",
4 | "add-dictionary-dialogue-alert-duplicate": "已存在同名词典。",
5 | "add-dictionary-dialogue-alert-empty-filename": "文件名不能为空。",
6 | "add-dictionary-dialogue-alert-empty-name": "词典名称不能为空。",
7 | "add-dictionary-dialogue-alert-failure-adding": "添加词典失败。",
8 | "add-dictionary-dialogue-alert-failure-validating": "检查词典有效性失败。",
9 | "add-dictionary-dialogue-alert-invalid": "无效的词典信息。",
10 | "add-dictionary-dialogue-title": "添加词典",
11 | "add-dictionary-dialogue-content-group": "分组",
12 | "add-dictionary-dialogue-content-filename": "文件名",
13 | "add-dictionary-dialogue-content-format": "格式",
14 | "add-dictionary-dialogue-content-name": "词典名",
15 | "app-context-message-failure-fetching-data": "从服务器获取数据失败。请检查网络连接,或更改服务器地址并重试。",
16 | "article-view-tap-to-search-dialogue-message": "搜索 “{0}”?",
17 | "change-address-dialogue-message-unable-to-connect": "无法连接到服务器。",
18 | "change-address-dialogue-title": "变更服务器地址",
19 | "change-colour-dialogue-content": "推荐填写“white”(白色)或“grey”(灰色)。\n该设置仅在 Dark Reader 扩展被禁用时生效。",
20 | "change-colour-dialogue-title": "变更暗色模式下字体颜色",
21 | "change-font-dialogue-font-sans-serif": "无衬线字体(黑体)",
22 | "change-font-dialogue-font-serif": "衬线字体(宋体)",
23 | "change-font-dialogue-title": "变更字体",
24 | "change-size-history-dialogue-message-failure": "更新历史记录大小失败。",
25 | "change-size-history-dialogue-placeholder": "非负整数",
26 | "change-size-history-dialogue-title": "变更历史记录大小",
27 | "change-size-suggestion-dialogue-message-failure": "更新候选词数量失败。",
28 | "change-size-suggestion-dialogue-placeholder": "正整数",
29 | "change-size-suggestion-dialogue-title": "变更候选词数量",
30 | "clear-history-dialogue-content": "确认清空历史记录?",
31 | "clear-history-dialogue-message-failure": "清空历史记录失败。",
32 | "clear-history-dialogue-title": "清空历史记录",
33 | "confirm-recreate-ngram-dialogue-content": "确认重建 ngram 索引?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "重建 ngram 索引失败。",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "正在重建 ngram 索引…",
36 | "confirm-recreate-ngram-dialogue-message-success": "成功重建 ngram 索引。",
37 | "confirm-recreate-ngram-dialogue-title": "重建 ngram 索引",
38 | "dark-reader-config-brightness": "亮度",
39 | "dark-reader-config-contrast": "对比度",
40 | "dark-reader-config-sepia": "棕褐色",
41 | "dark-reader-config-enabled": "启用",
42 | "dark-reader-config-performance-warning": "注意,该扩展可能导致文章加载速度变慢。\n",
43 | "dark-reader-config-title": "Dark Reader 扩展设置",
44 | "dictionary-manager-alert-edit-name-failure": "更新词典名称失败。",
45 | "dictionary-manager-alert-delete-dictionary-failure": "删除词典失败。",
46 | "dictionary-manager-alert-failure-reordering": "重新排序词典失败。",
47 | "dictionary-manager-dialogue-edit-name-title": "编辑词典名",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "确定从数据库中删除《{0}》?词典文件不会被删除。",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "删除词典",
50 | "dictionary-manager-dialogue-name-placeholder": "新词典名称",
51 | "drawer-library-label": "库",
52 | "drawer-query-label": "查询",
53 | "drawer-reconnect-label": "重新连接",
54 | "drawer-settings-label": "设置",
55 | "generic-alert-invalid-number": "无效数字。",
56 | "generic-cancel": "取消",
57 | "generic-delete": "删除",
58 | "generic-edit": "编辑",
59 | "generic-no": "否",
60 | "generic-ok": "好",
61 | "generic-rename": "重命名",
62 | "generic-yes": "是",
63 | "group-manager-alert-empty-name": "组名不能为空。",
64 | "group-manager-alert-failure-adding-dictionary": "将词典添加到分组失败。",
65 | "group-manager-alert-failure-adding-group": "添加分组失败。",
66 | "group-manager-alert-failure-changing-languages": "更改分组语言失败。",
67 | "group-manager-alert-failure-removing-dictionary": "从分组中删除词典失败。",
68 | "group-manager-alert-failure-removing-group": "删除分组失败。",
69 | "group-manager-alert-failure-renaming-group": "重命名分组失败。",
70 | "group-manager-alert-failure-reordering-groups": "重新排序分组失败。",
71 | "group-manager-alert-invalid-language-code": "发现无效的语言代码。",
72 | "group-manager-alert-name-exists": "已存在同名分组。",
73 | "group-manager-card-title-dictionaries": "词典",
74 | "group-manager-card-title-languages": "语言",
75 | "group-manager-dialogue-add-title": "添加分组",
76 | "group-manager-dialogue-add-content": "组名",
77 | "group-manager-dialogue-change-languages-content": "ISO-639-1 两字母语言代码列表,逗号隔开,不能为空。",
78 | "group-manager-dialogue-change-languages-title": "更改分组语言",
79 | "group-manager-dialogue-delete-content": "确定删除分组“{0}”?",
80 | "group-manager-dialogue-delete-title": "删除分组",
81 | "group-manager-dialogue-rename-title": "重命名分组",
82 | "library-screen-groups": "分组",
83 | "library-screen-sources": "来源",
84 | "placeholder-search": "搜索……",
85 | "query-screen-failure-fetch-articles": "获取文章失败。",
86 | "query-screen-failure-fetch-suggestions": "获取候选词失败。",
87 | "script-name-arabic": "阿拉伯字母",
88 | "script-name-bengali": "孟加拉文",
89 | "script-name-chinese-simplified": "简体中文",
90 | "script-name-chinese-traditional": "繁体中文",
91 | "script-name-cyrillic": "西里尔字母",
92 | "script-name-devanagari": "天城书",
93 | "script-name-greek": "希腊字母",
94 | "script-name-gujarati": "古吉拉特文",
95 | "script-name-gurmukhi": "果鲁穆奇字母",
96 | "script-name-hebrew": "希伯来文",
97 | "script-name-japanese": "日文",
98 | "script-name-kannada": "坎纳达文",
99 | "script-name-khmer": "高棉文",
100 | "script-name-korean": "韩文",
101 | "script-name-latin": "拉丁字母",
102 | "script-name-malayalam": "马拉雅拉姆文",
103 | "script-name-myanmar": "缅甸文",
104 | "script-name-oriya": "奥里雅文",
105 | "script-name-sinhala": "僧伽罗文",
106 | "script-name-tamil": "泰米尔文",
107 | "script-name-telugu": "泰卢固文",
108 | "script-name-thai": "泰文",
109 | "script-name-tibetan": "藏文",
110 | "script-name-vietnamese": "越南文",
111 | "settings-screen-dark-text-colour-title": "暗色模式下字体颜色",
112 | "settings-screen-font-family-title": "字体",
113 | "settings-screen-recreate-ngram-description": "可能会很慢",
114 | "settings-screen-server-address-title": "服务器地址",
115 | "settings-screen-size-history-title": "历史记录大小",
116 | "settings-screen-size-suggestions-title": "候选词数量",
117 | "source-manager-alert-duplicate": "来源已存在。",
118 | "source-manager-alert-empty-source": "来源不能为空。",
119 | "source-manager-alert-failure-adding-source": "添加来源失败。",
120 | "source-manager-alert-failure-deleting-source": "删除来源失败。",
121 | "source-manager-alert-failure-fetching-sources": "获取来源失败。",
122 | "source-manager-alert-failure-rescaning-sources": "重新扫描失败。",
123 | "source-manager-alert-invalid-source": "无效的来源。",
124 | "source-manager-dialogue-add-source-title": "添加来源",
125 | "source-manager-dialogue-rescanning-sources-title": "正在重新扫描",
126 | "source-manager-confirm-delete-title": "删除来源",
127 | "source-manager-confirm-delete-content": "确定删除来源 {0} ?"
128 | }
--------------------------------------------------------------------------------
/src/components/SettingsScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { FlatList, View } from 'react-native';
3 | import { Appbar, List } from 'react-native-paper';
4 | import ChangeAddressDialogue from './SettingsScreen/ChangeAddressDialogue';
5 | import ChangeFontDialogue from './SettingsScreen/ChangeFontDialogue';
6 | import AdditionalFontsDialogue from './SettingsScreen/AdditionalFontsDialogue';
7 | import ChangeColourDialogue from './SettingsScreen/ChangeColourDialogue';
8 | import DarkReaderConfigDialogue from './SettingsScreen/DarkReaderConfigDialogue';
9 | import ChangeSizeSuggestionDialogue from './SettingsScreen/ChangeSizeSuggestionDialogue';
10 | import ChangeSizeHistoryDialogue from './SettingsScreen/ChangeSizeHistoryDialogue';
11 | import ConfirmClearHistoryDialogue from './SettingsScreen/ConfirmClearHistoryDialogue';
12 | import ConfirmRecreateNgramDialogue from './SettingsScreen/ConfirmRecreateNgramDialogue';
13 | import { useAppContext } from '../AppContext';
14 | import { localisedStrings } from '../translations/l10n';
15 |
16 | export default function SettingsScreen({ navigation }) {
17 | const { serverAddress, setServerAddress, fontFamily, setFontFamily, scriptsWithAdditionalFonts, setScriptsWithAdditionalFonts, darkTextColour, setDarkTextColour, sizeSuggestion, setSizeSuggestion, sizeHistory, setSizeHistory, setHistory } = useAppContext();
18 |
19 | const [addressDialogueVisible, setAddressDialogueVisible] = useState(false);
20 | const [fontDialogueVisible, setFontDialogueVisible] = useState(false);
21 | const [additionalFontsDialogueVisible, setAdditionalFontsDialogueVisible] = useState(false);
22 | const [colourDialogueVisible, setColourDialogueVisible] = useState(false);
23 | const [darkReaderConfigDialogueVisible, setDarkReaderConfigDialogueVisible] = useState(false);
24 | const [sizeSuggestionDialogueVisible, setSizeSuggestionDialogueVisible] = useState(false);
25 | const [sizeHistoryDialogueVisible, setSizeHistoryDialogueVisible] = useState(false);
26 | const [clearHistoryDialogueVisible, setClearHistoryDialogueVisible] = useState(false);
27 | const [recreateNgramDialogueVisible, setRecreateNgramDialogueVisible] = useState(false);
28 |
29 | return (
30 |
31 |
32 | navigation.openDrawer()} />
33 |
34 |
35 | {
41 | setAddressDialogueVisible(true);
42 | }
43 | },
44 | {
45 | title: localisedStrings['settings-screen-font-family-title'],
46 | description: localisedStrings[`change-font-dialogue-font-${fontFamily}`],
47 | onPress: () => {
48 | setFontDialogueVisible(true);
49 | }
50 | },
51 | {
52 | title: localisedStrings['additional-fonts-dialogue-title'],
53 | description: '',
54 | onPress: () => {
55 | setAdditionalFontsDialogueVisible(true);
56 | }
57 | },
58 | {
59 | title: localisedStrings['settings-screen-dark-text-colour-title'],
60 | description: darkTextColour,
61 | onPress: () => {
62 | setColourDialogueVisible(true);
63 | }
64 | },
65 | {
66 | title: localisedStrings['dark-reader-config-title'],
67 | description: '',
68 | onPress: () => {
69 | setDarkReaderConfigDialogueVisible(true);
70 | }
71 | },
72 | {
73 | title: localisedStrings['settings-screen-size-suggestions-title'],
74 | description: sizeSuggestion.toString(),
75 | onPress: () => {
76 | setSizeSuggestionDialogueVisible(true);
77 | }
78 | },
79 | {
80 | title: localisedStrings['settings-screen-size-history-title'],
81 | description: sizeHistory.toString(),
82 | onPress: () => {
83 | setSizeHistoryDialogueVisible(true);
84 | }
85 | },
86 | {
87 | title: localisedStrings['clear-history-dialogue-title'],
88 | description: '',
89 | onPress: () => {
90 | setClearHistoryDialogueVisible(true);
91 | }
92 | },
93 | {
94 | title: localisedStrings['confirm-recreate-ngram-dialogue-title'],
95 | description: localisedStrings['settings-screen-recreate-ngram-description'],
96 | onPress: () => {
97 | setRecreateNgramDialogueVisible(true);
98 | }
99 | }
100 | ]}
101 | renderItem={({ item }) => (
102 |
106 | )}
107 | keyExtractor={(item, index) => index.toString()} />
108 |
112 |
117 |
122 |
127 |
130 |
136 |
143 |
148 |
152 |
153 | );
154 | }
--------------------------------------------------------------------------------
/src/AppContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useEffect, useState } from 'react';
2 | import { loadPersistentData, storePersistentData } from './utils';
3 | import { DEFAULT_SERVER_ADDRESS, DEFAULT_FONT_FAMILY, DEFAULT_DARK_TEXT_COLOUR, DEFAULT_ADDITIONAL_GOOGLE_FONTS_ENABLED_STATUS, DEFAULT_DARK_READER_ENABLED, DEFAULT_DARK_READER_BRIGHTNESS, DEFAULT_DARK_READER_CONTRAST, DEFAULT_DARK_READER_SEPIA } from './config';
4 | import { loadDataFromJsonResponse, convertDictionarySnakeCaseToCamelCase } from './utils';
5 | import { localisedStrings } from './translations/l10n';
6 |
7 | const AppContext = createContext();
8 |
9 | export function AppProvider({ children }) {
10 | const [serverAddress, setServerAddress] = useState('');
11 |
12 | const [dictionaries, setDictionaries] = useState([]);
13 | const [groups, setGroups] = useState([]);
14 | const [groupings, setGroupings] = useState({});
15 |
16 | const [history, setHistory] = useState([]);
17 |
18 | const [fontFamily, setFontFamily] = useState('');
19 | const [darkTextColour, setDarkTextColour] = useState('');
20 | const [scriptsWithAdditionalFonts, setScriptsWithAdditionalFonts] = useState({});
21 |
22 | const [darkReaderEnabled, setDarkReaderEnabled] = useState('');
23 | const [darkReaderBrightness, setDarkReaderBrightness] = useState('');
24 | const [darkReaderContrast, setDarkReaderContrast] = useState('');
25 | const [darkReaderSepia, setDarkReaderSepia] = useState('');
26 |
27 | const [sizeSuggestion, setSizeSuggestion] = useState(10);
28 | const [sizeHistory, setSizeHistory] = useState(100);
29 |
30 | const [formats, setFormats] = useState([]);
31 | const [sources, setSources] = useState([]);
32 |
33 | async function loadStoredData() {
34 | const storedServerAddress = await loadPersistentData('serverAddress', DEFAULT_SERVER_ADDRESS);
35 | setServerAddress(storedServerAddress);
36 | const storedFontFamily = await loadPersistentData('fontFamily', DEFAULT_FONT_FAMILY);
37 | setFontFamily(storedFontFamily);
38 | const storedDarkTextColour = await loadPersistentData('darkTextColour', DEFAULT_DARK_TEXT_COLOUR);
39 | setDarkTextColour(storedDarkTextColour);
40 | const storedScriptsWithAdditionalFonts = await loadPersistentData('scriptsWithAdditionalFonts', JSON.stringify(DEFAULT_ADDITIONAL_GOOGLE_FONTS_ENABLED_STATUS));
41 | setScriptsWithAdditionalFonts(JSON.parse(storedScriptsWithAdditionalFonts));
42 | const storedDarkReaderEnabled = await loadPersistentData('darkReaderEnabled', JSON.stringify(DEFAULT_DARK_READER_ENABLED));
43 | setDarkReaderEnabled(JSON.parse(storedDarkReaderEnabled));
44 | const storedDarkReaderBrightness = await loadPersistentData('darkReaderBrightness', DEFAULT_DARK_READER_BRIGHTNESS);
45 | setDarkReaderBrightness(parseInt(storedDarkReaderBrightness));
46 | const storedDarkReaderContrast = await loadPersistentData('darkReaderContrast', DEFAULT_DARK_READER_CONTRAST);
47 | setDarkReaderContrast(parseInt(storedDarkReaderContrast));
48 | const storedDarkReaderSepia = await loadPersistentData('darkReaderSepia', DEFAULT_DARK_READER_SEPIA);
49 | setDarkReaderSepia(parseInt(storedDarkReaderSepia));
50 | }
51 |
52 | async function storeServerAddress() {
53 | await storePersistentData('serverAddress', serverAddress.toString());
54 | }
55 |
56 | async function storeFontFamily() {
57 | await storePersistentData('fontFamily', fontFamily.toString());
58 | }
59 |
60 | async function storeDarkTextColour() {
61 | await storePersistentData('darkTextColour', darkTextColour.toString());
62 | }
63 |
64 | async function storeScriptsWithAdditionalFonts() {
65 | await storePersistentData('scriptsWithAdditionalFonts', JSON.stringify(scriptsWithAdditionalFonts));
66 | }
67 |
68 | async function storeDarkReaderEnabled() {
69 | await storePersistentData('darkReaderEnabled', JSON.stringify(darkReaderEnabled));
70 | }
71 |
72 | async function storeDarkReaderBrightness() {
73 | await storePersistentData('darkReaderBrightness', darkReaderBrightness.toString());
74 | }
75 |
76 | async function storeDarkReaderContrast() {
77 | await storePersistentData('darkReaderContrast', darkReaderContrast.toString());
78 | }
79 |
80 | async function storeDarkReaderSepia() {
81 | await storePersistentData('darkReaderSepia', darkReaderSepia.toString());
82 | }
83 |
84 | useEffect(function () { loadStoredData(); }, []);
85 | useEffect(function () { storeServerAddress(); }, [serverAddress]);
86 | useEffect(function () { storeFontFamily(); }, [fontFamily]);
87 | useEffect(function () { storeDarkTextColour(); }, [darkTextColour]);
88 | useEffect(function () { storeScriptsWithAdditionalFonts(); }, [scriptsWithAdditionalFonts]);
89 | useEffect(function () { storeDarkReaderEnabled(); }, [darkReaderEnabled]);
90 | useEffect(function () { storeDarkReaderBrightness(); }, [darkReaderBrightness]);
91 | useEffect(function () { storeDarkReaderContrast(); }, [darkReaderContrast]);
92 | useEffect(function () { storeDarkReaderSepia(); }, [darkReaderSepia]);
93 |
94 | async function fetchInitialData(
95 | apiPrefix,
96 | setDictionaries,
97 | setGroups,
98 | setGroupings,
99 | setHistory,
100 | setSizeHistory,
101 | setSizeSuggestion
102 | ) {
103 | try {
104 | const [
105 | dictionariesData,
106 | groupsData,
107 | groupingsData,
108 | historyData,
109 | sizeHistoryData,
110 | sizeSuggestionData,
111 | formatsData,
112 | sourcesData
113 | ] = await Promise.all([
114 | fetch(`${apiPrefix}/management/dictionaries`).then(loadDataFromJsonResponse),
115 | fetch(`${apiPrefix}/management/groups`).then(loadDataFromJsonResponse),
116 | fetch(`${apiPrefix}/management/dictionary_groupings`).then(loadDataFromJsonResponse),
117 | fetch(`${apiPrefix}/management/history`).then(loadDataFromJsonResponse),
118 | fetch(`${apiPrefix}/management/history_size`).then(loadDataFromJsonResponse),
119 | fetch(`${apiPrefix}/management/num_suggestions`).then(loadDataFromJsonResponse),
120 | fetch(`${apiPrefix}/management/formats`).then(loadDataFromJsonResponse),
121 | fetch(`${apiPrefix}/management/sources`).then(loadDataFromJsonResponse)
122 | ]);
123 |
124 | setDictionaries(dictionariesData.map(convertDictionarySnakeCaseToCamelCase));
125 | setGroups(groupsData);
126 | setGroupings(groupingsData);
127 | setHistory(historyData);
128 | setSizeHistory(sizeHistoryData['size']);
129 | setSizeSuggestion(sizeSuggestionData['size']);
130 | setFormats(formatsData);
131 | setSources(sourcesData);
132 | } catch (error) {
133 | alert(localisedStrings['app-context-message-failure-fetching-data']);
134 | }
135 | }
136 |
137 | useEffect(function () {
138 | if (serverAddress.length > 0)
139 | fetchInitialData(
140 | `${serverAddress}/api`,
141 | setDictionaries,
142 | setGroups,
143 | setGroupings,
144 | setHistory,
145 | setSizeHistory,
146 | setSizeSuggestion
147 | );
148 | }, [serverAddress]);
149 |
150 | return (
151 |
187 | {children}
188 |
189 | );
190 | }
191 |
192 | export function useAppContext() {
193 | return useContext(AppContext);
194 | }
195 |
--------------------------------------------------------------------------------
/src/translations/zh-TW.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "Download additional fonts from Google Fonts.\nSome fonts are rather large, so please only use when you see little boxes or question marks in the text.",
3 | "additional-fonts-dialogue-title": "附加字型",
4 | "add-dictionary-dialogue-alert-duplicate": "A dictionary with the same name already exists.",
5 | "add-dictionary-dialogue-alert-empty-filename": "Filename cannot be empty.",
6 | "add-dictionary-dialogue-alert-empty-name": "Dictionary name cannot be empty.",
7 | "add-dictionary-dialogue-alert-failure-adding": "Failed to add dictionary.",
8 | "add-dictionary-dialogue-alert-failure-validating": "Failed to validate dictionary.",
9 | "add-dictionary-dialogue-alert-invalid": "Invalid dictionary info.",
10 | "add-dictionary-dialogue-title": "Add dictionary",
11 | "add-dictionary-dialogue-content-group": "Group",
12 | "add-dictionary-dialogue-content-filename": "Filename",
13 | "add-dictionary-dialogue-content-format": "Format",
14 | "add-dictionary-dialogue-content-name": "Dictionary name",
15 | "app-context-message-failure-fetching-data": "從伺服器獲取資料失敗。請檢查網路連線,或更改伺服器地址並重試。",
16 | "article-view-tap-to-search-dialogue-message": "搜尋 “{0}”?",
17 | "change-address-dialogue-message-unable-to-connect": "無法連線到伺服器。",
18 | "change-address-dialogue-title": "變更伺服器地址",
19 | "change-colour-dialogue-content": "A value such as ‘white’ or ‘grey’ is recommended.\nThis setting only takes effect when the Dark Reader extension is disabled.",
20 | "change-colour-dialogue-title": "變更暗色模式下字型顏色",
21 | "change-font-dialogue-font-sans-serif": "sans-serif",
22 | "change-font-dialogue-font-serif": "serif",
23 | "change-font-dialogue-title": "變更字型",
24 | "change-size-history-dialogue-message-failure": "Failed to update history size.",
25 | "change-size-history-dialogue-placeholder": "a non-negative integer",
26 | "change-size-history-dialogue-title": "Change the size of history",
27 | "change-size-suggestion-dialogue-message-failure": "Failed to update the number of suggestions.",
28 | "change-size-suggestion-dialogue-placeholder": "a positive integer",
29 | "change-size-suggestion-dialogue-title": "Change the number of suggestions",
30 | "clear-history-dialogue-content": "Are you sure you want to clear history?",
31 | "clear-history-dialogue-message-failure": "Failed to clear history.",
32 | "clear-history-dialogue-title": "Clear history",
33 | "confirm-recreate-ngram-dialogue-content": "Are you sure you want to recreate the ngram index?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "Failed to recreate ngram index.",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "Recreating ngram index…",
36 | "confirm-recreate-ngram-dialogue-message-success": "Recreated ngram index.",
37 | "confirm-recreate-ngram-dialogue-title": "Recreate ngram index",
38 | "dark-reader-config-brightness": "Brightness",
39 | "dark-reader-config-contrast": "Contrast",
40 | "dark-reader-config-sepia": "Sepia",
41 | "dark-reader-config-enabled": "Enabled",
42 | "dark-reader-config-performance-warning": "Please note, this extension may cause articles to load a bit slower than usual.\n",
43 | "dark-reader-config-title": "Dark Reader extension preferences",
44 | "dictionary-manager-alert-edit-name-failure": "Failed to update dictionary name.",
45 | "dictionary-manager-alert-delete-dictionary-failure": "Failed to delete dictionary.",
46 | "dictionary-manager-alert-failure-reordering": "Failed to reorder dictionaries.",
47 | "dictionary-manager-dialogue-edit-name-title": "Edit dictionary name",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "Are you sure you want to delete ‘{0}’ from the database? The dictionary files will not be deleted.",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "Delete dictionary",
50 | "dictionary-manager-dialogue-name-placeholder": "new dictionary name",
51 | "drawer-library-label": "Library",
52 | "drawer-query-label": "查詢",
53 | "drawer-reconnect-label": "Reconnect",
54 | "drawer-settings-label": "設定",
55 | "generic-alert-invalid-number": "\"無效數字。",
56 | "generic-cancel": "取消",
57 | "generic-delete": "Delete",
58 | "generic-edit": "編輯",
59 | "generic-no": "否",
60 | "generic-ok": "好",
61 | "generic-rename": "Rename",
62 | "generic-yes": "是",
63 | "group-manager-alert-empty-name": "Group name cannot be empty.",
64 | "group-manager-alert-failure-adding-dictionary": "Failed to add dictionary to group.",
65 | "group-manager-alert-failure-adding-group": "Failed to add group.",
66 | "group-manager-alert-failure-changing-languages": "Failed to change the group's languages.",
67 | "group-manager-alert-failure-removing-dictionary": "Failed to remove dictionary from group.",
68 | "group-manager-alert-failure-removing-group": "Failed to remove group.",
69 | "group-manager-alert-failure-renaming-group": "Failed to rename group.",
70 | "group-manager-alert-failure-reordering-groups": "Failed to reorder groups.",
71 | "group-manager-alert-invalid-language-code": "Invalid language code(s) found.",
72 | "group-manager-alert-name-exists": "A group with the same name already exists.",
73 | "group-manager-card-title-dictionaries": "Dictionaries",
74 | "group-manager-card-title-languages": "Languages",
75 | "group-manager-dialogue-add-title": "Add group",
76 | "group-manager-dialogue-add-content": "Group name",
77 | "group-manager-dialogue-change-languages-content": "A comma separated list of two-letter ISO-639-1 language codes. It cannot be empty.",
78 | "group-manager-dialogue-change-languages-title": "Change the group's languages",
79 | "group-manager-dialogue-delete-content": "Are you sure you want to delete the group ‘{0}’?",
80 | "group-manager-dialogue-delete-title": "Delete group",
81 | "group-manager-dialogue-rename-title": "Rename group",
82 | "library-screen-groups": "Groups",
83 | "library-screen-sources": "Sources",
84 | "placeholder-search": "搜尋……",
85 | "query-screen-failure-fetch-articles": "Failed to fetch articles.",
86 | "query-screen-failure-fetch-suggestions": "Failed to fetch suggestions.",
87 | "script-name-arabic": "阿拉伯字母",
88 | "script-name-bengali": "孟加拉文",
89 | "script-name-chinese-simplified": "簡體中文",
90 | "script-name-chinese-traditional": "正體中文",
91 | "script-name-cyrillic": "西里爾字母",
92 | "script-name-devanagari": "Devanagari",
93 | "script-name-greek": "Greek",
94 | "script-name-gujarati": "Gujarati",
95 | "script-name-gurmukhi": "Gurmukhi",
96 | "script-name-hebrew": "Hebrew",
97 | "script-name-japanese": "Japanese",
98 | "script-name-kannada": "Kannada",
99 | "script-name-khmer": "Khmer",
100 | "script-name-korean": "Korean",
101 | "script-name-latin": "Latin",
102 | "script-name-malayalam": "Malayalam",
103 | "script-name-myanmar": "Burmese",
104 | "script-name-oriya": "Oriya",
105 | "script-name-sinhala": "Sinhalese",
106 | "script-name-tamil": "Tamil",
107 | "script-name-telugu": "Telugu",
108 | "script-name-thai": "Thai",
109 | "script-name-tibetan": "Tibetan",
110 | "script-name-vietnamese": "Vietnamese",
111 | "settings-screen-dark-text-colour-title": "Colour of the text in dark mode",
112 | "settings-screen-font-family-title": "Font",
113 | "settings-screen-recreate-ngram-description": "可能會很慢",
114 | "settings-screen-server-address-title": "Server address",
115 | "settings-screen-size-history-title": "History size",
116 | "settings-screen-size-suggestions-title": "Suggestion size",
117 | "source-manager-alert-duplicate": "The source already exists.",
118 | "source-manager-alert-empty-source": "Source cannot be empty.",
119 | "source-manager-alert-failure-adding-source": "Failed to add source.",
120 | "source-manager-alert-failure-deleting-source": "Failed to delete source.",
121 | "source-manager-alert-failure-fetching-sources": "Failed to fetch sources.",
122 | "source-manager-alert-failure-rescaning-sources": "Failed to rescan sources.",
123 | "source-manager-alert-invalid-source": "Invalid source.",
124 | "source-manager-dialogue-add-source-title": "Add source",
125 | "source-manager-dialogue-rescanning-sources-title": "Rescanning sources",
126 | "source-manager-confirm-delete-title": "Delete source",
127 | "source-manager-confirm-delete-content": "Are you sure you want to delete the source {0} ?"
128 | }
--------------------------------------------------------------------------------
/src/translations/ar-SA.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "Download additional fonts from Google Fonts.\nSome fonts are rather large, so please only use when you see little boxes or question marks in the text.",
3 | "additional-fonts-dialogue-title": "Additional fonts",
4 | "add-dictionary-dialogue-alert-duplicate": "يوجد بالفعل قاموس بنفس الاسم.",
5 | "add-dictionary-dialogue-alert-empty-filename": "Filename cannot be empty.",
6 | "add-dictionary-dialogue-alert-empty-name": "Dictionary name cannot be empty.",
7 | "add-dictionary-dialogue-alert-failure-adding": "Failed to add dictionary.",
8 | "add-dictionary-dialogue-alert-failure-validating": "Failed to validate dictionary.",
9 | "add-dictionary-dialogue-alert-invalid": "Invalid dictionary info.",
10 | "add-dictionary-dialogue-title": "Add dictionary",
11 | "add-dictionary-dialogue-content-group": "Group",
12 | "add-dictionary-dialogue-content-filename": "Filename",
13 | "add-dictionary-dialogue-content-format": "Format",
14 | "add-dictionary-dialogue-content-name": "Dictionary name",
15 | "app-context-message-failure-fetching-data": "Failed to fetch data from the server. Please check your connection or change the server address and try again.",
16 | "article-view-tap-to-search-dialogue-message": "Search for ‘{0}’?",
17 | "change-address-dialogue-message-unable-to-connect": "Unable to connect to server.",
18 | "change-address-dialogue-title": "Change server address",
19 | "change-colour-dialogue-content": "A value such as ‘white’ or ‘grey’ is recommended.\nThis setting only takes effect when the Dark Reader extension is disabled.",
20 | "change-colour-dialogue-title": "Change the colour of the text in the dark mode",
21 | "change-font-dialogue-font-sans-serif": "sans-serif",
22 | "change-font-dialogue-font-serif": "serif",
23 | "change-font-dialogue-title": "Change font",
24 | "change-size-history-dialogue-message-failure": "Failed to update history size.",
25 | "change-size-history-dialogue-placeholder": "a non-negative integer",
26 | "change-size-history-dialogue-title": "Change the size of history",
27 | "change-size-suggestion-dialogue-message-failure": "Failed to update the number of suggestions.",
28 | "change-size-suggestion-dialogue-placeholder": "a positive integer",
29 | "change-size-suggestion-dialogue-title": "Change the number of suggestions",
30 | "clear-history-dialogue-content": "Are you sure you want to clear history?",
31 | "clear-history-dialogue-message-failure": "Failed to clear history.",
32 | "clear-history-dialogue-title": "Clear history",
33 | "confirm-recreate-ngram-dialogue-content": "Are you sure you want to recreate the ngram index?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "Failed to recreate ngram index.",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "Recreating ngram index…",
36 | "confirm-recreate-ngram-dialogue-message-success": "Recreated ngram index.",
37 | "confirm-recreate-ngram-dialogue-title": "Recreate ngram index",
38 | "dark-reader-config-brightness": "Brightness",
39 | "dark-reader-config-contrast": "Contrast",
40 | "dark-reader-config-sepia": "Sepia",
41 | "dark-reader-config-enabled": "Enabled",
42 | "dark-reader-config-performance-warning": "Please note, this extension may cause articles to load a bit slower than usual.\n",
43 | "dark-reader-config-title": "Dark Reader extension preferences",
44 | "dictionary-manager-alert-edit-name-failure": "Failed to update dictionary name.",
45 | "dictionary-manager-alert-delete-dictionary-failure": "Failed to delete dictionary.",
46 | "dictionary-manager-alert-failure-reordering": "Failed to reorder dictionaries.",
47 | "dictionary-manager-dialogue-edit-name-title": "Edit dictionary name",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "Are you sure you want to delete ‘{0}’ from the database? The dictionary files will not be deleted.",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "Delete dictionary",
50 | "dictionary-manager-dialogue-name-placeholder": "new dictionary name",
51 | "drawer-library-label": "Library",
52 | "drawer-query-label": "Query",
53 | "drawer-reconnect-label": "Reconnect",
54 | "drawer-settings-label": "Settings",
55 | "generic-alert-invalid-number": "Invalid number.",
56 | "generic-cancel": "Cancel",
57 | "generic-delete": "Delete",
58 | "generic-edit": "Edit",
59 | "generic-no": "No",
60 | "generic-ok": "OK",
61 | "generic-rename": "Rename",
62 | "generic-yes": "Yes",
63 | "group-manager-alert-empty-name": "Group name cannot be empty.",
64 | "group-manager-alert-failure-adding-dictionary": "Failed to add dictionary to group.",
65 | "group-manager-alert-failure-adding-group": "Failed to add group.",
66 | "group-manager-alert-failure-changing-languages": "Failed to change the group's languages.",
67 | "group-manager-alert-failure-removing-dictionary": "Failed to remove dictionary from group.",
68 | "group-manager-alert-failure-removing-group": "Failed to remove group.",
69 | "group-manager-alert-failure-renaming-group": "Failed to rename group.",
70 | "group-manager-alert-failure-reordering-groups": "Failed to reorder groups.",
71 | "group-manager-alert-invalid-language-code": "Invalid language code(s) found.",
72 | "group-manager-alert-name-exists": "A group with the same name already exists.",
73 | "group-manager-card-title-dictionaries": "Dictionaries",
74 | "group-manager-card-title-languages": "Languages",
75 | "group-manager-dialogue-add-title": "Add group",
76 | "group-manager-dialogue-add-content": "Group name",
77 | "group-manager-dialogue-change-languages-content": "A comma separated list of two-letter ISO-639-1 language codes. It cannot be empty.",
78 | "group-manager-dialogue-change-languages-title": "Change the group's languages",
79 | "group-manager-dialogue-delete-content": "Are you sure you want to delete the group ‘{0}’?",
80 | "group-manager-dialogue-delete-title": "Delete group",
81 | "group-manager-dialogue-rename-title": "Rename group",
82 | "library-screen-groups": "Groups",
83 | "library-screen-sources": "Sources",
84 | "placeholder-search": "Search…",
85 | "query-screen-failure-fetch-articles": "Failed to fetch articles.",
86 | "query-screen-failure-fetch-suggestions": "Failed to fetch suggestions.",
87 | "script-name-arabic": "Arabic",
88 | "script-name-bengali": "Bengali",
89 | "script-name-chinese-simplified": "Chinese, Simplified",
90 | "script-name-chinese-traditional": "Chinese, Traditional",
91 | "script-name-cyrillic": "Cyrillic",
92 | "script-name-devanagari": "Devanagari",
93 | "script-name-greek": "Greek",
94 | "script-name-gujarati": "Gujarati",
95 | "script-name-gurmukhi": "Gurmukhi",
96 | "script-name-hebrew": "Hebrew",
97 | "script-name-japanese": "Japanese",
98 | "script-name-kannada": "Kannada",
99 | "script-name-khmer": "Khmer",
100 | "script-name-korean": "Korean",
101 | "script-name-latin": "Latin",
102 | "script-name-malayalam": "Malayalam",
103 | "script-name-myanmar": "Burmese",
104 | "script-name-oriya": "Oriya",
105 | "script-name-sinhala": "Sinhalese",
106 | "script-name-tamil": "Tamil",
107 | "script-name-telugu": "Telugu",
108 | "script-name-thai": "Thai",
109 | "script-name-tibetan": "Tibetan",
110 | "script-name-vietnamese": "Vietnamese",
111 | "settings-screen-dark-text-colour-title": "Colour of the text in dark mode",
112 | "settings-screen-font-family-title": "Font",
113 | "settings-screen-recreate-ngram-description": "It could be very slow.",
114 | "settings-screen-server-address-title": "Server address",
115 | "settings-screen-size-history-title": "History size",
116 | "settings-screen-size-suggestions-title": "Suggestion size",
117 | "source-manager-alert-duplicate": "The source already exists.",
118 | "source-manager-alert-empty-source": "Source cannot be empty.",
119 | "source-manager-alert-failure-adding-source": "Failed to add source.",
120 | "source-manager-alert-failure-deleting-source": "Failed to delete source.",
121 | "source-manager-alert-failure-fetching-sources": "Failed to fetch sources.",
122 | "source-manager-alert-failure-rescaning-sources": "Failed to rescan sources.",
123 | "source-manager-alert-invalid-source": "Invalid source.",
124 | "source-manager-dialogue-add-source-title": "Add source",
125 | "source-manager-dialogue-rescanning-sources-title": "Rescanning sources",
126 | "source-manager-confirm-delete-title": "Delete source",
127 | "source-manager-confirm-delete-content": "Are you sure you want to delete the source {0} ?"
128 | }
--------------------------------------------------------------------------------
/src/translations/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "Download additional fonts from Google Fonts.\nSome fonts are rather large, so please only use when you see little boxes or question marks in the text.",
3 | "additional-fonts-dialogue-title": "Additional fonts",
4 | "add-dictionary-dialogue-alert-duplicate": "A dictionary with the same name already exists.",
5 | "add-dictionary-dialogue-alert-empty-filename": "Filename cannot be empty.",
6 | "add-dictionary-dialogue-alert-empty-name": "Dictionary name cannot be empty.",
7 | "add-dictionary-dialogue-alert-failure-adding": "Failed to add dictionary.",
8 | "add-dictionary-dialogue-alert-failure-validating": "Failed to validate dictionary.",
9 | "add-dictionary-dialogue-alert-invalid": "Invalid dictionary info.",
10 | "add-dictionary-dialogue-title": "Add dictionary",
11 | "add-dictionary-dialogue-content-group": "Group",
12 | "add-dictionary-dialogue-content-filename": "Filename",
13 | "add-dictionary-dialogue-content-format": "Format",
14 | "add-dictionary-dialogue-content-name": "Dictionary name",
15 | "app-context-message-failure-fetching-data": "Failed to fetch data from the server. Please check your connection or change the server address and try again.",
16 | "article-view-tap-to-search-dialogue-message": "Search for “{0}”?",
17 | "change-address-dialogue-message-unable-to-connect": "Unable to connect to server.",
18 | "change-address-dialogue-title": "Change server address",
19 | "change-colour-dialogue-content": "A value such as “white” or “gray” is recommended.\nThis setting only takes effect when the Dark Reader extension is disabled.",
20 | "change-colour-dialogue-title": "Change the color of the text in the dark mode",
21 | "change-font-dialogue-font-sans-serif": "sans-serif",
22 | "change-font-dialogue-font-serif": "serif",
23 | "change-font-dialogue-title": "Change font",
24 | "change-size-history-dialogue-message-failure": "Failed to update history size.",
25 | "change-size-history-dialogue-placeholder": "a non-negative integer",
26 | "change-size-history-dialogue-title": "Change the size of history",
27 | "change-size-suggestion-dialogue-message-failure": "Failed to update the number of suggestions.",
28 | "change-size-suggestion-dialogue-placeholder": "a positive integer",
29 | "change-size-suggestion-dialogue-title": "Change the number of suggestions",
30 | "clear-history-dialogue-content": "Are you sure you want to clear history?",
31 | "clear-history-dialogue-message-failure": "Failed to clear history.",
32 | "clear-history-dialogue-title": "Clear history",
33 | "confirm-recreate-ngram-dialogue-content": "Are you sure you want to recreate the ngram index?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "Failed to recreate ngram index.",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "Recreating ngram index…",
36 | "confirm-recreate-ngram-dialogue-message-success": "Recreated ngram index.",
37 | "confirm-recreate-ngram-dialogue-title": "Recreate ngram index",
38 | "dark-reader-config-brightness": "Brightness",
39 | "dark-reader-config-contrast": "Contrast",
40 | "dark-reader-config-sepia": "Sepia",
41 | "dark-reader-config-enabled": "Enabled",
42 | "dark-reader-config-performance-warning": "Please note, this extension may cause articles to load a bit slower than usual.\n",
43 | "dark-reader-config-title": "Dark Reader extension preferences",
44 | "dictionary-manager-alert-edit-name-failure": "Failed to update dictionary name.",
45 | "dictionary-manager-alert-delete-dictionary-failure": "Failed to delete dictionary.",
46 | "dictionary-manager-alert-failure-reordering": "Failed to reorder dictionaries.",
47 | "dictionary-manager-dialogue-edit-name-title": "Edit dictionary name",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "Are you sure you want to delete “{0}” from the database? The dictionary files will not be deleted.",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "Delete dictionary",
50 | "dictionary-manager-dialogue-name-placeholder": "new dictionary name",
51 | "drawer-library-label": "Library",
52 | "drawer-query-label": "Query",
53 | "drawer-reconnect-label": "Reconnect",
54 | "drawer-settings-label": "Settings",
55 | "generic-alert-invalid-number": "Invalid number.",
56 | "generic-cancel": "Cancel",
57 | "generic-delete": "Delete",
58 | "generic-edit": "Edit",
59 | "generic-no": "No",
60 | "generic-ok": "OK",
61 | "generic-rename": "Rename",
62 | "generic-yes": "Yes",
63 | "group-manager-alert-empty-name": "Group name cannot be empty.",
64 | "group-manager-alert-failure-adding-dictionary": "Failed to add dictionary to group.",
65 | "group-manager-alert-failure-adding-group": "Failed to add group.",
66 | "group-manager-alert-failure-changing-languages": "Failed to change the group's languages.",
67 | "group-manager-alert-failure-removing-dictionary": "Failed to remove dictionary from group.",
68 | "group-manager-alert-failure-removing-group": "Failed to remove group.",
69 | "group-manager-alert-failure-renaming-group": "Failed to rename group.",
70 | "group-manager-alert-failure-reordering-groups": "Failed to reorder groups.",
71 | "group-manager-alert-invalid-language-code": "Invalid language code(s) found.",
72 | "group-manager-alert-name-exists": "A group with the same name already exists.",
73 | "group-manager-card-title-dictionaries": "Dictionaries",
74 | "group-manager-card-title-languages": "Languages",
75 | "group-manager-dialogue-add-title": "Add group",
76 | "group-manager-dialogue-add-content": "Group name",
77 | "group-manager-dialogue-change-languages-content": "A comma separated list of two-letter ISO-639-1 language codes. It cannot be empty.",
78 | "group-manager-dialogue-change-languages-title": "Change the group's languages",
79 | "group-manager-dialogue-delete-content": "Are you sure you want to delete the group “{0}“?",
80 | "group-manager-dialogue-delete-title": "Delete group",
81 | "group-manager-dialogue-rename-title": "Rename group",
82 | "library-screen-groups": "Groups",
83 | "library-screen-sources": "Sources",
84 | "placeholder-search": "Search…",
85 | "query-screen-failure-fetch-articles": "Failed to fetch articles.",
86 | "query-screen-failure-fetch-suggestions": "Failed to fetch suggestions.",
87 | "script-name-arabic": "Arabic",
88 | "script-name-bengali": "Bengali",
89 | "script-name-chinese-simplified": "Chinese, Simplified",
90 | "script-name-chinese-traditional": "Chinese, Traditional",
91 | "script-name-cyrillic": "Cyrillic",
92 | "script-name-devanagari": "Devanagari",
93 | "script-name-greek": "Greek",
94 | "script-name-gujarati": "Gujarati",
95 | "script-name-gurmukhi": "Gurmukhi",
96 | "script-name-hebrew": "Hebrew",
97 | "script-name-japanese": "Japanese",
98 | "script-name-kannada": "Kannada",
99 | "script-name-khmer": "Khmer",
100 | "script-name-korean": "Korean",
101 | "script-name-latin": "Latin",
102 | "script-name-malayalam": "Malayalam",
103 | "script-name-myanmar": "Burmese",
104 | "script-name-oriya": "Oriya",
105 | "script-name-sinhala": "Sinhalese",
106 | "script-name-tamil": "Tamil",
107 | "script-name-telugu": "Telugu",
108 | "script-name-thai": "Thai",
109 | "script-name-tibetan": "Tibetan",
110 | "script-name-vietnamese": "Vietnamese",
111 | "settings-screen-dark-text-colour-title": "Color of the text in dark mode",
112 | "settings-screen-font-family-title": "Font",
113 | "settings-screen-recreate-ngram-description": "It could be very slow.",
114 | "settings-screen-server-address-title": "Server address",
115 | "settings-screen-size-history-title": "History size",
116 | "settings-screen-size-suggestions-title": "Suggestion size",
117 | "source-manager-alert-duplicate": "The source already exists.",
118 | "source-manager-alert-empty-source": "Source cannot be empty.",
119 | "source-manager-alert-failure-adding-source": "Failed to add source.",
120 | "source-manager-alert-failure-deleting-source": "Failed to delete source.",
121 | "source-manager-alert-failure-fetching-sources": "Failed to fetch sources.",
122 | "source-manager-alert-failure-rescaning-sources": "Failed to rescan sources.",
123 | "source-manager-alert-invalid-source": "Invalid source.",
124 | "source-manager-dialogue-add-source-title": "Add source",
125 | "source-manager-dialogue-rescanning-sources-title": "Rescanning sources",
126 | "source-manager-confirm-delete-title": "Delete source",
127 | "source-manager-confirm-delete-content": "Are you sure you want to delete the source {0} ?"
128 | }
--------------------------------------------------------------------------------
/src/translations/es-ES.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "Download additional fonts from Google Fonts.\nSome fonts are rather large, so please only use when you see little boxes or question marks in the text.",
3 | "additional-fonts-dialogue-title": "Additional fonts",
4 | "add-dictionary-dialogue-alert-duplicate": "A dictionary with the same name already exists.",
5 | "add-dictionary-dialogue-alert-empty-filename": "Filename cannot be empty.",
6 | "add-dictionary-dialogue-alert-empty-name": "Dictionary name cannot be empty.",
7 | "add-dictionary-dialogue-alert-failure-adding": "Failed to add dictionary.",
8 | "add-dictionary-dialogue-alert-failure-validating": "Failed to validate dictionary.",
9 | "add-dictionary-dialogue-alert-invalid": "Invalid dictionary info.",
10 | "add-dictionary-dialogue-title": "Add dictionary",
11 | "add-dictionary-dialogue-content-group": "Grupo",
12 | "add-dictionary-dialogue-content-filename": "Filename",
13 | "add-dictionary-dialogue-content-format": "Format",
14 | "add-dictionary-dialogue-content-name": "Dictionary name",
15 | "app-context-message-failure-fetching-data": "Failed to fetch data from the server. Please check your connection or change the server address and try again.",
16 | "article-view-tap-to-search-dialogue-message": "¿Buscar ‘{0}’?",
17 | "change-address-dialogue-message-unable-to-connect": "Unable to connect to server.",
18 | "change-address-dialogue-title": "Change server address",
19 | "change-colour-dialogue-content": "A value such as ‘white’ or ‘grey’ is recommended.\nThis setting only takes effect when the Dark Reader extension is disabled.",
20 | "change-colour-dialogue-title": "Change the colour of the text in the dark mode",
21 | "change-font-dialogue-font-sans-serif": "sans-serif",
22 | "change-font-dialogue-font-serif": "serif",
23 | "change-font-dialogue-title": "Change font",
24 | "change-size-history-dialogue-message-failure": "Failed to update history size.",
25 | "change-size-history-dialogue-placeholder": "a non-negative integer",
26 | "change-size-history-dialogue-title": "Change the size of history",
27 | "change-size-suggestion-dialogue-message-failure": "Failed to update the number of suggestions.",
28 | "change-size-suggestion-dialogue-placeholder": "a positive integer",
29 | "change-size-suggestion-dialogue-title": "Change the number of suggestions",
30 | "clear-history-dialogue-content": "Are you sure you want to clear history?",
31 | "clear-history-dialogue-message-failure": "Failed to clear history.",
32 | "clear-history-dialogue-title": "Clear history",
33 | "confirm-recreate-ngram-dialogue-content": "Are you sure you want to recreate the ngram index?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "Failed to recreate ngram index.",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "Recreating ngram index…",
36 | "confirm-recreate-ngram-dialogue-message-success": "Recreated ngram index.",
37 | "confirm-recreate-ngram-dialogue-title": "Recreate ngram index",
38 | "dark-reader-config-brightness": "Brightness",
39 | "dark-reader-config-contrast": "Contrast",
40 | "dark-reader-config-sepia": "Sepia",
41 | "dark-reader-config-enabled": "Enabled",
42 | "dark-reader-config-performance-warning": "Please note, this extension may cause articles to load a bit slower than usual.\n",
43 | "dark-reader-config-title": "Dark Reader extension preferences",
44 | "dictionary-manager-alert-edit-name-failure": "Failed to update dictionary name.",
45 | "dictionary-manager-alert-delete-dictionary-failure": "Failed to delete dictionary.",
46 | "dictionary-manager-alert-failure-reordering": "Failed to reorder dictionaries.",
47 | "dictionary-manager-dialogue-edit-name-title": "Edit dictionary name",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "Are you sure you want to delete ‘{0}’ from the database? The dictionary files will not be deleted.",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "Delete dictionary",
50 | "dictionary-manager-dialogue-name-placeholder": "new dictionary name",
51 | "drawer-library-label": "Library",
52 | "drawer-query-label": "Query",
53 | "drawer-reconnect-label": "Reconnect",
54 | "drawer-settings-label": "Settings",
55 | "generic-alert-invalid-number": "Invalid number.",
56 | "generic-cancel": "Cancel",
57 | "generic-delete": "Delete",
58 | "generic-edit": "Edit",
59 | "generic-no": "No",
60 | "generic-ok": "OK",
61 | "generic-rename": "Rename",
62 | "generic-yes": "Sí",
63 | "group-manager-alert-empty-name": "Group name cannot be empty.",
64 | "group-manager-alert-failure-adding-dictionary": "Failed to add dictionary to group.",
65 | "group-manager-alert-failure-adding-group": "Failed to add group.",
66 | "group-manager-alert-failure-changing-languages": "Failed to change the group's languages.",
67 | "group-manager-alert-failure-removing-dictionary": "Failed to remove dictionary from group.",
68 | "group-manager-alert-failure-removing-group": "Failed to remove group.",
69 | "group-manager-alert-failure-renaming-group": "Failed to rename group.",
70 | "group-manager-alert-failure-reordering-groups": "Failed to reorder groups.",
71 | "group-manager-alert-invalid-language-code": "Invalid language code(s) found.",
72 | "group-manager-alert-name-exists": "A group with the same name already exists.",
73 | "group-manager-card-title-dictionaries": "Dictionaries",
74 | "group-manager-card-title-languages": "Languages",
75 | "group-manager-dialogue-add-title": "Add group",
76 | "group-manager-dialogue-add-content": "Group name",
77 | "group-manager-dialogue-change-languages-content": "A comma separated list of two-letter ISO-639-1 language codes. It cannot be empty.",
78 | "group-manager-dialogue-change-languages-title": "Change the group's languages",
79 | "group-manager-dialogue-delete-content": "Are you sure you want to delete the group ‘{0}’?",
80 | "group-manager-dialogue-delete-title": "Delete group",
81 | "group-manager-dialogue-rename-title": "Rename group",
82 | "library-screen-groups": "Groups",
83 | "library-screen-sources": "Sources",
84 | "placeholder-search": "Búsqueda…",
85 | "query-screen-failure-fetch-articles": "Failed to fetch articles.",
86 | "query-screen-failure-fetch-suggestions": "Failed to fetch suggestions.",
87 | "script-name-arabic": "Arabic",
88 | "script-name-bengali": "Bengali",
89 | "script-name-chinese-simplified": "Chinese, Simplified",
90 | "script-name-chinese-traditional": "Chinese, Traditional",
91 | "script-name-cyrillic": "Cyrillic",
92 | "script-name-devanagari": "Devanagari",
93 | "script-name-greek": "Greek",
94 | "script-name-gujarati": "Gujarati",
95 | "script-name-gurmukhi": "Gurmukhi",
96 | "script-name-hebrew": "Hebrew",
97 | "script-name-japanese": "Japanese",
98 | "script-name-kannada": "Kannada",
99 | "script-name-khmer": "Khmer",
100 | "script-name-korean": "Korean",
101 | "script-name-latin": "Latin",
102 | "script-name-malayalam": "Malayalam",
103 | "script-name-myanmar": "Burmese",
104 | "script-name-oriya": "Oriya",
105 | "script-name-sinhala": "Sinhalese",
106 | "script-name-tamil": "Tamil",
107 | "script-name-telugu": "Telugu",
108 | "script-name-thai": "Thai",
109 | "script-name-tibetan": "Tibetan",
110 | "script-name-vietnamese": "Vietnamese",
111 | "settings-screen-dark-text-colour-title": "Colour of the text in dark mode",
112 | "settings-screen-font-family-title": "Font",
113 | "settings-screen-recreate-ngram-description": "It could be very slow.",
114 | "settings-screen-server-address-title": "Server address",
115 | "settings-screen-size-history-title": "History size",
116 | "settings-screen-size-suggestions-title": "Suggestion size",
117 | "source-manager-alert-duplicate": "The source already exists.",
118 | "source-manager-alert-empty-source": "Source cannot be empty.",
119 | "source-manager-alert-failure-adding-source": "Failed to add source.",
120 | "source-manager-alert-failure-deleting-source": "Failed to delete source.",
121 | "source-manager-alert-failure-fetching-sources": "Failed to fetch sources.",
122 | "source-manager-alert-failure-rescaning-sources": "Failed to rescan sources.",
123 | "source-manager-alert-invalid-source": "Invalid source.",
124 | "source-manager-dialogue-add-source-title": "Add source",
125 | "source-manager-dialogue-rescanning-sources-title": "Rescanning sources",
126 | "source-manager-confirm-delete-title": "Delete source",
127 | "source-manager-confirm-delete-content": "Are you sure you want to delete the source {0} ?"
128 | }
--------------------------------------------------------------------------------
/src/translations/de-DE.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "Download additional fonts from Google Fonts.\nSome fonts are rather large, so please only use when you see little boxes or question marks in the text.",
3 | "additional-fonts-dialogue-title": "Additional fonts",
4 | "add-dictionary-dialogue-alert-duplicate": "A dictionary with the same name already exists.",
5 | "add-dictionary-dialogue-alert-empty-filename": "Filename cannot be empty.",
6 | "add-dictionary-dialogue-alert-empty-name": "Dictionary name cannot be empty.",
7 | "add-dictionary-dialogue-alert-failure-adding": "Failed to add dictionary.",
8 | "add-dictionary-dialogue-alert-failure-validating": "Failed to validate dictionary.",
9 | "add-dictionary-dialogue-alert-invalid": "Invalid dictionary info.",
10 | "add-dictionary-dialogue-title": "Add dictionary",
11 | "add-dictionary-dialogue-content-group": "Group",
12 | "add-dictionary-dialogue-content-filename": "Filename",
13 | "add-dictionary-dialogue-content-format": "Format",
14 | "add-dictionary-dialogue-content-name": "Dictionary name",
15 | "app-context-message-failure-fetching-data": "Failed to fetch data from the server. Please check your connection or change the server address and try again.",
16 | "article-view-tap-to-search-dialogue-message": "Search for ‘{0}’?",
17 | "change-address-dialogue-message-unable-to-connect": "Unable to connect to server.",
18 | "change-address-dialogue-title": "Change server address",
19 | "change-colour-dialogue-content": "A value such as ‘white’ or ‘grey’ is recommended.\nThis setting only takes effect when the Dark Reader extension is disabled.",
20 | "change-colour-dialogue-title": "Change the colour of the text in the dark mode",
21 | "change-font-dialogue-font-sans-serif": "sans-serif",
22 | "change-font-dialogue-font-serif": "serif",
23 | "change-font-dialogue-title": "Change font",
24 | "change-size-history-dialogue-message-failure": "Failed to update history size.",
25 | "change-size-history-dialogue-placeholder": "a non-negative integer",
26 | "change-size-history-dialogue-title": "Change the size of history",
27 | "change-size-suggestion-dialogue-message-failure": "Failed to update the number of suggestions.",
28 | "change-size-suggestion-dialogue-placeholder": "a positive integer",
29 | "change-size-suggestion-dialogue-title": "Change the number of suggestions",
30 | "clear-history-dialogue-content": "Are you sure you want to clear history?",
31 | "clear-history-dialogue-message-failure": "Failed to clear history.",
32 | "clear-history-dialogue-title": "Clear history",
33 | "confirm-recreate-ngram-dialogue-content": "Are you sure you want to recreate the ngram index?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "Failed to recreate ngram index.",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "Recreating ngram index…",
36 | "confirm-recreate-ngram-dialogue-message-success": "Recreated ngram index.",
37 | "confirm-recreate-ngram-dialogue-title": "Recreate ngram index",
38 | "dark-reader-config-brightness": "Brightness",
39 | "dark-reader-config-contrast": "Contrast",
40 | "dark-reader-config-sepia": "Sepia",
41 | "dark-reader-config-enabled": "Enabled",
42 | "dark-reader-config-performance-warning": "Please note, this extension may cause articles to load a bit slower than usual.\n",
43 | "dark-reader-config-title": "Dark Reader extension preferences",
44 | "dictionary-manager-alert-edit-name-failure": "Failed to update dictionary name.",
45 | "dictionary-manager-alert-delete-dictionary-failure": "Failed to delete dictionary.",
46 | "dictionary-manager-alert-failure-reordering": "Failed to reorder dictionaries.",
47 | "dictionary-manager-dialogue-edit-name-title": "Edit dictionary name",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "Are you sure you want to delete ‘{0}’ from the database? The dictionary files will not be deleted.",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "Delete dictionary",
50 | "dictionary-manager-dialogue-name-placeholder": "new dictionary name",
51 | "drawer-library-label": "Library",
52 | "drawer-query-label": "Query",
53 | "drawer-reconnect-label": "Reconnect",
54 | "drawer-settings-label": "Settings",
55 | "generic-alert-invalid-number": "Invalid number.",
56 | "generic-cancel": "Cancel",
57 | "generic-delete": "Delete",
58 | "generic-edit": "Edit",
59 | "generic-no": "No",
60 | "generic-ok": "OK",
61 | "generic-rename": "Rename",
62 | "generic-yes": "Yes",
63 | "group-manager-alert-empty-name": "Group name cannot be empty.",
64 | "group-manager-alert-failure-adding-dictionary": "Failed to add dictionary to group.",
65 | "group-manager-alert-failure-adding-group": "Failed to add group.",
66 | "group-manager-alert-failure-changing-languages": "Failed to change the group's languages.",
67 | "group-manager-alert-failure-removing-dictionary": "Failed to remove dictionary from group.",
68 | "group-manager-alert-failure-removing-group": "Failed to remove group.",
69 | "group-manager-alert-failure-renaming-group": "Failed to rename group.",
70 | "group-manager-alert-failure-reordering-groups": "Failed to reorder groups.",
71 | "group-manager-alert-invalid-language-code": "Invalid language code(s) found.",
72 | "group-manager-alert-name-exists": "A group with the same name already exists.",
73 | "group-manager-card-title-dictionaries": "Dictionaries",
74 | "group-manager-card-title-languages": "Languages",
75 | "group-manager-dialogue-add-title": "Add group",
76 | "group-manager-dialogue-add-content": "Group name",
77 | "group-manager-dialogue-change-languages-content": "A comma separated list of two-letter ISO-639-1 language codes. It cannot be empty.",
78 | "group-manager-dialogue-change-languages-title": "Change the group's languages",
79 | "group-manager-dialogue-delete-content": "Are you sure you want to delete the group ‘{0}’?",
80 | "group-manager-dialogue-delete-title": "Delete group",
81 | "group-manager-dialogue-rename-title": "Rename group",
82 | "library-screen-groups": "Groups",
83 | "library-screen-sources": "Sources",
84 | "placeholder-search": "Search…",
85 | "query-screen-failure-fetch-articles": "Failed to fetch articles.",
86 | "query-screen-failure-fetch-suggestions": "Failed to fetch suggestions.",
87 | "script-name-arabic": "Arabic",
88 | "script-name-bengali": "Bengali",
89 | "script-name-chinese-simplified": "Chinese, Simplified",
90 | "script-name-chinese-traditional": "Chinese, Traditional",
91 | "script-name-cyrillic": "Cyrillic",
92 | "script-name-devanagari": "Devanagari",
93 | "script-name-greek": "Greek",
94 | "script-name-gujarati": "Gujarati",
95 | "script-name-gurmukhi": "Gurmukhi",
96 | "script-name-hebrew": "Hebrew",
97 | "script-name-japanese": "Japanese",
98 | "script-name-kannada": "Kannada",
99 | "script-name-khmer": "Khmer",
100 | "script-name-korean": "Korean",
101 | "script-name-latin": "Latin",
102 | "script-name-malayalam": "Malayalam",
103 | "script-name-myanmar": "Burmese",
104 | "script-name-oriya": "Oriya",
105 | "script-name-sinhala": "Sinhalese",
106 | "script-name-tamil": "Tamil",
107 | "script-name-telugu": "Telugu",
108 | "script-name-thai": "Thai",
109 | "script-name-tibetan": "Tibetan",
110 | "script-name-vietnamese": "Vietnamese",
111 | "settings-screen-dark-text-colour-title": "Colour of the text in dark mode",
112 | "settings-screen-font-family-title": "Font",
113 | "settings-screen-recreate-ngram-description": "It could be very slow.",
114 | "settings-screen-server-address-title": "Server address",
115 | "settings-screen-size-history-title": "History size",
116 | "settings-screen-size-suggestions-title": "Suggestion size",
117 | "source-manager-alert-duplicate": "The source already exists.",
118 | "source-manager-alert-empty-source": "Source cannot be empty.",
119 | "source-manager-alert-failure-adding-source": "Failed to add source.",
120 | "source-manager-alert-failure-deleting-source": "Failed to delete source.",
121 | "source-manager-alert-failure-fetching-sources": "Failed to fetch sources.",
122 | "source-manager-alert-failure-rescaning-sources": "Failed to rescan sources.",
123 | "source-manager-alert-invalid-source": "Invalid source.",
124 | "source-manager-dialogue-add-source-title": "Add source",
125 | "source-manager-dialogue-rescanning-sources-title": "Rescanning sources",
126 | "source-manager-confirm-delete-title": "Delete source",
127 | "source-manager-confirm-delete-content": "Are you sure you want to delete the source {0} ?"
128 | }
--------------------------------------------------------------------------------
/src/translations/el-GR.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "Download additional fonts from Google Fonts.\nSome fonts are rather large, so please only use when you see little boxes or question marks in the text.",
3 | "additional-fonts-dialogue-title": "Additional fonts",
4 | "add-dictionary-dialogue-alert-duplicate": "A dictionary with the same name already exists.",
5 | "add-dictionary-dialogue-alert-empty-filename": "Filename cannot be empty.",
6 | "add-dictionary-dialogue-alert-empty-name": "Dictionary name cannot be empty.",
7 | "add-dictionary-dialogue-alert-failure-adding": "Failed to add dictionary.",
8 | "add-dictionary-dialogue-alert-failure-validating": "Failed to validate dictionary.",
9 | "add-dictionary-dialogue-alert-invalid": "Invalid dictionary info.",
10 | "add-dictionary-dialogue-title": "Add dictionary",
11 | "add-dictionary-dialogue-content-group": "Group",
12 | "add-dictionary-dialogue-content-filename": "Filename",
13 | "add-dictionary-dialogue-content-format": "Format",
14 | "add-dictionary-dialogue-content-name": "Dictionary name",
15 | "app-context-message-failure-fetching-data": "Failed to fetch data from the server. Please check your connection or change the server address and try again.",
16 | "article-view-tap-to-search-dialogue-message": "Search for ‘{0}’?",
17 | "change-address-dialogue-message-unable-to-connect": "Unable to connect to server.",
18 | "change-address-dialogue-title": "Change server address",
19 | "change-colour-dialogue-content": "A value such as ‘white’ or ‘grey’ is recommended.\nThis setting only takes effect when the Dark Reader extension is disabled.",
20 | "change-colour-dialogue-title": "Change the colour of the text in the dark mode",
21 | "change-font-dialogue-font-sans-serif": "sans-serif",
22 | "change-font-dialogue-font-serif": "serif",
23 | "change-font-dialogue-title": "Change font",
24 | "change-size-history-dialogue-message-failure": "Failed to update history size.",
25 | "change-size-history-dialogue-placeholder": "a non-negative integer",
26 | "change-size-history-dialogue-title": "Change the size of history",
27 | "change-size-suggestion-dialogue-message-failure": "Failed to update the number of suggestions.",
28 | "change-size-suggestion-dialogue-placeholder": "a positive integer",
29 | "change-size-suggestion-dialogue-title": "Change the number of suggestions",
30 | "clear-history-dialogue-content": "Are you sure you want to clear history?",
31 | "clear-history-dialogue-message-failure": "Failed to clear history.",
32 | "clear-history-dialogue-title": "Clear history",
33 | "confirm-recreate-ngram-dialogue-content": "Are you sure you want to recreate the ngram index?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "Failed to recreate ngram index.",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "Recreating ngram index…",
36 | "confirm-recreate-ngram-dialogue-message-success": "Recreated ngram index.",
37 | "confirm-recreate-ngram-dialogue-title": "Recreate ngram index",
38 | "dark-reader-config-brightness": "Brightness",
39 | "dark-reader-config-contrast": "Contrast",
40 | "dark-reader-config-sepia": "Sepia",
41 | "dark-reader-config-enabled": "Enabled",
42 | "dark-reader-config-performance-warning": "Please note, this extension may cause articles to load a bit slower than usual.\n",
43 | "dark-reader-config-title": "Dark Reader extension preferences",
44 | "dictionary-manager-alert-edit-name-failure": "Failed to update dictionary name.",
45 | "dictionary-manager-alert-delete-dictionary-failure": "Failed to delete dictionary.",
46 | "dictionary-manager-alert-failure-reordering": "Failed to reorder dictionaries.",
47 | "dictionary-manager-dialogue-edit-name-title": "Edit dictionary name",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "Are you sure you want to delete ‘{0}’ from the database? The dictionary files will not be deleted.",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "Delete dictionary",
50 | "dictionary-manager-dialogue-name-placeholder": "new dictionary name",
51 | "drawer-library-label": "Library",
52 | "drawer-query-label": "Query",
53 | "drawer-reconnect-label": "Reconnect",
54 | "drawer-settings-label": "Settings",
55 | "generic-alert-invalid-number": "Invalid number.",
56 | "generic-cancel": "Cancel",
57 | "generic-delete": "Delete",
58 | "generic-edit": "Edit",
59 | "generic-no": "No",
60 | "generic-ok": "OK",
61 | "generic-rename": "Rename",
62 | "generic-yes": "Yes",
63 | "group-manager-alert-empty-name": "Group name cannot be empty.",
64 | "group-manager-alert-failure-adding-dictionary": "Failed to add dictionary to group.",
65 | "group-manager-alert-failure-adding-group": "Failed to add group.",
66 | "group-manager-alert-failure-changing-languages": "Failed to change the group's languages.",
67 | "group-manager-alert-failure-removing-dictionary": "Failed to remove dictionary from group.",
68 | "group-manager-alert-failure-removing-group": "Failed to remove group.",
69 | "group-manager-alert-failure-renaming-group": "Failed to rename group.",
70 | "group-manager-alert-failure-reordering-groups": "Failed to reorder groups.",
71 | "group-manager-alert-invalid-language-code": "Invalid language code(s) found.",
72 | "group-manager-alert-name-exists": "A group with the same name already exists.",
73 | "group-manager-card-title-dictionaries": "Dictionaries",
74 | "group-manager-card-title-languages": "Languages",
75 | "group-manager-dialogue-add-title": "Add group",
76 | "group-manager-dialogue-add-content": "Group name",
77 | "group-manager-dialogue-change-languages-content": "A comma separated list of two-letter ISO-639-1 language codes. It cannot be empty.",
78 | "group-manager-dialogue-change-languages-title": "Change the group's languages",
79 | "group-manager-dialogue-delete-content": "Are you sure you want to delete the group ‘{0}’?",
80 | "group-manager-dialogue-delete-title": "Delete group",
81 | "group-manager-dialogue-rename-title": "Rename group",
82 | "library-screen-groups": "Groups",
83 | "library-screen-sources": "Sources",
84 | "placeholder-search": "Search…",
85 | "query-screen-failure-fetch-articles": "Failed to fetch articles.",
86 | "query-screen-failure-fetch-suggestions": "Failed to fetch suggestions.",
87 | "script-name-arabic": "Arabic",
88 | "script-name-bengali": "Bengali",
89 | "script-name-chinese-simplified": "Chinese, Simplified",
90 | "script-name-chinese-traditional": "Chinese, Traditional",
91 | "script-name-cyrillic": "Cyrillic",
92 | "script-name-devanagari": "Devanagari",
93 | "script-name-greek": "Greek",
94 | "script-name-gujarati": "Gujarati",
95 | "script-name-gurmukhi": "Gurmukhi",
96 | "script-name-hebrew": "Hebrew",
97 | "script-name-japanese": "Japanese",
98 | "script-name-kannada": "Kannada",
99 | "script-name-khmer": "Khmer",
100 | "script-name-korean": "Korean",
101 | "script-name-latin": "Latin",
102 | "script-name-malayalam": "Malayalam",
103 | "script-name-myanmar": "Burmese",
104 | "script-name-oriya": "Oriya",
105 | "script-name-sinhala": "Sinhalese",
106 | "script-name-tamil": "Tamil",
107 | "script-name-telugu": "Telugu",
108 | "script-name-thai": "Thai",
109 | "script-name-tibetan": "Tibetan",
110 | "script-name-vietnamese": "Vietnamese",
111 | "settings-screen-dark-text-colour-title": "Colour of the text in dark mode",
112 | "settings-screen-font-family-title": "Font",
113 | "settings-screen-recreate-ngram-description": "It could be very slow.",
114 | "settings-screen-server-address-title": "Server address",
115 | "settings-screen-size-history-title": "History size",
116 | "settings-screen-size-suggestions-title": "Suggestion size",
117 | "source-manager-alert-duplicate": "The source already exists.",
118 | "source-manager-alert-empty-source": "Source cannot be empty.",
119 | "source-manager-alert-failure-adding-source": "Failed to add source.",
120 | "source-manager-alert-failure-deleting-source": "Failed to delete source.",
121 | "source-manager-alert-failure-fetching-sources": "Failed to fetch sources.",
122 | "source-manager-alert-failure-rescaning-sources": "Failed to rescan sources.",
123 | "source-manager-alert-invalid-source": "Invalid source.",
124 | "source-manager-dialogue-add-source-title": "Add source",
125 | "source-manager-dialogue-rescanning-sources-title": "Rescanning sources",
126 | "source-manager-confirm-delete-title": "Delete source",
127 | "source-manager-confirm-delete-content": "Are you sure you want to delete the source {0} ?"
128 | }
--------------------------------------------------------------------------------
/src/translations/en-GB.json:
--------------------------------------------------------------------------------
1 | {
2 | "additional-fonts-dialogue-content": "Download additional fonts from Google Fonts.\nSome fonts are rather large, so please only use when you see little boxes or question marks in the text.",
3 | "additional-fonts-dialogue-title": "Additional fonts",
4 | "add-dictionary-dialogue-alert-duplicate": "A dictionary with the same name already exists.",
5 | "add-dictionary-dialogue-alert-empty-filename": "Filename cannot be empty.",
6 | "add-dictionary-dialogue-alert-empty-name": "Dictionary name cannot be empty.",
7 | "add-dictionary-dialogue-alert-failure-adding": "Failed to add dictionary.",
8 | "add-dictionary-dialogue-alert-failure-validating": "Failed to validate dictionary.",
9 | "add-dictionary-dialogue-alert-invalid": "Invalid dictionary info.",
10 | "add-dictionary-dialogue-title": "Add dictionary",
11 | "add-dictionary-dialogue-content-group": "Group",
12 | "add-dictionary-dialogue-content-filename": "Filename",
13 | "add-dictionary-dialogue-content-format": "Format",
14 | "add-dictionary-dialogue-content-name": "Dictionary name",
15 | "app-context-message-failure-fetching-data": "Failed to fetch data from the server. Please check your connection or change the server address and try again.",
16 | "article-view-tap-to-search-dialogue-message": "Search for ‘{0}’?",
17 | "change-address-dialogue-message-unable-to-connect": "Unable to connect to server.",
18 | "change-address-dialogue-title": "Change server address",
19 | "change-colour-dialogue-content": "A value such as ‘white’ or ‘grey’ is recommended.\nThis setting only takes effect when the Dark Reader extension is disabled.",
20 | "change-colour-dialogue-title": "Change the colour of the text in the dark mode",
21 | "change-font-dialogue-font-sans-serif": "sans-serif",
22 | "change-font-dialogue-font-serif": "serif",
23 | "change-font-dialogue-title": "Change font",
24 | "change-size-history-dialogue-message-failure": "Failed to update history size.",
25 | "change-size-history-dialogue-placeholder": "a non-negative integer",
26 | "change-size-history-dialogue-title": "Change the size of history",
27 | "change-size-suggestion-dialogue-message-failure": "Failed to update the number of suggestions.",
28 | "change-size-suggestion-dialogue-placeholder": "a positive integer",
29 | "change-size-suggestion-dialogue-title": "Change the number of suggestions",
30 | "clear-history-dialogue-content": "Are you sure you want to clear history?",
31 | "clear-history-dialogue-message-failure": "Failed to clear history.",
32 | "clear-history-dialogue-title": "Clear history",
33 | "confirm-recreate-ngram-dialogue-content": "Are you sure you want to recreate the ngram index?",
34 | "confirm-recreate-ngram-dialogue-message-failure": "Failed to recreate ngram index.",
35 | "confirm-recreate-ngram-dialogue-message-in-progress": "Recreating ngram index…",
36 | "confirm-recreate-ngram-dialogue-message-success": "Recreated ngram index.",
37 | "confirm-recreate-ngram-dialogue-title": "Recreate ngram index",
38 | "dark-reader-config-brightness": "Brightness",
39 | "dark-reader-config-contrast": "Contrast",
40 | "dark-reader-config-sepia": "Sepia",
41 | "dark-reader-config-enabled": "Enabled",
42 | "dark-reader-config-performance-warning": "Please note, this extension may cause articles to load a bit slower than usual.\n",
43 | "dark-reader-config-title": "Dark Reader extension preferences",
44 | "dictionary-manager-alert-edit-name-failure": "Failed to update dictionary name.",
45 | "dictionary-manager-alert-delete-dictionary-failure": "Failed to delete dictionary.",
46 | "dictionary-manager-alert-failure-reordering": "Failed to reorder dictionaries.",
47 | "dictionary-manager-dialogue-edit-name-title": "Edit dictionary name",
48 | "dictionary-manager-dialogue-delete-dictionary-content": "Are you sure you want to delete ‘{0}’ from the database? The dictionary files will not be deleted.",
49 | "dictionary-manager-dialogue-delete-dictionary-title": "Delete dictionary",
50 | "dictionary-manager-dialogue-name-placeholder": "new dictionary name",
51 | "drawer-library-label": "Library",
52 | "drawer-query-label": "Query",
53 | "drawer-reconnect-label": "Reconnect",
54 | "drawer-settings-label": "Settings",
55 | "generic-alert-invalid-number": "Invalid number.",
56 | "generic-cancel": "Cancel",
57 | "generic-delete": "Delete",
58 | "generic-edit": "Edit",
59 | "generic-no": "No",
60 | "generic-ok": "OK",
61 | "generic-rename": "Rename",
62 | "generic-yes": "Yes",
63 | "group-manager-alert-empty-name": "Group name cannot be empty.",
64 | "group-manager-alert-failure-adding-dictionary": "Failed to add dictionary to group.",
65 | "group-manager-alert-failure-adding-group": "Failed to add group.",
66 | "group-manager-alert-failure-changing-languages": "Failed to change the group's languages.",
67 | "group-manager-alert-failure-removing-dictionary": "Failed to remove dictionary from group.",
68 | "group-manager-alert-failure-removing-group": "Failed to remove group.",
69 | "group-manager-alert-failure-renaming-group": "Failed to rename group.",
70 | "group-manager-alert-failure-reordering-groups": "Failed to reorder groups.",
71 | "group-manager-alert-invalid-language-code": "Invalid language code(s) found.",
72 | "group-manager-alert-name-exists": "A group with the same name already exists.",
73 | "group-manager-card-title-dictionaries": "Dictionaries",
74 | "group-manager-card-title-languages": "Languages",
75 | "group-manager-dialogue-add-title": "Add group",
76 | "group-manager-dialogue-add-content": "Group name",
77 | "group-manager-dialogue-change-languages-content": "A comma separated list of two-letter ISO-639-1 language codes. It cannot be empty.",
78 | "group-manager-dialogue-change-languages-title": "Change the group's languages",
79 | "group-manager-dialogue-delete-content": "Are you sure you want to delete the group ‘{0}’?",
80 | "group-manager-dialogue-delete-title": "Delete group",
81 | "group-manager-dialogue-rename-title": "Rename group",
82 | "library-screen-groups": "Groups",
83 | "library-screen-sources": "Sources",
84 | "placeholder-search": "Search…",
85 | "query-screen-failure-fetch-articles": "Failed to fetch articles.",
86 | "query-screen-failure-fetch-suggestions": "Failed to fetch suggestions.",
87 | "script-name-arabic": "Arabic",
88 | "script-name-bengali": "Bengali",
89 | "script-name-chinese-simplified": "Chinese, Simplified",
90 | "script-name-chinese-traditional": "Chinese, Traditional",
91 | "script-name-cyrillic": "Cyrillic",
92 | "script-name-devanagari": "Devanagari",
93 | "script-name-greek": "Greek",
94 | "script-name-gujarati": "Gujarati",
95 | "script-name-gurmukhi": "Gurmukhi",
96 | "script-name-hebrew": "Hebrew",
97 | "script-name-japanese": "Japanese",
98 | "script-name-kannada": "Kannada",
99 | "script-name-khmer": "Khmer",
100 | "script-name-korean": "Korean",
101 | "script-name-latin": "Latin",
102 | "script-name-malayalam": "Malayalam",
103 | "script-name-myanmar": "Burmese",
104 | "script-name-oriya": "Oriya",
105 | "script-name-sinhala": "Sinhalese",
106 | "script-name-tamil": "Tamil",
107 | "script-name-telugu": "Telugu",
108 | "script-name-thai": "Thai",
109 | "script-name-tibetan": "Tibetan",
110 | "script-name-vietnamese": "Vietnamese",
111 | "settings-screen-dark-text-colour-title": "Colour of the text in dark mode",
112 | "settings-screen-font-family-title": "Font",
113 | "settings-screen-recreate-ngram-description": "It could be very slow.",
114 | "settings-screen-server-address-title": "Server address",
115 | "settings-screen-size-history-title": "History size",
116 | "settings-screen-size-suggestions-title": "Suggestion size",
117 | "source-manager-alert-duplicate": "The source already exists.",
118 | "source-manager-alert-empty-source": "Source cannot be empty.",
119 | "source-manager-alert-failure-adding-source": "Failed to add source.",
120 | "source-manager-alert-failure-deleting-source": "Failed to delete source.",
121 | "source-manager-alert-failure-fetching-sources": "Failed to fetch sources.",
122 | "source-manager-alert-failure-rescaning-sources": "Failed to rescan sources.",
123 | "source-manager-alert-invalid-source": "Invalid source.",
124 | "source-manager-dialogue-add-source-title": "Add source",
125 | "source-manager-dialogue-rescanning-sources-title": "Rescanning sources",
126 | "source-manager-confirm-delete-title": "Delete source",
127 | "source-manager-confirm-delete-content": "Are you sure you want to delete the source {0} ?"
128 | }
--------------------------------------------------------------------------------