diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3e1db9c0..e8ab61e8f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -257,6 +257,16 @@ dependencies { } ``` +#### I18n library + +[`lib-i18n`](https://github.com/tachiyomiorg/tachiyomi-extensions/tree/master/lib/i18n) is a library for handling internationalization in the sources. It allows loading `.properties` files with messages located under the `res/raw` folder of each extension, that can be used to translate strings under the source. + +```gradle +dependencies { + implementation(project(':lib-i18n')) +} +``` + #### Additional dependencies If you find yourself needing additional functionality, you can add more dependencies to your `build.gradle` file. diff --git a/lib/i18n/build.gradle.kts b/lib/i18n/build.gradle.kts new file mode 100644 index 000000000..5eaa29645 --- /dev/null +++ b/lib/i18n/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + compileSdk = AndroidConfig.compileSdk + + defaultConfig { + minSdk = AndroidConfig.minSdk + targetSdk = AndroidConfig.targetSdk + } +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly(libs.kotlin.stdlib) +} diff --git a/lib/i18n/src/main/AndroidManifest.xml b/lib/i18n/src/main/AndroidManifest.xml new file mode 100644 index 000000000..04965588a --- /dev/null +++ b/lib/i18n/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/lib/i18n/src/main/java/eu/kanade/tachiyomi/lib/i18n/Intl.kt b/lib/i18n/src/main/java/eu/kanade/tachiyomi/lib/i18n/Intl.kt new file mode 100644 index 000000000..85132dfa5 --- /dev/null +++ b/lib/i18n/src/main/java/eu/kanade/tachiyomi/lib/i18n/Intl.kt @@ -0,0 +1,83 @@ +package eu.kanade.tachiyomi.lib.i18n + +import java.io.InputStreamReader +import java.text.Collator +import java.util.Locale +import java.util.PropertyResourceBundle + +/** + * A simple wrapper to make internationalization easier to use in sources. + * + * Message files should be put at the `/res/raw` folder, with the name + * `messages_{iso_639_1}.properties`, where `iso_639_1` should be using + * snake case and be in lowercase. + * + * To edit the strings, use the official JetBrain's + * [Resource Bundle Editor plugin](https://plugins.jetbrains.com/plugin/17035-resource-bundle-editor). + * + * Make sure to configure Android Studio to save Properties files as UTF-8 as well. + * You can refer to this [documentation](https://www.jetbrains.com/help/idea/properties-files.html#1cbc434e) + * on how to do so. + */ +class Intl( + private val language: String, + private val baseLanguage: String, + private val availableLanguages: Set, + private val classLoader: ClassLoader, + private val createMessageFileName: (String) -> String = { createDefaultMessageFileName(it) } +) { + + val chosenLanguage: String = when (language) { + in availableLanguages -> language + else -> baseLanguage + } + + private val locale: Locale = Locale.forLanguageTag(chosenLanguage) + + val collator: Collator = Collator.getInstance(locale) + + private val baseBundle: PropertyResourceBundle by lazy { createBundle(baseLanguage) } + + private val bundle: PropertyResourceBundle by lazy { + if (chosenLanguage == baseLanguage) baseBundle else createBundle(chosenLanguage) + } + + /** + * Returns the string from the message file. If the [key] is not present + * in the current language, the English value will be returned. If the [key] + * is also not present in English, the [key] surrounded by brackets will be returned. + */ + operator fun get(key: String): String = when { + bundle.containsKey(key) -> bundle.getString(key) + baseBundle.containsKey(key) -> baseBundle.getString(key) + else -> "[$key]" + } + + fun languageDisplayName(localeCode: String): String = + Locale.forLanguageTag(localeCode) + .getDisplayName(locale) + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() } + + /** + * Creates a [PropertyResourceBundle] instance from the language specified. + * The expected message file will be loaded from the `res/raw`. + * + * The [PropertyResourceBundle] is used directly instead of [java.util.ResourceBundle] + * because the later has issues with UTF-8 files in Java 8, which would need + * the message files to be saved in ISO-8859-1, making the file readability bad. + */ + private fun createBundle(lang: String): PropertyResourceBundle { + val fileName = createMessageFileName(lang) + val fileContent = classLoader.getResourceAsStream(fileName) + + return PropertyResourceBundle(InputStreamReader(fileContent, "UTF-8")) + } + + companion object { + fun createDefaultMessageFileName(lang: String): String { + val langSnakeCase = lang.replace("-", "_").lowercase() + + return "res/raw/messages_$langSnakeCase.properties" + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 025aa944a..9d8ac45a2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ include(":core") -listOf("dataimage", "unpacker", "cryptoaes", "textinterceptor", "synchrony").forEach { +listOf("dataimage", "unpacker", "cryptoaes", "textinterceptor", "synchrony", "i18n").forEach { include(":lib-$it") project(":lib-$it").projectDir = File("lib/$it") } diff --git a/src/all/mangadex/build.gradle b/src/all/mangadex/build.gradle index cfdda1e0e..d28b63e97 100644 --- a/src/all/mangadex/build.gradle +++ b/src/all/mangadex/build.gradle @@ -6,8 +6,12 @@ ext { extName = 'MangaDex' pkgNameSuffix = 'all.mangadex' extClass = '.MangaDexFactory' - extVersionCode = 181 + extVersionCode = 182 isNsfw = true } +dependencies { + implementation(project(":lib-i18n")) +} + apply from: "$rootDir/common.gradle" diff --git a/src/all/mangadex/res/raw/messages_en.properties b/src/all/mangadex/res/raw/messages_en.properties new file mode 100644 index 000000000..c411bb60a --- /dev/null +++ b/src/all/mangadex/res/raw/messages_en.properties @@ -0,0 +1,145 @@ +alternative_titles=Alternative titles: +alternative_titles_in_description=Alternative titles in description +alternative_titles_in_description_summary=Include a manga's alternative titles at the end of its description +block_group_by_uuid=Block groups by UUID +block_group_by_uuid_summary=Chapters from blocked groups will not show up in Latest or Manga feed. Enter as a Comma-separated list of group UUIDs +block_uploader_by_uuid=Block uploader by UUID +block_uploader_by_uuid_summary=Chapters from blocked uploaders will not show up in Latest or Manga feed. Enter as a Comma-separated list of uploader UUIDs +content=Content +content_gore=Gore +content_rating=Content rating +content_rating_erotica=Erotica +content_rating_genre=Content rating: %s +content_rating_pornographic=Pornographic +content_rating_safe=Safe +content_rating_suggestive=Suggestive +content_sexual_violence=Sexual violence +cover_quality=Cover quality +cover_quality_low=Low +cover_quality_medium=Medium +cover_quality_original=Original +data_saver=Data saver +data_saver_summary=Enables smaller, more compressed images +excluded_tags_mode=Excluded tags mode +filter_original_languages=Filter original languages +filter_original_languages_summary=Only show content that was originally published in the selected languages in both latest and browse +format=Format +format_adaptation=Adaptation +format_anthology=Anthology +format_award_winning=Award winning +format_doujinshi=Doujinshi +format_fan_colored=Fan colored +format_full_color=Full color +format_long_strip=Long strip +format_official_colored=Official colored +format_oneshot=Oneshot +format_user_created=User created +format_web_comic=Web comic +format_yonkoma=4-Koma +genre=Genre +genre_action=Action +genre_adventure=Adventure +genre_boys_love=Boy's Love +genre_comedy=Comedy +genre_crime=Crime +genre_drama=Drama +genre_fantasy=Fantasy +genre_girls_love=Girl's Love +genre_historical=Historical +genre_horror=Horror +genre_isekai=Isekai +genre_magical_girls=Magical girls +genre_mecha=Mecha +genre_medical=Medical +genre_mystery=Mystery +genre_philosophical=Philosophical +genre_romance=Romance +genre_sci_fi=Sci-Fi +genre_slice_of_life=Slice of life +genre_sports=Sports +genre_superhero=Superhero +genre_thriller=Thriller +genre_tragedy=Tragedy +genre_wuxia=Wuxia +has_available_chapters=Has available chapters +included_tags_mode=Included tags mode +invalid_author_id=Not a valid author ID +invalid_group_id=Not a valid group ID +invalid_uuids=The text contains invalid UUIDs +migrate_warning=Migrate this entry from MangaDex to MangaDex to update it +mode_and=And +mode_or=Or +no_group=No Group +no_series_in_list=No series in the list +original_language=Original language +original_language_filter_chinese=%s (Manhua) +original_language_filter_japanese=%s (Manga) +original_language_filter_korean=%s (Manhwa) +publication_demographic=Publication demographic +publication_demographic_josei=Josei +publication_demographic_none=None +publication_demographic_seinen=Seinen +publication_demographic_shoujo=Shoujo +publication_demographic_shounen=Shounen +sort=Sort +sort_alphabetic=Alphabetic +sort_chapter_uploaded_at=Chapter uploaded at +sort_content_created_at=Content created at +sort_content_info_updated_at=Content info updated at +sort_number_of_follows=Number of follows +sort_rating=Rating +sort_relevance=Relevance +sort_year=Year +standard_content_rating=Default content rating +standard_content_rating_summary=Show content with the selected ratings by default +standard_https_port=Use HTTPS port 443 only +standard_https_port_summary=Enable to only request image servers that use port 443. This allows users with stricter firewall restrictions to access MangaDex images +status=Status +status_cancelled=Cancelled +status_completed=Completed +status_hiatus=Hiatus +status_ongoing=Ongoing +tags_mode=Tags mode +theme=Theme +theme_aliens=Aliens +theme_animals=Animals +theme_cooking=Cooking +theme_crossdressing=Crossdressing +theme_delinquents=Delinquents +theme_demons=Demons +theme_gender_swap=Genderswap +theme_ghosts=Ghosts +theme_gyaru=Gyaru +theme_harem=Harem +theme_incest=Incest +theme_loli=Loli +theme_mafia=Mafia +theme_magic=Magic +theme_martial_arts=Martial arts +theme_military=Military +theme_monster_girls=Monster girls +theme_monsters=Monsters +theme_music=Music +theme_ninja=Ninja +theme_office_workers=Office workers +theme_police=Police +theme_post_apocalyptic=Post-apocalyptic +theme_psychological=Psychological +theme_reincarnation=Reincarnation +theme_reverse_harem=Reverse harem +theme_samurai=Samurai +theme_school_life=School life +theme_shota=Shota +theme_supernatural=Supernatural +theme_survival=Survival +theme_time_travel=Time travel +theme_traditional_games=Traditional games +theme_vampires=Vampires +theme_video_games=Video games +theme_villainess=Vilania +theme_virtual_reality=Virtual reality +theme_zombies=Zombies +try_using_first_volume_cover=Attempt to use the first volume cover as cover +try_using_first_volume_cover_summary=May need to manually refresh entries already in library. Otherwise, clear database to have new covers to show up. +unable_to_process_chapter_request=Unable to process Chapter request. HTTP code: %d +uploaded_by=Uploaded by %s \ No newline at end of file diff --git a/src/all/mangadex/res/raw/messages_es.properties b/src/all/mangadex/res/raw/messages_es.properties new file mode 100644 index 000000000..fc1dc27ab --- /dev/null +++ b/src/all/mangadex/res/raw/messages_es.properties @@ -0,0 +1,108 @@ +block_group_by_uuid=Bloquear grupos por UUID +block_group_by_uuid_summary=Los capítulos de los grupos bloqueados no aparecerán en Recientes o en el Feed de mangas. Introduce una coma para separar la lista de UUIDs +block_uploader_by_uuid=Bloquear uploader por UUID +block_uploader_by_uuid_summary=Los capítulos de los uploaders bloqueados no aparecerán en Recientes o en el Feed de mangas. Introduce una coma para separar la lista de UUIDs +content=Contenido +content_rating=Clasificación de contenido +content_rating_erotica=Erótico +content_rating_genre=Clasificación: %s +content_rating_pornographic=Pornográfico +content_rating_safe=Seguro +content_rating_suggestive=Sugestivo +content_sexual_violence=Violencia sexual +cover_quality=Calidad de la portada +cover_quality_low=Bajo +cover_quality_medium=Medio +data_saver=Ahorro de datos +data_saver_summary=Utiliza imágenes más pequeñas y más comprimidas +excluded_tags_mode=Modo de etiquetas excluidas +filter_original_languages=Filtrar por lenguajes +filter_original_languages_summary=Muestra solo el contenido publicado en los idiomas seleccionados en recientes y en la búsqueda +format=Formato +format_adaptation=Adaptación +format_anthology=Antología +format_award_winning=Ganador de premio +format_fan_colored=Coloreado por fans +format_full_color=Todo a color +format_long_strip=Tira larga +format_official_colored=Coloreo oficial +format_user_created=Creado por usuario +genre=Genero +genre_action=Acción +genre_adventure=Aventura +genre_comedy=Comedia +genre_crime=Crimen +genre_fantasy=Fantasia +genre_historical=Histórico +genre_magical_girls=Chicas mágicas +genre_medical=Medico +genre_mystery=Misterio +genre_philosophical=Filosófico +genre_sci_fi=Ciencia ficción +genre_slice_of_life=Recuentos de la vida +genre_sports=Deportes +genre_superhero=Superhéroes +genre_tragedy=Tragedia +has_available_chapters=Tiene capítulos disponibles +included_tags_mode=Modo de etiquetas incluidas +invalid_author_id=ID de autor inválida +invalid_group_id=ID de grupo inválida +migrate_warning=Migre la entrada MangaDex a MangaDex para actualizarla +mode_and=Y +mode_or=O +no_group=Sin grupo +no_series_in_list=No hay series en la lista +original_language=Lenguaje original +publication_demographic=Demografía +publication_demographic_none=Ninguna +sort=Ordenar +sort_alphabetic=Alfabeticamente +sort_chapter_uploaded_at=Capítulo subido en +sort_content_created_at=Contenido creado en +sort_content_info_updated_at=Información del contenido actualizada en +sort_number_of_follows=Número de seguidores +sort_rating=Calificación +sort_relevance=Relevancia +sort_year=Año +standard_content_rating=Clasificación de contenido por defecto +standard_content_rating_summary=Muestra el contenido con la clasificación de contenido seleccionada por defecto +standard_https_port=Utilizar el puerto 443 de HTTPS +standard_https_port_summary=Habilite esta opción solicitar las imágenes a los servidores que usan el puerto 443. Esto permite a los usuarios con restricciones estrictas de firewall acceder a las imagenes en MangaDex +status=Estado +status_cancelled=Cancelado +status_completed=Completado +status_hiatus=Pausado +status_ongoing=Publicandose +tags_mode=Modo de etiquetas +theme=Tema +theme_aliens=Alienígenas +theme_animals=Animales +theme_cooking=Cocina +theme_crossdressing=Travestismo +theme_delinquents=Delincuentes +theme_demons=Demonios +theme_gender_swap=Cambio de sexo +theme_ghosts=Fantasmas +theme_incest=Incesto +theme_magic=Magia +theme_martial_arts=Artes marciales +theme_military=Militar +theme_monster_girls=Chicas monstruo +theme_monsters=Monstruos +theme_music=Musica +theme_office_workers=Oficinistas +theme_police=Policial +theme_post_apocalyptic=Post-apocalíptico +theme_psychological=Psicológico +theme_reincarnation=Reencarnación +theme_reverse_harem=Harem inverso +theme_school_life=Vida escolar +theme_supernatural=Sobrenatural +theme_survival=Supervivencia +theme_time_travel=Viaje en el tiempo +theme_traditional_games=Juegos tradicionales +theme_vampires=Vampiros +theme_villainess=Villana +theme_virtual_reality=Realidad virtual +unable_to_process_chapter_request=No se ha podido procesar la solicitud del capítulo. Código HTTP: %d +uploaded_by=Subido por %s \ No newline at end of file diff --git a/src/all/mangadex/res/raw/messages_pt_br.properties b/src/all/mangadex/res/raw/messages_pt_br.properties new file mode 100644 index 000000000..f0d66530b --- /dev/null +++ b/src/all/mangadex/res/raw/messages_pt_br.properties @@ -0,0 +1,118 @@ +alternative_titles=Títulos alternativos: +alternative_titles_in_description=Títulos alternativos na descrição +alternative_titles_in_description_summary=Inclui os títulos alternativos das séries no final de cada descrição +block_group_by_uuid=Bloquear grupos por UUID +block_group_by_uuid_summary=Capítulos de grupos bloqueados não irão aparecer no feed de Recentes ou Mangás. Digite uma lista de UUIDs dos grupos separados por vírgulas +block_uploader_by_uuid=Bloquear uploaders por UUID +block_uploader_by_uuid_summary=Capítulos de usuários bloqueados não irão aparecer no feed de Recentes ou Mangás. Digite uma lista de UUIDs dos usuários separados por vírgulas +content=Conteúdo +content_rating=Classificação de conteúdo +content_rating_erotica=Erótico +content_rating_genre=Classificação: %s +content_rating_pornographic=Pornográfico +content_rating_safe=Seguro +content_rating_suggestive=Sugestivo +content_sexual_violence=Violência sexual +cover_quality=Qualidade da capa +cover_quality_low=Baixa +cover_quality_medium=Média +data_saver=Economia de dados +data_saver_summary=Utiliza imagens menores e mais compactadas +excluded_tags_mode=Modo de exclusão de tags +filter_original_languages=Filtrar os idiomas originais +filter_original_languages_summary=Mostra somente conteúdos que foram publicados originalmente nos idiomas selecionados nas seções de recentes e navegar +format=Formato +format_adaptation=Adaptação +format_anthology=Antologia +format_award_winning=Premiado +format_fan_colored=Colorizado por fãs +format_full_color=Colorido +format_long_strip=Vertical +format_official_colored=Colorizado oficialmente +format_user_created=Criado por usuários +genre=Gênero +genre_action=Ação +genre_adventure=Aventura +genre_comedy=Comédia +genre_crime=Crime +genre_fantasy=Fantasia +genre_historical=Histórico +genre_magical_girls=Garotas mágicas +genre_medical=Médico +genre_mystery=Mistério +genre_philosophical=Filosófico +genre_sci_fi=Ficção científica +genre_slice_of_life=Cotidiano +genre_sports=Esportes +genre_superhero=Super-heroi +genre_tragedy=Tragédia +has_available_chapters=Há capítulos disponíveis +included_tags_mode=Modo de inclusão de tags +invalid_author_id=ID do autor inválido +invalid_group_id=ID do grupo inválido +invalid_uuids=O texto contém UUIDs inválidos +migrate_warning=Migre esta entrada do MangaDex para o MangaDex para atualizar +mode_and=E +mode_or=Ou +no_group=Sem grupo +no_series_in_list=Sem séries na lista +original_language=Idioma original +original_language_filter_japanese=%s (Mangá) +publication_demographic=Demografia da publicação +publication_demographic_none=Nenhuma +sort=Ordenar +sort_alphabetic=Alfabeticamente +sort_chapter_uploaded_at=Upload do capítulo +sort_content_created_at=Criação do conteúdo +sort_content_info_updated_at=Atualização das informações +sort_number_of_follows=Número de seguidores +sort_rating=Nota +sort_relevance=Relevância +sort_year=Ano de lançamento +standard_content_rating=Classificação de conteúdo padrão +standard_content_rating_summary=Mostra os conteúdos com as classificações selecionadas por padrão +standard_https_port=Utilizar somente a porta 443 do HTTPS +standard_https_port_summary=Ative para fazer requisições em somente servidores de imagem que usem a porta 443. Isso permite com que usuários com regras mais restritas de firewall possam acessar as imagens do MangaDex. +status=Estado +status_cancelled=Cancelado +status_completed=Completo +status_hiatus=Hiato +status_ongoing=Em andamento +tags_mode=Modo das tags +theme=Tema +theme_aliens=Alienígenas +theme_animals=Animais +theme_cooking=Culinária +theme_delinquents=Delinquentes +theme_demons=Demônios +theme_gender_swap=Troca de gêneros +theme_ghosts=Fantasmas +theme_harem=Harém +theme_incest=Incesto +theme_mafia=Máfia +theme_magic=Magia +theme_martial_arts=Artes marciais +theme_military=Militar +theme_monster_girls=Garotas monstro +theme_monsters=Monstros +theme_music=Musical +theme_office_workers=Funcionários de escritório +theme_police=Policial +theme_post_apocalyptic=Pós-apocalíptico +theme_psychological=Psicológico +theme_reincarnation=Reencarnação +theme_reverse_harem=Harém reverso +theme_school_life=Vida escolar +theme_supernatural=Sobrenatural +theme_survival=Sobrevivência +theme_time_travel=Viagem no tempo +theme_traditional_games=Jogos tradicionais +theme_vampires=Vampiros +theme_video_games=Videojuegos +theme_villainess=Villainess +theme_virtual_reality=Realidade virtual +theme_zombies=Zumbis +try_using_first_volume_cover=Tentar usar a capa do primeiro volume como capa +try_using_first_volume_cover_summary=Pode ser necessário atualizar os itens já adicionados na biblioteca. Alternativamente, limpe o banco de dados para as novas capas aparecerem. +unable_to_process_chapter_request=Não foi possível processar a requisição do capítulo. Código HTTP: %d +uploaded_by=Enviado por %s \ No newline at end of file diff --git a/src/all/mangadex/res/raw/messages_ru.properties b/src/all/mangadex/res/raw/messages_ru.properties new file mode 100644 index 000000000..fbc49b49f --- /dev/null +++ b/src/all/mangadex/res/raw/messages_ru.properties @@ -0,0 +1,138 @@ +block_group_by_uuid=Заблокировать группы по UUID +block_group_by_uuid_summary=Главы от заблокированных групп не будут отображаться в последних обновлениях и в списке глав тайтла. Введите через запятую список UUID групп. +block_uploader_by_uuid=Заблокировать загрузчика по UUID +block_uploader_by_uuid_summary=Главы от заблокированных загрузчиков не будут отображаться в последних обновлениях и в списке глав тайтла. Введите через запятую список UUID загрузчиков. +content=Неприемлемый контент +content_gore=Жестокость +content_rating=Рейтинг контента +content_rating_erotica=Эротический +content_rating_genre=Рейтинг контента: %s +content_rating_pornographic=Порнографический +content_rating_safe=Безопасный +content_rating_suggestive=Намекающий +content_sexual_violence=Сексуальное насилие +cover_quality=Качество обложки +cover_quality_low=Низкое +cover_quality_medium=Среднее +cover_quality_original=Оригинальное +data_saver=Экономия трафика +data_saver_summary=Использует меньшие по размеру, сжатые изображения +excluded_tags_mode=Исключая +filter_original_languages=Фильтр по языку оригинала +filter_original_languages_summary=Показывать тайтлы которые изначально были выпущены только в выбранных языках в последних обновлениях и при поиске +format=Формат +format_adaptation=Адаптация +format_anthology=Антология +format_award_winning=Отмеченный наградами +format_doujinshi=Додзинси +format_fan_colored=Раскрашенная фанатами +format_full_color=В цвете +format_long_strip=Веб +format_official_colored=Официально раскрашенная +format_oneshot=Сингл +format_user_created=Созданная пользователями +format_web_comic=Веб-комикс +format_yonkoma=Ёнкома +genre=Жанр +genre_action=Боевик +genre_adventure=Приключения +genre_boys_love=BL +genre_comedy=Комедия +genre_crime=Криминал +genre_drama=Драма +genre_fantasy=Фэнтези +genre_girls_love=GL +genre_historical=История +genre_horror=Ужасы +genre_isekai=Исекай +genre_magical_girls=Махо-сёдзё +genre_mecha=Меха +genre_medical=Медицина +genre_mystery=Мистика +genre_philosophical=Философия +genre_romance=Романтика +genre_sci_fi=Научная фантастика +genre_slice_of_life=Повседневность +genre_sports=Спорт +genre_superhero=Супергерои +genre_thriller=Триллер +genre_tragedy=Трагедия +genre_wuxia=Культивация +has_available_chapters=Есть главы +included_tags_mode=Включая +invalid_author_id=Недействительный ID автора +invalid_group_id=Недействительный ID группы +mode_and=И +mode_or=Или +no_group=Нет группы +no_series_in_list=Лист пуст +original_language=Язык оригинала +original_language_filter_chinese=%s (Манхуа) +original_language_filter_japanese=%s (Манга) +original_language_filter_korean=%s (Манхва) +publication_demographic=Целевая аудитория +publication_demographic_josei=Дзёсэй +publication_demographic_none=Нет +publication_demographic_seinen=Сэйнэн +publication_demographic_shoujo=Сёдзё +publication_demographic_shounen=Сёнэн +sort=Сортировать по +sort_alphabetic=Алфавиту +sort_chapter_uploaded_at=Загруженной главе +sort_content_created_at=По дате создания +sort_content_info_updated_at=По дате обновления +sort_number_of_follows=Количеству фолловеров +sort_rating=Популярности +sort_relevance=Лучшему соответствию +sort_year=Год +standard_content_rating=Рейтинг контента по умолчанию +standard_content_rating_summary=Показывать контент с выбранным рейтингом по умолчанию +standard_https_port=Использовать только HTTPS порт 443 +standard_https_port_summary=Запрашивает изображения только с серверов которые используют порт 443. Это позволяет пользователям со строгими правилами брандмауэра загружать изображения с MangaDex. +status=Статус +status_cancelled=Отменён +status_completed=Завершён +status_hiatus=Приостановлен +status_ongoing=Онгоинг +tags_mode=Режим поиска +theme=Теги +theme_aliens=Инопланетяне +theme_animals=Животные +theme_cooking=Животные +theme_crossdressing=Кроссдрессинг +theme_delinquents=Хулиганы +theme_demons=Демоны +theme_gender_swap=Смена гендера +theme_ghosts=Призраки +theme_gyaru=Гяру +theme_harem=Гарем +theme_incest=Инцест +theme_loli=Лоли +theme_mafia=Мафия +theme_magic=Магия +theme_martial_arts=Боевые исскуства +theme_military=Военные +theme_monster_girls=Монстродевушки +theme_monsters=Монстры +theme_music=Музыка +theme_ninja=Ниндзя +theme_office_workers=Офисные работники +theme_police=Полиция +theme_post_apocalyptic=Постапокалиптика +theme_psychological=Психология +theme_reincarnation=Реинкарнация +theme_reverse_harem=Обратный гарем +theme_samurai=Самураи +theme_school_life=Школа +theme_shota=Шота +theme_supernatural=Сверхъестественное +theme_survival=Выживание +theme_time_travel=Путешествие во времени +theme_traditional_games=Путешествие во времени +theme_vampires=Вампиры +theme_video_games=Видеоигры +theme_villainess=Злодейка +theme_virtual_reality=Виртуальная реальность +theme_zombies=Зомби +unable_to_process_chapter_request=Не удалось обработать ссылку на главу. Ошибка: %d +uploaded_by=Загрузил %s \ No newline at end of file diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt index b7f8eaa11..95eca0a26 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.extension.all.mangadex +import eu.kanade.tachiyomi.lib.i18n.Intl import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone @@ -49,8 +50,8 @@ object MDConstants { return "${coverQualityPref}_$dexLang" } - fun getCoverQualityPreferenceEntries(intl: MangaDexIntl) = - arrayOf(intl.coverQualityOriginal, intl.coverQualityMedium, intl.coverQualityLow) + fun getCoverQualityPreferenceEntries(intl: Intl) = + arrayOf(intl["cover_quality_original"], intl["cover_quality_medium"], intl["cover_quality_low"]) fun getCoverQualityPreferenceEntryValues() = arrayOf("", ".512.jpg", ".256.jpg") diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt index 2bb9a1495..95f5f9183 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt @@ -82,9 +82,10 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .addQueryParameter("includes[]", MDConstants.coverArt) .addQueryParameter("contentRating[]", preferences.contentRating) .addQueryParameter("originalLanguage[]", preferences.originalLanguages) + .build() return GET( - url = url.build().toString(), + url = url, headers = headers, cache = CacheControl.FORCE_NETWORK, ) @@ -170,8 +171,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) .addQueryParameter("includeFuturePublishAt", "0") .addQueryParameter("includeEmptyPages", "0") + .build() - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return GET(url, headers, CacheControl.FORCE_NETWORK) } // Search manga section @@ -218,7 +220,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .asObservable() .map { response -> if (response.isSuccessful.not()) { - throw Exception(helper.intl.unableToProcessChapterRequest(response.code)) + throw Exception(helper.intl["unable_to_process_chapter_request"].format(response.code)) } response.parseAs().data!!.relationships @@ -249,7 +251,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St query.startsWith(MDConstants.prefixGrpSearch) -> { val groupId = query.removePrefix(MDConstants.prefixGrpSearch) if (!helper.containsUuid(groupId)) { - throw Exception(helper.intl.invalidGroupId) + throw Exception(helper.intl["invalid_group_id"]) } tempUrl.addQueryParameter("group", groupId) @@ -258,7 +260,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St query.startsWith(MDConstants.prefixAuthSearch) -> { val authorId = query.removePrefix(MDConstants.prefixAuthSearch) if (!helper.containsUuid(authorId)) { - throw Exception(helper.intl.invalidAuthorId) + throw Exception(helper.intl["invalid_author_id"]) } tempUrl.addQueryParameter("authorOrArtist", authorId) @@ -294,7 +296,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val amount = listDtoFiltered.count() if (amount < 1) { - throw Exception(helper.intl.noSeriesInList) + throw Exception(helper.intl["no_series_in_list"]) } val minIndex = (page - 1) * MDConstants.mangaLimit @@ -311,7 +313,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St url.addQueryParameter("ids[]", ids) - val mangaRequest = GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + val mangaRequest = GET(url.build(), headers, CacheControl.FORCE_NETWORK) val mangaResponse = client.newCall(mangaRequest).execute() val mangaList = searchMangaListParse(mangaResponse) @@ -361,8 +363,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St MDConstants.defaultBlockedGroups + preferences.blockedGroups, ) .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) + .build() - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return GET(url, headers, CacheControl.FORCE_NETWORK) } // Manga Details section @@ -382,15 +385,16 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St */ override fun mangaDetailsRequest(manga: SManga): Request { if (!helper.containsUuid(manga.url.trim())) { - throw Exception(helper.intl.migrateWarning) + throw Exception(helper.intl["migrate_warning"]) } val url = (MDConstants.apiUrl + manga.url).toHttpUrl().newBuilder() .addQueryParameter("includes[]", MDConstants.coverArt) .addQueryParameter("includes[]", MDConstants.author) .addQueryParameter("includes[]", MDConstants.artist) + .build() - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return GET(url, headers, CacheControl.FORCE_NETWORK) } override fun mangaDetailsParse(response: Response): SManga { @@ -453,7 +457,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .addQueryParameter("locales[]", locales.toSet()) .addQueryParameter("limit", limit.toString()) .addQueryParameter("offset", "0") - .toString() + .build() val result = runCatching { client.newCall(GET(apiUrl, headers)).execute().parseAs().data @@ -482,7 +486,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St */ override fun chapterListRequest(manga: SManga): Request { if (!helper.containsUuid(manga.url)) { - throw Exception(helper.intl.migrateWarning) + throw Exception(helper.intl["migrate_warning"]) } return paginatedChapterListRequest(helper.getUUIDFromUrl(manga.url), 0) @@ -499,8 +503,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .addQueryParameter("contentRating[]", "pornographic") .addQueryParameter("excludedGroups[]", preferences.blockedGroups) .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) + .build() - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return GET(url, headers, CacheControl.FORCE_NETWORK) } override fun chapterListParse(response: Response): List { @@ -542,7 +547,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St override fun pageListRequest(chapter: SChapter): Request { if (!helper.containsUuid(chapter.url)) { - throw Exception(helper.intl.migrateWarning) + throw Exception(helper.intl["migrate_warning"]) } val chapterId = chapter.url.substringAfter("/chapter/") @@ -586,7 +591,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St override fun setupPreferenceScreen(screen: PreferenceScreen) { val coverQualityPref = ListPreference(screen.context).apply { key = MDConstants.getCoverQualityPreferenceKey(dexLang) - title = helper.intl.coverQuality + title = helper.intl["cover_quality"] entries = MDConstants.getCoverQualityPreferenceEntries(helper.intl) entryValues = MDConstants.getCoverQualityPreferenceEntryValues() setDefaultValue(MDConstants.getCoverQualityPreferenceDefaultValue()) @@ -605,8 +610,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val tryUsingFirstVolumeCoverPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getTryUsingFirstVolumeCoverPrefKey(dexLang) - title = helper.intl.tryUsingFirstVolumeCover - summary = helper.intl.tryUsingFirstVolumeCoverSummary + title = helper.intl["try_using_first_volume_cover"] + summary = helper.intl["try_using_first_volume_cover_summary"] setDefaultValue(MDConstants.tryUsingFirstVolumeCoverDefault) setOnPreferenceChangeListener { _, newValue -> @@ -620,8 +625,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val dataSaverPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getDataSaverPreferenceKey(dexLang) - title = helper.intl.dataSaver - summary = helper.intl.dataSaverSummary + title = helper.intl["data_saver"] + summary = helper.intl["data_saver_summary"] setDefaultValue(false) setOnPreferenceChangeListener { _, newValue -> @@ -635,8 +640,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val standardHttpsPortPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getStandardHttpsPreferenceKey(dexLang) - title = helper.intl.standardHttpsPort - summary = helper.intl.standardHttpsPortSummary + title = helper.intl["standard_https_port"] + summary = helper.intl["standard_https_port_summary"] setDefaultValue(false) setOnPreferenceChangeListener { _, newValue -> @@ -650,13 +655,13 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val contentRatingPref = MultiSelectListPreference(screen.context).apply { key = MDConstants.getContentRatingPrefKey(dexLang) - title = helper.intl.standardContentRating - summary = helper.intl.standardContentRatingSummary + title = helper.intl["standard_content_rating"] + summary = helper.intl["standard_content_rating_summary"] entries = arrayOf( - helper.intl.contentRatingSafe, - helper.intl.contentRatingSuggestive, - helper.intl.contentRatingErotica, - helper.intl.contentRatingPornographic, + helper.intl["content_rating_safe"], + helper.intl["content_rating_suggestive"], + helper.intl["content_rating_erotica"], + helper.intl["content_rating_pornographic"], ) entryValues = arrayOf( MDConstants.contentRatingPrefValSafe, @@ -677,8 +682,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val originalLanguagePref = MultiSelectListPreference(screen.context).apply { key = MDConstants.getOriginalLanguagePrefKey(dexLang) - title = helper.intl.filterOriginalLanguages - summary = helper.intl.filterOriginalLanguagesSummary + title = helper.intl["filter_original_languages"] + summary = helper.intl["filter_original_languages_summary"] entries = arrayOf( helper.intl.languageDisplayName(MangaDexIntl.JAPANESE), helper.intl.languageDisplayName(MangaDexIntl.CHINESE), @@ -702,8 +707,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val blockedGroupsPref = EditTextPreference(screen.context).apply { key = MDConstants.getBlockedGroupsPrefKey(dexLang) - title = helper.intl.blockGroupByUuid - summary = helper.intl.blockGroupByUuidSummary + title = helper.intl["block_group_by_uuid"] + summary = helper.intl["block_group_by_uuid_summary"] setOnBindEditTextListener(helper::setupEditTextUuidValidator) @@ -716,8 +721,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val blockedUploaderPref = EditTextPreference(screen.context).apply { key = MDConstants.getBlockedUploaderPrefKey(dexLang) - title = helper.intl.blockUploaderByUuid - summary = helper.intl.blockUploaderByUuidSummary + title = helper.intl["block_uploader_by_uuid"] + summary = helper.intl["block_uploader_by_uuid_summary"] setOnBindEditTextListener(helper::setupEditTextUuidValidator) @@ -730,8 +735,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val altTitlesInDescPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getAltTitlesInDescPrefKey(dexLang) - title = helper.intl.altTitlesInDesc - summary = helper.intl.altTitlesInDescSummary + title = helper.intl["alternative_titles_in_description"] + summary = helper.intl["alternative_titles_in_description_summary"] setDefaultValue(false) setOnPreferenceChangeListener { _, newValue -> diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt index d6e8f4959..b8dff948c 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt @@ -4,6 +4,7 @@ import android.content.SharedPreferences import eu.kanade.tachiyomi.extension.all.mangadex.dto.ContentRatingDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.PublicationDemographicDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.StatusDto +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import okhttp3.HttpUrl @@ -13,7 +14,7 @@ class MangaDexFilters { internal fun getMDFilterList( preferences: SharedPreferences, dexLang: String, - intl: MangaDexIntl, + intl: Intl, ): FilterList = FilterList( HasAvailableChaptersFilter(intl), OriginalLanguageList(intl, getOriginalLanguage(preferences, dexLang, intl)), @@ -22,18 +23,18 @@ class MangaDexFilters { StatusList(intl, getStatus(intl)), SortFilter(intl, getSortables(intl)), TagsFilter(intl, getTagFilters(intl)), - TagList(intl.content, getContents(intl)), - TagList(intl.format, getFormats(intl)), - TagList(intl.genre, getGenres(intl)), - TagList(intl.theme, getThemes(intl)), + TagList(intl["content"], getContents(intl)), + TagList(intl["format"], getFormats(intl)), + TagList(intl["genre"], getGenres(intl)), + TagList(intl["theme"], getThemes(intl)), ) private interface UrlQueryFilter { fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) } - private class HasAvailableChaptersFilter(intl: MangaDexIntl) : - Filter.CheckBox(intl.hasAvailableChapters), + private class HasAvailableChaptersFilter(intl: Intl) : + Filter.CheckBox(intl["has_available_chapters"]), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -45,8 +46,8 @@ class MangaDexFilters { } private class OriginalLanguage(name: String, val isoCode: String) : Filter.CheckBox(name) - private class OriginalLanguageList(intl: MangaDexIntl, originalLanguage: List) : - Filter.Group(intl.originalLanguage, originalLanguage), + private class OriginalLanguageList(intl: Intl, originalLanguage: List) : + Filter.Group(intl["original_language"], originalLanguage), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -69,7 +70,7 @@ class MangaDexFilters { private fun getOriginalLanguage( preferences: SharedPreferences, dexLang: String, - intl: MangaDexIntl, + intl: Intl, ): List { val originalLanguages = preferences.getStringSet( MDConstants.getOriginalLanguagePrefKey(dexLang), @@ -77,18 +78,18 @@ class MangaDexFilters { )!! return listOf( - OriginalLanguage(intl.originalLanguageFilterJapanese, MDConstants.originalLanguagePrefValJapanese) + OriginalLanguage(intl["original_language_filter_japanese"], MDConstants.originalLanguagePrefValJapanese) .apply { state = MDConstants.originalLanguagePrefValJapanese in originalLanguages }, - OriginalLanguage(intl.originalLanguageFilterChinese, MDConstants.originalLanguagePrefValChinese) + OriginalLanguage(intl["original_language_filter_chinese"], MDConstants.originalLanguagePrefValChinese) .apply { state = MDConstants.originalLanguagePrefValChinese in originalLanguages }, - OriginalLanguage(intl.originalLanguageFilterKorean, MDConstants.originalLanguagePrefValKorean) + OriginalLanguage(intl["original_language_filter_korean"], MDConstants.originalLanguagePrefValKorean) .apply { state = MDConstants.originalLanguagePrefValKorean in originalLanguages }, ) } private class ContentRating(name: String, val value: String) : Filter.CheckBox(name) - private class ContentRatingList(intl: MangaDexIntl, contentRating: List) : - Filter.Group(intl.contentRating, contentRating), + private class ContentRatingList(intl: Intl, contentRating: List) : + Filter.Group(intl["content_rating"], contentRating), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -103,7 +104,7 @@ class MangaDexFilters { private fun getContentRating( preferences: SharedPreferences, dexLang: String, - intl: MangaDexIntl, + intl: Intl, ): List { val contentRatings = preferences.getStringSet( MDConstants.getContentRatingPrefKey(dexLang), @@ -111,24 +112,24 @@ class MangaDexFilters { ) return listOf( - ContentRating(intl.contentRatingSafe, ContentRatingDto.SAFE.value).apply { + ContentRating(intl["content_rating_safe"], ContentRatingDto.SAFE.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValSafe) ?: true }, - ContentRating(intl.contentRatingSuggestive, ContentRatingDto.SUGGESTIVE.value).apply { + ContentRating(intl["content_rating_suggestive"], ContentRatingDto.SUGGESTIVE.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValSuggestive) ?: true }, - ContentRating(intl.contentRatingErotica, ContentRatingDto.EROTICA.value).apply { + ContentRating(intl["content_rating_erotica"], ContentRatingDto.EROTICA.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValErotica) ?: false }, - ContentRating(intl.contentRatingPornographic, ContentRatingDto.PORNOGRAPHIC.value).apply { + ContentRating(intl["content_rating_pornographic"], ContentRatingDto.PORNOGRAPHIC.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValPornographic) ?: false }, ) } private class Demographic(name: String, val value: String) : Filter.CheckBox(name) - private class DemographicList(intl: MangaDexIntl, demographics: List) : - Filter.Group(intl.publicationDemographic, demographics), + private class DemographicList(intl: Intl, demographics: List) : + Filter.Group(intl["publication_demographic"], demographics), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -140,17 +141,17 @@ class MangaDexFilters { } } - private fun getDemographics(intl: MangaDexIntl) = listOf( - Demographic(intl.publicationDemographicNone, PublicationDemographicDto.NONE.value), - Demographic(intl.publicationDemographicShounen, PublicationDemographicDto.SHOUNEN.value), - Demographic(intl.publicationDemographicShoujo, PublicationDemographicDto.SHOUJO.value), - Demographic(intl.publicationDemographicSeinen, PublicationDemographicDto.SEINEN.value), - Demographic(intl.publicationDemographicJosei, PublicationDemographicDto.JOSEI.value), + private fun getDemographics(intl: Intl) = listOf( + Demographic(intl["publication_demographic_none"], PublicationDemographicDto.NONE.value), + Demographic(intl["publication_demographic_shounen"], PublicationDemographicDto.SHOUNEN.value), + Demographic(intl["publication_demographic_shoujo"], PublicationDemographicDto.SHOUJO.value), + Demographic(intl["publication_demographic_seinen"], PublicationDemographicDto.SEINEN.value), + Demographic(intl["publication_demographic_josei"], PublicationDemographicDto.JOSEI.value), ) private class Status(name: String, val value: String) : Filter.CheckBox(name) - private class StatusList(intl: MangaDexIntl, status: List) : - Filter.Group(intl.status, status), + private class StatusList(intl: Intl, status: List) : + Filter.Group(intl["status"], status), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -162,31 +163,31 @@ class MangaDexFilters { } } - private fun getStatus(intl: MangaDexIntl) = listOf( - Status(intl.statusOngoing, StatusDto.ONGOING.value), - Status(intl.statusCompleted, StatusDto.COMPLETED.value), - Status(intl.statusHiatus, StatusDto.HIATUS.value), - Status(intl.statusCancelled, StatusDto.CANCELLED.value), + private fun getStatus(intl: Intl) = listOf( + Status(intl["status_ongoing"], StatusDto.ONGOING.value), + Status(intl["status_completed"], StatusDto.COMPLETED.value), + Status(intl["status_hiatus"], StatusDto.HIATUS.value), + Status(intl["status_cancelled"], StatusDto.CANCELLED.value), ) data class Sortable(val title: String, val value: String) { override fun toString(): String = title } - private fun getSortables(intl: MangaDexIntl) = arrayOf( - Sortable(intl.sortAlphabetic, "title"), - Sortable(intl.sortChapterUploadedAt, "latestUploadedChapter"), - Sortable(intl.sortNumberOfFollows, "followedCount"), - Sortable(intl.sortContentCreatedAt, "createdAt"), - Sortable(intl.sortContentInfoUpdatedAt, "updatedAt"), - Sortable(intl.sortRelevance, "relevance"), - Sortable(intl.sortYear, "year"), - Sortable(intl.sortRating, "rating"), + private fun getSortables(intl: Intl) = arrayOf( + Sortable(intl["sort_alphabetic"], "title"), + Sortable(intl["sort_chapter_uploaded_at"], "latestUploadedChapter"), + Sortable(intl["sort_number_of_follows"], "followedCount"), + Sortable(intl["sort_content_created_at"], "createdAt"), + Sortable(intl["sort_content_info_updated_at"], "updatedAt"), + Sortable(intl["sort_relevance"], "relevance"), + Sortable(intl["sort_year"], "year"), + Sortable(intl["sort_rating"], "rating"), ) - class SortFilter(intl: MangaDexIntl, private val sortables: Array) : + class SortFilter(intl: Intl, private val sortables: Array) : Filter.Sort( - intl.sort, + intl["sort"], sortables.map(Sortable::title).toTypedArray(), Selection(5, false), ), @@ -222,112 +223,112 @@ class MangaDexFilters { } } - private fun getContents(intl: MangaDexIntl): List { + private fun getContents(intl: Intl): List { val tags = listOf( - Tag("b29d6a3d-1569-4e7a-8caf-7557bc92cd5d", intl.contentGore), - Tag("97893a4c-12af-4dac-b6be-0dffb353568e", intl.contentSexualViolence), + Tag("b29d6a3d-1569-4e7a-8caf-7557bc92cd5d", intl["content_gore"]), + Tag("97893a4c-12af-4dac-b6be-0dffb353568e", intl["content_sexual_violence"]), ) return tags.sortIfTranslated(intl) } - private fun getFormats(intl: MangaDexIntl): List { + private fun getFormats(intl: Intl): List { val tags = listOf( - Tag("b11fda93-8f1d-4bef-b2ed-8803d3733170", intl.formatFourKoma), - Tag("f4122d1c-3b44-44d0-9936-ff7502c39ad3", intl.formatAdaptation), - Tag("51d83883-4103-437c-b4b1-731cb73d786c", intl.formatAnthology), - Tag("0a39b5a1-b235-4886-a747-1d05d216532d", intl.formatAwardWinning), - Tag("b13b2a48-c720-44a9-9c77-39c9979373fb", intl.formatDoujinshi), - Tag("7b2ce280-79ef-4c09-9b58-12b7c23a9b78", intl.formatFanColored), - Tag("f5ba408b-0e7a-484d-8d49-4e9125ac96de", intl.formatFullColor), - Tag("3e2b8dae-350e-4ab8-a8ce-016e844b9f0d", intl.formatLongStrip), - Tag("320831a8-4026-470b-94f6-8353740e6f04", intl.formatOfficialColored), - Tag("0234a31e-a729-4e28-9d6a-3f87c4966b9e", intl.formatOneshot), - Tag("891cf039-b895-47f0-9229-bef4c96eccd4", intl.formatUserCreated), - Tag("e197df38-d0e7-43b5-9b09-2842d0c326dd", intl.formatWebComic), + Tag("b11fda93-8f1d-4bef-b2ed-8803d3733170", intl["format_yonkoma"]), + Tag("f4122d1c-3b44-44d0-9936-ff7502c39ad3", intl["format_adaptation"]), + Tag("51d83883-4103-437c-b4b1-731cb73d786c", intl["format_anthology"]), + Tag("0a39b5a1-b235-4886-a747-1d05d216532d", intl["format_award_winning"]), + Tag("b13b2a48-c720-44a9-9c77-39c9979373fb", intl["format_doujinshi"]), + Tag("7b2ce280-79ef-4c09-9b58-12b7c23a9b78", intl["format_fan_colored"]), + Tag("f5ba408b-0e7a-484d-8d49-4e9125ac96de", intl["format_full_color"]), + Tag("3e2b8dae-350e-4ab8-a8ce-016e844b9f0d", intl["format_long_strip"]), + Tag("320831a8-4026-470b-94f6-8353740e6f04", intl["format_official_colored"]), + Tag("0234a31e-a729-4e28-9d6a-3f87c4966b9e", intl["format_oneshot"]), + Tag("891cf039-b895-47f0-9229-bef4c96eccd4", intl["format_user_created"]), + Tag("e197df38-d0e7-43b5-9b09-2842d0c326dd", intl["format_web_comic"]), ) return tags.sortIfTranslated(intl) } - private fun getGenres(intl: MangaDexIntl): List { + private fun getGenres(intl: Intl): List { val tags = listOf( - Tag("391b0423-d847-456f-aff0-8b0cfc03066b", intl.genreAction), - Tag("87cc87cd-a395-47af-b27a-93258283bbc6", intl.genreAdventure), - Tag("5920b825-4181-4a17-beeb-9918b0ff7a30", intl.genreBoysLove), - Tag("4d32cc48-9f00-4cca-9b5a-a839f0764984", intl.genreComedy), - Tag("5ca48985-9a9d-4bd8-be29-80dc0303db72", intl.genreCrime), - Tag("b9af3a63-f058-46de-a9a0-e0c13906197a", intl.genreDrama), - Tag("cdc58593-87dd-415e-bbc0-2ec27bf404cc", intl.genreFantasy), - Tag("a3c67850-4684-404e-9b7f-c69850ee5da6", intl.genreGirlsLove), - Tag("33771934-028e-4cb3-8744-691e866a923e", intl.genreHistorical), - Tag("cdad7e68-1419-41dd-bdce-27753074a640", intl.genreHorror), - Tag("ace04997-f6bd-436e-b261-779182193d3d", intl.genreIsekai), - Tag("81c836c9-914a-4eca-981a-560dad663e73", intl.genreMagicalGirls), - Tag("50880a9d-5440-4732-9afb-8f457127e836", intl.genreMecha), - Tag("c8cbe35b-1b2b-4a3f-9c37-db84c4514856", intl.genreMedical), - Tag("ee968100-4191-4968-93d3-f82d72be7e46", intl.genreMystery), - Tag("b1e97889-25b4-4258-b28b-cd7f4d28ea9b", intl.genrePhilosophical), - Tag("423e2eae-a7a2-4a8b-ac03-a8351462d71d", intl.genreRomance), - Tag("256c8bd9-4904-4360-bf4f-508a76d67183", intl.genreSciFi), - Tag("e5301a23-ebd9-49dd-a0cb-2add944c7fe9", intl.genreSliceOfLife), - Tag("69964a64-2f90-4d33-beeb-f3ed2875eb4c", intl.genreSports), - Tag("7064a261-a137-4d3a-8848-2d385de3a99c", intl.genreSuperhero), - Tag("07251805-a27e-4d59-b488-f0bfbec15168", intl.genreThriller), - Tag("f8f62932-27da-4fe4-8ee1-6779a8c5edba", intl.genreTragedy), - Tag("acc803a4-c95a-4c22-86fc-eb6b582d82a2", intl.genreWuxia), + Tag("391b0423-d847-456f-aff0-8b0cfc03066b", intl["genre_action"]), + Tag("87cc87cd-a395-47af-b27a-93258283bbc6", intl["genre_adventure"]), + Tag("5920b825-4181-4a17-beeb-9918b0ff7a30", intl["genre_boys_love"]), + Tag("4d32cc48-9f00-4cca-9b5a-a839f0764984", intl["genre_comedy"]), + Tag("5ca48985-9a9d-4bd8-be29-80dc0303db72", intl["genre_crime"]), + Tag("b9af3a63-f058-46de-a9a0-e0c13906197a", intl["genre_drama"]), + Tag("cdc58593-87dd-415e-bbc0-2ec27bf404cc", intl["genre_fantasy"]), + Tag("a3c67850-4684-404e-9b7f-c69850ee5da6", intl["genre_girls_love"]), + Tag("33771934-028e-4cb3-8744-691e866a923e", intl["genre_historical"]), + Tag("cdad7e68-1419-41dd-bdce-27753074a640", intl["genre_horror"]), + Tag("ace04997-f6bd-436e-b261-779182193d3d", intl["genre_isekai"]), + Tag("81c836c9-914a-4eca-981a-560dad663e73", intl["genre_magical_girls"]), + Tag("50880a9d-5440-4732-9afb-8f457127e836", intl["genre_mecha"]), + Tag("c8cbe35b-1b2b-4a3f-9c37-db84c4514856", intl["genre_medical"]), + Tag("ee968100-4191-4968-93d3-f82d72be7e46", intl["genre_mystery"]), + Tag("b1e97889-25b4-4258-b28b-cd7f4d28ea9b", intl["genre_philosophical"]), + Tag("423e2eae-a7a2-4a8b-ac03-a8351462d71d", intl["genre_romance"]), + Tag("256c8bd9-4904-4360-bf4f-508a76d67183", intl["genre_sci_fi"]), + Tag("e5301a23-ebd9-49dd-a0cb-2add944c7fe9", intl["genre_slice_of_life"]), + Tag("69964a64-2f90-4d33-beeb-f3ed2875eb4c", intl["genre_sports"]), + Tag("7064a261-a137-4d3a-8848-2d385de3a99c", intl["genre_superhero"]), + Tag("07251805-a27e-4d59-b488-f0bfbec15168", intl["genre_thriller"]), + Tag("f8f62932-27da-4fe4-8ee1-6779a8c5edba", intl["genre_tragedy"]), + Tag("acc803a4-c95a-4c22-86fc-eb6b582d82a2", intl["genre_wuxia"]), ) return tags.sortIfTranslated(intl) } - private fun getThemes(intl: MangaDexIntl): List { + private fun getThemes(intl: Intl): List { val tags = listOf( - Tag("e64f6742-c834-471d-8d72-dd51fc02b835", intl.themeAliens), - Tag("3de8c75d-8ee3-48ff-98ee-e20a65c86451", intl.themeAnimals), - Tag("ea2bc92d-1c26-4930-9b7c-d5c0dc1b6869", intl.themeCooking), - Tag("9ab53f92-3eed-4e9b-903a-917c86035ee3", intl.themeCrossdressing), - Tag("da2d50ca-3018-4cc0-ac7a-6b7d472a29ea", intl.themeDelinquents), - Tag("39730448-9a5f-48a2-85b0-a70db87b1233", intl.themeDemons), - Tag("2bd2e8d0-f146-434a-9b51-fc9ff2c5fe6a", intl.themeGenderSwap), - Tag("3bb26d85-09d5-4d2e-880c-c34b974339e9", intl.themeGhosts), - Tag("fad12b5e-68ba-460e-b933-9ae8318f5b65", intl.themeGyaru), - Tag("aafb99c1-7f60-43fa-b75f-fc9502ce29c7", intl.themeHarem), - Tag("5bd0e105-4481-44ca-b6e7-7544da56b1a3", intl.themeIncest), - Tag("2d1f5d56-a1e5-4d0d-a961-2193588b08ec", intl.themeLoli), - Tag("85daba54-a71c-4554-8a28-9901a8b0afad", intl.themeMafia), - Tag("a1f53773-c69a-4ce5-8cab-fffcd90b1565", intl.themeMagic), - Tag("799c202e-7daa-44eb-9cf7-8a3c0441531e", intl.themeMartialArts), - Tag("ac72833b-c4e9-4878-b9db-6c8a4a99444a", intl.themeMilitary), - Tag("dd1f77c5-dea9-4e2b-97ae-224af09caf99", intl.themeMonsterGirls), - Tag("36fd93ea-e8b8-445e-b836-358f02b3d33d", intl.themeMonsters), - Tag("f42fbf9e-188a-447b-9fdc-f19dc1e4d685", intl.themeMusic), - Tag("489dd859-9b61-4c37-af75-5b18e88daafc", intl.themeNinja), - Tag("92d6d951-ca5e-429c-ac78-451071cbf064", intl.themeOfficeWorkers), - Tag("df33b754-73a3-4c54-80e6-1a74a8058539", intl.themePolice), - Tag("9467335a-1b83-4497-9231-765337a00b96", intl.themePostApocalyptic), - Tag("3b60b75c-a2d7-4860-ab56-05f391bb889c", intl.themePsychological), - Tag("0bc90acb-ccc1-44ca-a34a-b9f3a73259d0", intl.themeReincarnation), - Tag("65761a2a-415e-47f3-bef2-a9dababba7a6", intl.themeReverseHarem), - Tag("81183756-1453-4c81-aa9e-f6e1b63be016", intl.themeSamurai), - Tag("caaa44eb-cd40-4177-b930-79d3ef2afe87", intl.themeSchoolLife), - Tag("ddefd648-5140-4e5f-ba18-4eca4071d19b", intl.themeShota), - Tag("eabc5b4c-6aff-42f3-b657-3e90cbd00b75", intl.themeSupernatural), - Tag("5fff9cde-849c-4d78-aab0-0d52b2ee1d25", intl.themeSurvival), - Tag("292e862b-2d17-4062-90a2-0356caa4ae27", intl.themeTimeTravel), - Tag("31932a7e-5b8e-49a6-9f12-2afa39dc544c", intl.themeTraditionalGames), - Tag("d7d1730f-6eb0-4ba6-9437-602cac38664c", intl.themeVampires), - Tag("9438db5a-7e2a-4ac0-b39e-e0d95a34b8a8", intl.themeVideoGames), - Tag("d14322ac-4d6f-4e9b-afd9-629d5f4d8a41", intl.themeVillainess), - Tag("8c86611e-fab7-4986-9dec-d1a2f44acdd5", intl.themeVirtualReality), - Tag("631ef465-9aba-4afb-b0fc-ea10efe274a8", intl.themeZombies), + Tag("e64f6742-c834-471d-8d72-dd51fc02b835", intl["theme_aliens"]), + Tag("3de8c75d-8ee3-48ff-98ee-e20a65c86451", intl["theme_animals"]), + Tag("ea2bc92d-1c26-4930-9b7c-d5c0dc1b6869", intl["theme_cooking"]), + Tag("9ab53f92-3eed-4e9b-903a-917c86035ee3", intl["theme_crossdressing"]), + Tag("da2d50ca-3018-4cc0-ac7a-6b7d472a29ea", intl["theme_delinquents"]), + Tag("39730448-9a5f-48a2-85b0-a70db87b1233", intl["theme_demons"]), + Tag("2bd2e8d0-f146-434a-9b51-fc9ff2c5fe6a", intl["theme_gender_swap"]), + Tag("3bb26d85-09d5-4d2e-880c-c34b974339e9", intl["theme_ghosts"]), + Tag("fad12b5e-68ba-460e-b933-9ae8318f5b65", intl["theme_gyaru"]), + Tag("aafb99c1-7f60-43fa-b75f-fc9502ce29c7", intl["theme_harem"]), + Tag("5bd0e105-4481-44ca-b6e7-7544da56b1a3", intl["theme_incest"]), + Tag("2d1f5d56-a1e5-4d0d-a961-2193588b08ec", intl["theme_loli"]), + Tag("85daba54-a71c-4554-8a28-9901a8b0afad", intl["theme_mafia"]), + Tag("a1f53773-c69a-4ce5-8cab-fffcd90b1565", intl["theme_magic"]), + Tag("799c202e-7daa-44eb-9cf7-8a3c0441531e", intl["theme_martial_arts"]), + Tag("ac72833b-c4e9-4878-b9db-6c8a4a99444a", intl["theme_military"]), + Tag("dd1f77c5-dea9-4e2b-97ae-224af09caf99", intl["theme_monster_girls"]), + Tag("36fd93ea-e8b8-445e-b836-358f02b3d33d", intl["theme_monsters"]), + Tag("f42fbf9e-188a-447b-9fdc-f19dc1e4d685", intl["theme_music"]), + Tag("489dd859-9b61-4c37-af75-5b18e88daafc", intl["theme_ninja"]), + Tag("92d6d951-ca5e-429c-ac78-451071cbf064", intl["theme_office_workers"]), + Tag("df33b754-73a3-4c54-80e6-1a74a8058539", intl["theme_police"]), + Tag("9467335a-1b83-4497-9231-765337a00b96", intl["theme_post_apocalyptic"]), + Tag("3b60b75c-a2d7-4860-ab56-05f391bb889c", intl["theme_psychological"]), + Tag("0bc90acb-ccc1-44ca-a34a-b9f3a73259d0", intl["theme_reincarnation"]), + Tag("65761a2a-415e-47f3-bef2-a9dababba7a6", intl["theme_reverse_harem"]), + Tag("81183756-1453-4c81-aa9e-f6e1b63be016", intl["theme_samurai"]), + Tag("caaa44eb-cd40-4177-b930-79d3ef2afe87", intl["theme_school_life"]), + Tag("ddefd648-5140-4e5f-ba18-4eca4071d19b", intl["theme_shota"]), + Tag("eabc5b4c-6aff-42f3-b657-3e90cbd00b75", intl["theme_supernatural"]), + Tag("5fff9cde-849c-4d78-aab0-0d52b2ee1d25", intl["theme_survival"]), + Tag("292e862b-2d17-4062-90a2-0356caa4ae27", intl["theme_time_travel"]), + Tag("31932a7e-5b8e-49a6-9f12-2afa39dc544c", intl["theme_traditional_games"]), + Tag("d7d1730f-6eb0-4ba6-9437-602cac38664c", intl["theme_vampires"]), + Tag("9438db5a-7e2a-4ac0-b39e-e0d95a34b8a8", intl["theme_video_games"]), + Tag("d14322ac-4d6f-4e9b-afd9-629d5f4d8a41", intl["theme_villainess"]), + Tag("8c86611e-fab7-4986-9dec-d1a2f44acdd5", intl["theme_virtual_reality"]), + Tag("631ef465-9aba-4afb-b0fc-ea10efe274a8", intl["theme_zombies"]), ) return tags.sortIfTranslated(intl) } // to get all tags from dex https://api.mangadex.org/manga/tag - internal fun getTags(intl: MangaDexIntl): List { + internal fun getTags(intl: Intl): List { return getContents(intl) + getFormats(intl) + getGenres(intl) + getThemes(intl) } @@ -335,13 +336,13 @@ class MangaDexFilters { override fun toString(): String = title } - private fun getTagModes(intl: MangaDexIntl) = arrayOf( - TagMode(intl.modeAnd, "AND"), - TagMode(intl.modeOr, "OR"), + private fun getTagModes(intl: Intl) = arrayOf( + TagMode(intl["mode_and"], "AND"), + TagMode(intl["mode_or"], "OR"), ) - private class TagInclusionMode(intl: MangaDexIntl, modes: Array) : - Filter.Select(intl.includedTagsMode, modes, 0), + private class TagInclusionMode(intl: Intl, modes: Array) : + Filter.Select(intl["included_tags_mode"], modes, 0), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -349,8 +350,8 @@ class MangaDexFilters { } } - private class TagExclusionMode(intl: MangaDexIntl, modes: Array) : - Filter.Select(intl.excludedTagsMode, modes, 1), + private class TagExclusionMode(intl: Intl, modes: Array) : + Filter.Select(intl["excluded_tags_mode"], modes, 1), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -358,8 +359,8 @@ class MangaDexFilters { } } - private class TagsFilter(intl: MangaDexIntl, innerFilters: FilterList) : - Filter.Group>(intl.tags, innerFilters), + private class TagsFilter(intl: Intl, innerFilters: FilterList) : + Filter.Group>(intl["tags_mode"], innerFilters), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -368,20 +369,20 @@ class MangaDexFilters { } } - private fun getTagFilters(intl: MangaDexIntl): FilterList = FilterList( + private fun getTagFilters(intl: Intl): FilterList = FilterList( TagInclusionMode(intl, getTagModes(intl)), TagExclusionMode(intl, getTagModes(intl)), ) - internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList, dexLang: String): String { + internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList, dexLang: String): HttpUrl { filters.filterIsInstance() .forEach { filter -> filter.addQueryParameter(url, dexLang) } - return url.toString() + return url.build() } - private fun List.sortIfTranslated(intl: MangaDexIntl): List = apply { - if (intl.availableLang == MangaDexIntl.ENGLISH) { + private fun List.sortIfTranslated(intl: Intl): List = apply { + if (intl.chosenLanguage == MangaDexIntl.ENGLISH) { return this } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt index 60234e8ae..331cdd6ea 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt @@ -29,6 +29,7 @@ import eu.kanade.tachiyomi.extension.all.mangadex.dto.TagDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.UnknownEntity import eu.kanade.tachiyomi.extension.all.mangadex.dto.UserAttributes import eu.kanade.tachiyomi.extension.all.mangadex.dto.UserDto +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter @@ -85,7 +86,19 @@ class MangaDexHelper(lang: String) { } } - val intl = MangaDexIntl(lang) + val intl = Intl( + language = lang, + baseLanguage = MangaDexIntl.ENGLISH, + availableLanguages = MangaDexIntl.AVAILABLE_LANGS, + classLoader = this::class.java.classLoader, + createMessageFileName = { lang -> + when (lang) { + MangaDexIntl.SPANISH_LATAM -> Intl.createDefaultMessageFileName(MangaDexIntl.SPANISH) + MangaDexIntl.PORTUGUESE -> Intl.createDefaultMessageFileName(MangaDexIntl.BRAZILIAN_PORTUGUESE) + else -> Intl.createDefaultMessageFileName(lang) + } + }, + ) /** * Gets the UUID from the url @@ -302,10 +315,11 @@ class MangaDexHelper(lang: String) { val dexLocale = Locale.forLanguageTag(lang) val nonGenres = listOfNotNull( - attr.publicationDemographic?.let { intl.publicationDemographic(it) }, + attr.publicationDemographic + ?.let { intl["publication_demographic_${it.name.lowercase()}"] }, attr.contentRating .takeIf { it != ContentRatingDto.SAFE } - ?.let { intl.contentRatingGenre(it) }, + ?.let { intl["content_rating_genre"].format(intl["content_rating_${it.name.lowercase()}"]) }, attr.originalLanguage ?.let { Locale.forLanguageTag(it) } ?.getDisplayName(dexLocale) @@ -346,7 +360,7 @@ class MangaDexHelper(lang: String) { if (altTitles.isNotEmpty()) { val altTitlesDesc = altTitles - .joinToString("\n", "${intl.altTitleText}\n") { "• $it" } + .joinToString("\n", "${intl["alternative_titles"]}\n") { "• $it" } desc += (if (desc.isNullOrBlank()) "" else "\n\n") + altTitlesDesc.removeEntitiesAndMarkdown() } } @@ -378,9 +392,9 @@ class MangaDexHelper(lang: String) { val users = chapterDataDto.relationships .filterIsInstance() .mapNotNull { it.attributes?.username } - if (users.isNotEmpty()) intl.uploadedBy(users) else "" + if (users.isNotEmpty()) intl["uploaded_by"].format(users.joinToString(" & ")) else "" } - .ifEmpty { intl.noGroup } // "No Group" as final resort + .ifEmpty { intl["no_group"] } // "No Group" as final resort val chapterName = mutableListOf() // Build chapter name @@ -465,7 +479,7 @@ class MangaDexHelper(lang: String) { .map(String::trim) .all(::isUuid) - editText.error = if (!isValid) intl.invalidUuids else null + editText.error = if (!isValid) intl["invalid_uuids"] else null editText.rootView.findViewById