From 35f9173980d5551005ada043c0df3bf19c24c603 Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 25 Oct 2023 12:16:58 +0200 Subject: [PATCH] Started moving all strings to strings.xml --- .../java/com/futo/platformplayer/Settings.kt | 173 ++++----- .../com/futo/platformplayer/SettingsDev.kt | 60 ++-- .../platforms/js/SourcePluginDescriptor.kt | 7 +- .../views/fields/ButtonField.kt | 4 +- .../views/fields/DropdownField.kt | 10 +- .../futo/platformplayer/views/fields/Field.kt | 2 +- .../platformplayer/views/fields/GroupField.kt | 4 +- .../views/fields/ReadOnlyTextField.kt | 4 +- .../views/fields/ToggleField.kt | 6 +- .../main/res/layout/activity_add_source.xml | 11 +- .../layout/activity_add_source_options.xml | 6 +- app/src/main/res/layout/activity_captcha.xml | 4 +- .../main/res/layout/activity_exception.xml | 12 +- .../main/res/layout/activity_manage_tabs.xml | 2 +- .../layout/activity_polycentric_backup.xml | 10 +- .../activity_polycentric_create_profile.xml | 8 +- .../res/layout/activity_polycentric_home.xml | 10 +- .../activity_polycentric_import_profile.xml | 8 +- .../layout/activity_polycentric_profile.xml | 12 +- .../res/layout/activity_polycentric_why.xml | 10 +- app/src/main/res/layout/activity_settings.xml | 4 +- app/src/main/res/layout/big_button_group.xml | 2 +- .../res/layout/dialog_automatic_backup.xml | 10 +- .../dialog_automatic_backup_restore.xml | 10 +- .../main/res/layout/dialog_casting_add.xml | 8 +- .../res/layout/dialog_casting_connect.xml | 10 +- .../res/layout/dialog_casting_connected.xml | 6 +- app/src/main/res/layout/dialog_changelog.xml | 11 +- app/src/main/res/layout/dialog_comment.xml | 12 +- app/src/main/res/layout/dialog_data.xml | 6 +- app/src/main/res/layout/dialog_import.xml | 34 +- app/src/main/res/layout/dialog_migrate.xml | 38 +- .../main/res/layout/dialog_multi_button.xml | 4 +- app/src/main/res/layout/dialog_update.xml | 2 +- app/src/main/res/layout/field_button.xml | 7 +- .../main/res/layout/fragment_add_top_bar.xml | 4 +- app/src/main/res/layout/fragment_buy.xml | 8 +- app/src/main/res/layout/fragment_channel.xml | 2 +- .../layout/fragment_channel_monetization.xml | 14 +- .../res/layout/fragment_channel_store.xml | 6 +- .../main/res/layout/fragment_downloads.xml | 16 +- app/src/main/res/layout/fragment_history.xml | 2 +- app/src/main/res/layout/fragment_import.xml | 2 +- .../res/layout/fragment_import_top_bar.xml | 2 +- .../res/layout/fragment_overview_top_bar.xml | 4 +- app/src/main/res/layout/fragment_sources.xml | 2 +- .../main/res/layout/fragview_post_detail.xml | 10 +- .../main/res/layout/fragview_video_detail.xml | 18 +- .../main/res/layout/list_chat_donation.xml | 2 +- app/src/main/res/layout/list_comment.xml | 4 +- app/src/main/res/layout/list_device.xml | 2 +- app/src/main/res/layout/list_download.xml | 4 +- app/src/main/res/layout/list_downloaded.xml | 4 +- .../res/layout/list_downloaded_playlist.xml | 3 +- app/src/main/res/layout/list_history.xml | 2 +- app/src/main/res/layout/list_playlist.xml | 2 +- .../main/res/layout/list_playlist_feed.xml | 6 +- .../res/layout/list_playlist_feed_preview.xml | 4 +- app/src/main/res/layout/list_post_preview.xml | 8 +- .../main/res/layout/list_post_thumbnail.xml | 2 +- .../res/layout/list_source_construction.xml | 4 +- .../main/res/layout/list_source_disabled.xml | 2 +- .../main/res/layout/list_source_enabled.xml | 2 +- .../main/res/layout/list_video_horizontal.xml | 2 +- .../main/res/layout/list_video_preview.xml | 2 +- .../res/layout/list_video_preview_nested.xml | 8 +- .../main/res/layout/list_video_thumbnail.xml | 4 +- .../layout/list_video_thumbnail_nested.xml | 10 +- app/src/main/res/layout/overlay_livechat.xml | 20 +- .../main/res/layout/overlay_slide_up_menu.xml | 2 +- .../layout/overlay_slide_up_menu_group.xml | 3 +- .../layout/overlay_slide_up_menu_option.xml | 5 +- .../layout/overlay_slide_up_menu_title.xml | 2 +- app/src/main/res/layout/payment_checkout.xml | 327 ------------------ app/src/main/res/layout/payment_country.xml | 49 --- app/src/main/res/layout/payment_currency.xml | 49 --- .../res/layout/payment_currency_other.xml | 52 --- app/src/main/res/layout/payment_method.xml | 50 --- app/src/main/res/layout/payment_overlay.xml | 127 ------- .../main/res/layout/payment_postal_code.xml | 55 --- .../main/res/layout/rating_likesdislikes.xml | 5 +- .../main/res/layout/thumbnail_player_ui.xml | 4 +- app/src/main/res/layout/video_player_ui.xml | 7 +- .../res/layout/video_player_ui_fullscreen.xml | 7 +- app/src/main/res/layout/view_add_comment.xml | 2 +- app/src/main/res/layout/view_announcement.xml | 4 +- app/src/main/res/layout/view_bullet_point.xml | 6 +- app/src/main/res/layout/view_cast.xml | 8 +- app/src/main/res/layout/view_desc_button.xml | 4 +- .../main/res/layout/view_gesture_controls.xml | 14 +- .../main/res/layout/view_source_header.xml | 23 +- app/src/main/res/layout/view_up_next.xml | 8 +- app/src/main/res/values/strings.xml | 270 ++++++++++++++- dep/futopay | 2 +- gradle.properties | 3 +- 95 files changed, 685 insertions(+), 1117 deletions(-) delete mode 100644 app/src/main/res/layout/payment_checkout.xml delete mode 100644 app/src/main/res/layout/payment_country.xml delete mode 100644 app/src/main/res/layout/payment_currency.xml delete mode 100644 app/src/main/res/layout/payment_currency_other.xml delete mode 100644 app/src/main/res/layout/payment_method.xml delete mode 100644 app/src/main/res/layout/payment_overlay.xml delete mode 100644 app/src/main/res/layout/payment_postal_code.xml diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index dd37868c..fd80df96 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -45,8 +45,8 @@ class Settings : FragmentedStorageFileJson() { val onTabsChanged = Event0(); @FormField( - "Manage Polycentric identity", FieldForm.BUTTON, - "Manage your Polycentric identity", -4 + R.string.manage_polycentric_identity, FieldForm.BUTTON, + R.string.manage_your_polycentric_identity, -4 ) @FormFieldButton(R.drawable.ic_person) fun managePolycentricIdentity() { @@ -60,8 +60,8 @@ class Settings : FragmentedStorageFileJson() { } @FormField( - "Show FAQ", FieldForm.BUTTON, - "Get answers to common questions", -3 + R.string.show_faq, FieldForm.BUTTON, + R.string.get_answers_to_common_questions, -3 ) @FormFieldButton(R.drawable.ic_quiz) fun openFAQ() { @@ -73,8 +73,8 @@ class Settings : FragmentedStorageFileJson() { } } @FormField( - "Show Issues", FieldForm.BUTTON, - "A list of user-reported and self-reported issues", -2 + R.string.show_issues, FieldForm.BUTTON, + R.string.a_list_of_user_reported_and_self_reported_issues, -2 ) @FormFieldButton(R.drawable.ic_data_alert) fun openIssues() { @@ -87,8 +87,8 @@ class Settings : FragmentedStorageFileJson() { } @FormField( - "Submit feedback", FieldForm.BUTTON, - "Give feedback on the application", -1 + R.string.submit_feedback, FieldForm.BUTTON, + R.string.give_feedback_on_the_application, -1 ) @FormFieldButton(R.drawable.ic_bug) fun submitFeedback() { @@ -107,8 +107,8 @@ class Settings : FragmentedStorageFileJson() { } @FormField( - "Manage Tabs", FieldForm.BUTTON, - "Change tabs visible on the home screen", -1 + R.string.manage_tabs, FieldForm.BUTTON, + R.string.change_tabs_visible_on_the_home_screen, -1 ) @FormFieldButton(R.drawable.ic_tabs) fun manageTabs() { @@ -121,11 +121,11 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Home", "group", "Configure how your Home tab works and feels", 1) + @FormField(R.string.home, "group", R.string.configure_how_your_home_tab_works_and_feels, 1) var home = HomeSettings(); @Serializable class HomeSettings { - @FormField("Feed Style", FieldForm.DROPDOWN, "", 5) + @FormField(R.string.feed_style, FieldForm.DROPDOWN, -1, 5) @DropdownFieldOptionsId(R.array.feed_style) var homeFeedStyle: Int = 1; @@ -137,16 +137,16 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Search", "group", "", 2) + @FormField(R.string.search, "group", -1, 2) var search = SearchSettings(); @Serializable class SearchSettings { - @FormField("Search History", FieldForm.TOGGLE, "", 4) + @FormField(R.string.search_history, FieldForm.TOGGLE, -1, 4) @Serializable(with = FlexibleBooleanSerializer::class) var searchHistory: Boolean = true; - @FormField("Feed Style", FieldForm.DROPDOWN, "", 5) + @FormField(R.string.feed_style, FieldForm.DROPDOWN, -1, 5) @DropdownFieldOptionsId(R.array.feed_style) var searchFeedStyle: Int = 1; @@ -159,11 +159,11 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Subscriptions", "group", "Configure how your Subscriptions works and feels", 3) + @FormField(R.string.subscriptions, "group", R.string.configure_how_your_subscriptions_works_and_feels, 3) var subscriptions = SubscriptionsSettings(); @Serializable class SubscriptionsSettings { - @FormField("Feed Style", FieldForm.DROPDOWN, "", 5) + @FormField(R.string.feed_style, FieldForm.DROPDOWN, -1, 5) @DropdownFieldOptionsId(R.array.feed_style) var subscriptionsFeedStyle: Int = 1; @@ -174,11 +174,11 @@ class Settings : FragmentedStorageFileJson() { return FeedStyle.THUMBNAIL; } - @FormField("Fetch on app boot", FieldForm.TOGGLE, "Shortly after opening the app, start fetching subscriptions.", 6) + @FormField(R.string.fetch_on_app_boot, FieldForm.TOGGLE, R.string.shortly_after_opening_the_app_start_fetching_subscriptions, 6) @Serializable(with = FlexibleBooleanSerializer::class) var fetchOnAppBoot: Boolean = true; - @FormField("Background Update", FieldForm.DROPDOWN, "Experimental background update for subscriptions cache (requires restart)", 7) + @FormField(R.string.background_update, FieldForm.DROPDOWN, R.string.experimental_background_update_for_subscriptions_cache, 7) @DropdownFieldOptionsId(R.array.background_interval) var subscriptionsBackgroundUpdateInterval: Int = 0; @@ -194,7 +194,7 @@ class Settings : FragmentedStorageFileJson() { }; - @FormField("Subscription Concurrency", FieldForm.DROPDOWN, "Specify how many threads are used to fetch channels (requires restart)", 8) + @FormField(R.string.subscription_concurrency, FieldForm.DROPDOWN, R.string.specify_how_many_threads_are_used_to_fetch_channels, 8) @DropdownFieldOptionsId(R.array.thread_count) var subscriptionConcurrency: Int = 3; @@ -203,17 +203,17 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Player", "group", "Change behavior of the player", 4) + @FormField(R.string.player, "group", R.string.change_behavior_of_the_player, 4) var playback = PlaybackSettings(); @Serializable class PlaybackSettings { - @FormField("Primary Language", FieldForm.DROPDOWN, "", 0) + @FormField(R.string.primary_language, FieldForm.DROPDOWN, -1, 0) @DropdownFieldOptionsId(R.array.languages) var primaryLanguage: Int = 0; fun getPrimaryLanguage(context: Context) = context.resources.getStringArray(R.array.languages)[primaryLanguage]; - @FormField("Default Playback Speed", FieldForm.DROPDOWN, "", 1) + @FormField(R.string.default_playback_speed, FieldForm.DROPDOWN, -1, 1) @DropdownFieldOptionsId(R.array.playback_speeds) var defaultPlaybackSpeed: Int = 3; fun getDefaultPlaybackSpeed(): Float = when(defaultPlaybackSpeed) { @@ -229,29 +229,29 @@ class Settings : FragmentedStorageFileJson() { else -> 1.0f; }; - @FormField("Preferred Quality", FieldForm.DROPDOWN, "", 2) + @FormField(R.string.preferred_quality, FieldForm.DROPDOWN, -1, 2) @DropdownFieldOptionsId(R.array.preferred_quality_array) var preferredQuality: Int = 0; - @FormField("Preferred Metered Quality", FieldForm.DROPDOWN, "", 2) + @FormField(R.string.preferred_metered_quality, FieldForm.DROPDOWN, -1, 2) @DropdownFieldOptionsId(R.array.preferred_quality_array) var preferredMeteredQuality: Int = 0; fun getPreferredQualityPixelCount(): Int = preferedQualityToPixels(preferredQuality); fun getPreferredMeteredQualityPixelCount(): Int = preferedQualityToPixels(preferredMeteredQuality); fun getCurrentPreferredQualityPixelCount(): Int = if(!StateApp.instance.isCurrentMetered()) getPreferredQualityPixelCount() else getPreferredMeteredQualityPixelCount(); - @FormField("Preferred Preview Quality", FieldForm.DROPDOWN, "", 3) + @FormField(R.string.preferred_preview_quality, FieldForm.DROPDOWN, -1, 3) @DropdownFieldOptionsId(R.array.preferred_quality_array) var preferredPreviewQuality: Int = 5; fun getPreferredPreviewQualityPixelCount(): Int = preferedQualityToPixels(preferredPreviewQuality); - @FormField("Auto-Rotate", FieldForm.DROPDOWN, "", 4) + @FormField(R.string.auto_rotate, FieldForm.DROPDOWN, -1, 4) @DropdownFieldOptionsId(R.array.system_enabled_disabled_array) var autoRotate: Int = 2; fun isAutoRotate() = autoRotate == 1 || (autoRotate == 2 && StateApp.instance.getCurrentSystemAutoRotate()); - @FormField("Auto-Rotate Dead Zone", FieldForm.DROPDOWN, "This prevents the device from rotating within the given amount of degrees.", 5) + @FormField(R.string.auto_rotate_dead_zone, FieldForm.DROPDOWN, R.string.this_prevents_the_device_from_rotating_within_the_given_amount_of_degrees, 5) @DropdownFieldOptionsId(R.array.auto_rotate_dead_zone) var autoRotateDeadZone: Int = 0; @@ -259,19 +259,19 @@ class Settings : FragmentedStorageFileJson() { return autoRotateDeadZone * 5; } - @FormField("Background Behavior", FieldForm.DROPDOWN, "", 6) + @FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 6) @DropdownFieldOptionsId(R.array.player_background_behavior) var backgroundPlay: Int = 2; fun isBackgroundContinue() = backgroundPlay == 1; fun isBackgroundPictureInPicture() = backgroundPlay == 2; - @FormField("Resume After Preview", FieldForm.DROPDOWN, "When watching a video in preview mode, resume at the position when opening the video", 7) + @FormField(R.string.resume_after_preview, FieldForm.DROPDOWN, R.string.when_watching_a_video_in_preview_mode_resume_at_the_position_when_opening_the_video_code, 7) @DropdownFieldOptionsId(R.array.resume_after_preview) var resumeAfterPreview: Int = 1; - @FormField("Live Chat Webview", FieldForm.TOGGLE, "Use the live chat web window when available over native implementation.", 8) + @FormField(R.string.live_chat_webview, FieldForm.TOGGLE, R.string.use_the_live_chat_web_window_when_available_over_native_implementation, 8) var useLiveChatWindow: Boolean = true; fun shouldResumePreview(previewedPosition: Long): Boolean{ @@ -283,12 +283,12 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Downloads", "group", "Configure downloading of videos", 5) + @FormField(R.string.downloads, "group", R.string.configure_downloading_of_videos, 5) var downloads = Downloads(); @Serializable class Downloads { - @FormField("Download when", FieldForm.DROPDOWN, "Configure when videos should be downloaded", 0) + @FormField(R.string.download_when, FieldForm.DROPDOWN, R.string.configure_when_videos_should_be_downloaded, 0) @DropdownFieldOptionsId(R.array.when_download) var whenDownload: Int = 0; @@ -301,21 +301,21 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Default Video Quality", FieldForm.DROPDOWN, "", 2) + @FormField(R.string.default_video_quality, FieldForm.DROPDOWN, -1, 2) @DropdownFieldOptionsId(R.array.preferred_video_download) var preferredVideoQuality: Int = 4; fun getDefaultVideoQualityPixels(): Int = preferedQualityToPixels(preferredVideoQuality); - @FormField("Default Audio Quality", FieldForm.DROPDOWN, "", 3) + @FormField(R.string.default_audio_quality, FieldForm.DROPDOWN, -1, 3) @DropdownFieldOptionsId(R.array.preferred_audio_download) var preferredAudioQuality: Int = 1; fun isHighBitrateDefault(): Boolean = preferredAudioQuality > 0; - @FormField("ByteRange Download", FieldForm.TOGGLE, "Attempt to utilize byte ranges, this can be combined with concurrency to bypass throttling", 4) + @FormField(R.string.byte_range_download, FieldForm.TOGGLE, R.string.attempt_to_utilize_byte_ranges, 4) @Serializable(with = FlexibleBooleanSerializer::class) var byteRangeDownload: Boolean = true; - @FormField("ByteRange Concurrency", FieldForm.DROPDOWN, "Number of concurrent threads to multiply download speeds from throttled sources", 5) + @FormField(R.string.byte_range_concurrency, FieldForm.DROPDOWN, R.string.number_of_concurrent_threads_to_multiply_download_speeds_from_throttled_sources, 5) @DropdownFieldOptionsId(R.array.thread_count) var byteRangeConcurrency: Int = 3; fun getByteRangeThreadCount(): Int { @@ -323,20 +323,20 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Browsing", "group", "Configure browsing behavior", 6) + @FormField(R.string.browsing, "group", R.string.configure_browsing_behavior, 6) var browsing = Browsing(); @Serializable class Browsing { - @FormField("Enable Video Cache", FieldForm.TOGGLE, "A cache to quickly load previously fetched videos", 0) + @FormField(R.string.enable_video_cache, FieldForm.TOGGLE, R.string.cache_to_quickly_load_previously_fetched_videos, 0) @Serializable(with = FlexibleBooleanSerializer::class) var videoCache: Boolean = true; } - @FormField("Casting", "group", "Configure casting", 7) + @FormField(R.string.casting, "group", R.string.configure_casting, 7) var casting = Casting(); @Serializable class Casting { - @FormField("Enabled", FieldForm.TOGGLE, "Enable casting", 0) + @FormField(R.string.enabled, FieldForm.TOGGLE, R.string.enable_casting, 0) @Serializable(with = FlexibleBooleanSerializer::class) var enabled: Boolean = true; @@ -358,24 +358,24 @@ class Settings : FragmentedStorageFileJson() { } - @FormField("Logging", FieldForm.GROUP, "", 8) + @FormField(R.string.logging, FieldForm.GROUP, -1, 8) var logging = Logging(); @Serializable class Logging { - @FormField("Log Level", FieldForm.DROPDOWN, "", 0) + @FormField(R.string.log_level, FieldForm.DROPDOWN, -1, 0) @DropdownFieldOptionsId(R.array.log_levels) var logLevel: Int = 0; @FormField( - "Submit logs", FieldForm.BUTTON, - "Submit logs to help us narrow down issues", 1 + R.string.submit_logs, FieldForm.BUTTON, + R.string.submit_logs_to_help_us_narrow_down_issues, 1 ) fun submitLogs() { StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { if (!Logger.submitLogs()) { withContext(Dispatchers.Main) { - SettingsActivity.getActivity()?.let { UIDialogs.toast(it, "Please enable logging to submit logs") } + SettingsActivity.getActivity()?.let { UIDialogs.toast(it, it.getString(R.string.please_enable_logging_to_submit_logs)) } } } } catch (e: Throwable) { @@ -387,40 +387,40 @@ class Settings : FragmentedStorageFileJson() { - @FormField("Announcement", FieldForm.GROUP, "", 10) + @FormField(R.string.announcement, FieldForm.GROUP, -1, 10) var announcementSettings = AnnouncementSettings(); @Serializable class AnnouncementSettings { @FormField( - "Reset announcements", FieldForm.BUTTON, - "Reset hidden announcements", 1 + R.string.reset_announcements, FieldForm.BUTTON, + R.string.reset_hidden_announcements, 1 ) fun resetAnnouncements() { StateAnnouncement.instance.resetAnnouncements(); - UIDialogs.toast("Announcements reset."); + SettingsActivity.getActivity()?.let { UIDialogs.toast(it, it.getString(R.string.announcements_reset)); }; } } - @FormField("Plugins", FieldForm.GROUP, "", 11) + @FormField(R.string.plugins, FieldForm.GROUP, -1, 11) @Transient var plugins = Plugins(); @Serializable class Plugins { - @FormField("Clear Cookies on Logout", FieldForm.TOGGLE, "Clears cookies when you log out, allowing you to change account.", 0) + @FormField(R.string.clear_cookies_on_logout, FieldForm.TOGGLE, R.string.clears_cookies_when_you_log_out, 0) var clearCookiesOnLogout: Boolean = true; @FormField( - "Clear Cookies", FieldForm.BUTTON, - "Clears in-app browser cookies, especially useful for fully logging out of plugins.", 1 + R.string.clear_cookies, FieldForm.BUTTON, + R.string.clears_in_app_browser_cookies, 1 ) fun clearCookies() { val cookieManager: CookieManager = CookieManager.getInstance(); cookieManager.removeAllCookies(null); } @FormField( - "Reinstall Embedded Plugins", FieldForm.BUTTON, - "Also removes any data related plugin like login or settings (may not clear browser cache)", 1 + R.string.reinstall_embedded_plugins, FieldForm.BUTTON, + R.string.also_removes_any_data_related_plugin_like_login_or_settings, 1 ) fun reinstallEmbedded() { StateApp.instance.scopeOrNull!!.launch(Dispatchers.IO) { @@ -429,7 +429,7 @@ class Settings : FragmentedStorageFileJson() { withContext(Dispatchers.Main) { StateApp.instance.contextOrNull?.let { - UIDialogs.toast(it, "Embedded plugins reinstalled, a reboot is recommended"); + UIDialogs.toast(it, it.getString(R.string.embedded_plugins_reinstalled_a_reboot_is_recommended)); }; } } catch (ex: Exception) { @@ -444,7 +444,7 @@ class Settings : FragmentedStorageFileJson() { } - @FormField("External Storage", FieldForm.GROUP, "", 12) + @FormField(R.string.external_storage, FieldForm.GROUP, -1, 12) var storage = Storage(); @Serializable class Storage { @@ -456,13 +456,13 @@ class Settings : FragmentedStorageFileJson() { fun isStorageMainValid(context: Context): Boolean = StateApp.instance.isValidStorageUri(context, getStorageGeneralUri()); fun isStorageDownloadValid(context: Context): Boolean = StateApp.instance.isValidStorageUri(context, getStorageDownloadUri()); - @FormField("Change external General directory", FieldForm.BUTTON, "Change the external directory for general files, used for persistent files like auto-backup", 3) + @FormField(R.string.change_external_general_directory, FieldForm.BUTTON, R.string.change_the_external_directory_for_general_files, 3) fun changeStorageGeneral() { SettingsActivity.getActivity()?.let { StateApp.instance.changeExternalGeneralDirectory(it); } } - @FormField("Change external Downloads directory", FieldForm.BUTTON, "Change the external storage for download files, used for exported download files", 4) + @FormField(R.string.change_external_downloads_directory, FieldForm.BUTTON, R.string.change_the_external_storage_for_download_files, 4) fun changeStorageDownload() { SettingsActivity.getActivity()?.let { StateApp.instance.changeExternalDownloadDirectory(it); @@ -471,19 +471,19 @@ class Settings : FragmentedStorageFileJson() { } - @FormField("Auto Update", "group", "Configure the auto updater", 12) + @FormField(R.string.auto_update, "group", R.string.configure_the_auto_updater, 12) var autoUpdate = AutoUpdate(); @Serializable class AutoUpdate { - @FormField("Check", FieldForm.DROPDOWN, "", 0) + @FormField(R.string.check, FieldForm.DROPDOWN, -1, 0) @DropdownFieldOptionsId(R.array.auto_update_when_array) var check: Int = 0; - @FormField("Background download", FieldForm.DROPDOWN, "Configure if background download should be used", 1) + @FormField(R.string.background_download, FieldForm.DROPDOWN, R.string.configure_if_background_download_should_be_used, 1) @DropdownFieldOptionsId(R.array.background_download) var backgroundDownload: Int = 0; - @FormField("Download when", FieldForm.DROPDOWN, "Configure when updates should be downloaded", 2) + @FormField(R.string.download_when, FieldForm.DROPDOWN, R.string.configure_when_updates_should_be_downloaded, 2) @DropdownFieldOptionsId(R.array.when_download) var whenDownload: Int = 0; @@ -501,8 +501,8 @@ class Settings : FragmentedStorageFileJson() { } @FormField( - "Manual check", FieldForm.BUTTON, - "Manually check for updates", 3 + R.string.manual_check, FieldForm.BUTTON, + R.string.manually_check_for_updates, 3 ) fun manualCheck() { if (!BuildConfig.IS_PLAYSTORE_BUILD) { @@ -514,19 +514,20 @@ class Settings : FragmentedStorageFileJson() { try { it.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=${it.packageName}"))) } catch (e: ActivityNotFoundException) { - UIDialogs.toast(it, "Failed to show store."); + UIDialogs.toast(it, it.getString(R.string.failed_to_show_store)); } } } } @FormField( - "View changelog", FieldForm.BUTTON, - "Review the current and past changelogs", 4 + R.string.view_changelog, FieldForm.BUTTON, + R.string.review_the_current_and_past_changelogs, 4 ) fun viewChangelog() { - UIDialogs.toast("Retrieving changelog"); SettingsActivity.getActivity()?.let { + UIDialogs.toast(it.getString(R.string.retrieving_changelog)); + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { val version = StateUpdate.instance.downloadVersionCode(ManagedHttpClient()) ?: return@launch; @@ -543,8 +544,8 @@ class Settings : FragmentedStorageFileJson() { } @FormField( - "Remove Cached Version", FieldForm.BUTTON, - "Remove the last downloaded version", 5 + R.string.remove_cached_version, FieldForm.BUTTON, + R.string.remove_the_last_downloaded_version, 5 ) fun removeCachedVersion() { StateApp.withContext { @@ -561,7 +562,7 @@ class Settings : FragmentedStorageFileJson() { } } - @FormField("Backup", FieldForm.GROUP, "", 13) + @FormField(R.string.backup, FieldForm.GROUP, -1, 13) var backup = Backup(); @Serializable class Backup { @@ -571,16 +572,16 @@ class Settings : FragmentedStorageFileJson() { var autoBackupPassword: String? = null; fun shouldAutomaticBackup() = autoBackupPassword != null; - @FormField("Automatic Backup", FieldForm.READONLYTEXT, "", 0) + @FormField(R.string.automatic_backup, FieldForm.READONLYTEXT, -1, 0) val automaticBackupText get() = if(!shouldAutomaticBackup()) "None" else "Every Day"; - @FormField("Set Automatic Backup", FieldForm.BUTTON, "Configure daily backup in case of catastrophic failure. (Written to the external Grayjay directory)", 1) + @FormField(R.string.set_automatic_backup, FieldForm.BUTTON, R.string.configure_daily_backup_in_case_of_catastrophic_failure, 1) fun configureAutomaticBackup() { UIDialogs.showAutomaticBackupDialog(SettingsActivity.getActivity()!!, autoBackupPassword != null) { SettingsActivity.getActivity()?.reloadSettings(); }; } - @FormField("Restore Automatic Backup", FieldForm.BUTTON, "Restore a previous automatic backup", 2) + @FormField(R.string.restore_automatic_backup, FieldForm.BUTTON, R.string.restore_a_previous_automatic_backup, 2) fun restoreAutomaticBackup() { val activity = SettingsActivity.getActivity()!! @@ -591,38 +592,38 @@ class Settings : FragmentedStorageFileJson() { } - @FormField("Export Data", FieldForm.BUTTON, "Creates a zip file with your data which can be imported by opening it with Grayjay", 3) + @FormField(R.string.export_data, FieldForm.BUTTON, R.string.creates_a_zip_file_with_your_data_which_can_be_imported_by_opening_it_with_grayjay, 3) fun export() { StateBackup.startExternalBackup(); } } - @FormField("Payment", FieldForm.GROUP, "", 14) + @FormField(R.string.payment, FieldForm.GROUP, -1, 14) var payment = Payment(); @Serializable class Payment { - @FormField("Payment Status", FieldForm.READONLYTEXT, "", 1) - val paymentStatus: String get() = if (StatePayment.instance.hasPaid) "Paid" else "Not Paid"; + @FormField(R.string.payment_status, FieldForm.READONLYTEXT, -1, 1) + val paymentStatus: String get() = SettingsActivity.getActivity()?.let { if (StatePayment.instance.hasPaid) it.getString(R.string.paid) else it.getString(R.string.not_paid); } ?: "Unknown"; - @FormField("Clear Payment", FieldForm.BUTTON, "Deletes license keys from app", 2) + @FormField(R.string.clear_payment, FieldForm.BUTTON, R.string.deletes_license_keys_from_app, 2) fun clearPayment() { StatePayment.instance.clearLicenses(); SettingsActivity.getActivity()?.let { - UIDialogs.toast(it, "Licenses cleared, might require app restart"); + UIDialogs.toast(it, it.getString(R.string.licenses_cleared_might_require_app_restart)); it.reloadSettings(); } } } - @FormField("Info", FieldForm.GROUP, "", 15) + @FormField(R.string.info, FieldForm.GROUP, -1, 15) var info = Info(); @Serializable class Info { - @FormField("Version Code", FieldForm.READONLYTEXT, "", 1, "code") + @FormField(R.string.version_code, FieldForm.READONLYTEXT, -1, 1, "code") var versionCode = BuildConfig.VERSION_CODE; - @FormField("Version Name", FieldForm.READONLYTEXT, "", 2) + @FormField(R.string.version_name, FieldForm.READONLYTEXT, -1, 2) var versionName = BuildConfig.VERSION_NAME; - @FormField("Version Type", FieldForm.READONLYTEXT, "", 3) + @FormField(R.string.version_type, FieldForm.READONLYTEXT, -1, 3) var versionType = BuildConfig.BUILD_TYPE; } diff --git a/app/src/main/java/com/futo/platformplayer/SettingsDev.kt b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt index 39812de6..15ec7117 100644 --- a/app/src/main/java/com/futo/platformplayer/SettingsDev.kt +++ b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt @@ -34,22 +34,22 @@ import kotlin.system.measureTimeMillis @Serializable() class SettingsDev : FragmentedStorageFileJson() { - @FormField("Developer Mode", FieldForm.TOGGLE, "", 0) + @FormField(R.string.developer_mode, FieldForm.TOGGLE, -1, 0) @Serializable(with = FlexibleBooleanSerializer::class) var developerMode: Boolean = false; - @FormField("Development Server", FieldForm.GROUP, - "Settings related to development server, be careful as it may open your phone to security vulnerabilities", 1) + @FormField(R.string.development_server, FieldForm.GROUP, + R.string.settings_related_to_development_server_be_careful_as_it_may_open_your_phone_to_security_vulnerabilities, 1) val devServerSettings: DeveloperServerFields = DeveloperServerFields(); @Serializable class DeveloperServerFields { - @FormField("Start Server on boot", FieldForm.TOGGLE, "", 0) + @FormField(R.string.start_server_on_boot, FieldForm.TOGGLE, -1, 0) @Serializable(with = FlexibleBooleanSerializer::class) var devServerOnBoot: Boolean = false; - @FormField("Start Server", FieldForm.BUTTON, - "Starts a DevServer on port 11337, may expose vulnerabilities.", 1) + @FormField(R.string.start_server, FieldForm.BUTTON, + R.string.starts_a_devServer_on_port_11337_may_expose_vulnerabilities, 1) fun startServer() { StateDeveloper.instance.runServer(); StateApp.instance.contextOrNull?.let { @@ -58,31 +58,31 @@ class SettingsDev : FragmentedStorageFileJson() { } } - @FormField("Experimental", FieldForm.GROUP, - "Settings related to development server, be careful as it may open your phone to security vulnerabilities", 2) + @FormField(R.string.experimental, FieldForm.GROUP, + R.string.settings_related_to_development_server_be_careful_as_it_may_open_your_phone_to_security_vulnerabilities, 2) val experimentalSettings: ExperimentalFields = ExperimentalFields(); @Serializable class ExperimentalFields { - @FormField("Background Subscription Testing", FieldForm.TOGGLE, "", 0) + @FormField(R.string.background_subscription_testing, FieldForm.TOGGLE, -1, 0) @Serializable(with = FlexibleBooleanSerializer::class) var backgroundSubscriptionFetching: Boolean = false; } - @FormField("Crash Me", FieldForm.BUTTON, - "Crashes the application on purpose", 2) + @FormField(R.string.crash_me, FieldForm.BUTTON, + R.string.crashes_the_application_on_purpose, 2) fun crashMe() { throw java.lang.IllegalStateException("This is an uncaught exception triggered on purpose!"); } - @FormField("Delete Announcements", FieldForm.BUTTON, - "Delete all announcements", 2) + @FormField(R.string.delete_announcements, FieldForm.BUTTON, + R.string.delete_all_announcements, 2) fun deleteAnnouncements() { StateAnnouncement.instance.deleteAllAnnouncements(); } - @FormField("Clear Cookies", FieldForm.BUTTON, - "Clear all cook from the CookieManager", 2) + @FormField(R.string.clear_cookies, FieldForm.BUTTON, + R.string.clear_all_cookies_from_the_cookieManager, 2) fun clearCookies() { val cookieManager: CookieManager = CookieManager.getInstance() cookieManager.removeAllCookies(null); @@ -90,13 +90,13 @@ class SettingsDev : FragmentedStorageFileJson() { @Contextual @Transient - @FormField("V8 Benchmarks", FieldForm.GROUP, - "Various benchmarks using the integrated V8 engine", 3) + @FormField(R.string.v8_benchmarks, FieldForm.GROUP, + R.string.various_benchmarks_using_the_integrated_v8_engine, 3) val v8Benchmarks: V8Benchmarks = V8Benchmarks(); class V8Benchmarks { @FormField( - "Test V8 Creation speed", FieldForm.BUTTON, - "Tests V8 creation times and running", 1 + R.string.test_v8_creation_speed, FieldForm.BUTTON, + R.string.tests_v8_creation_times_and_running, 1 ) fun testV8Creation() { var plugin: V8Plugin? = null; @@ -138,8 +138,8 @@ class SettingsDev : FragmentedStorageFileJson() { } @FormField( - "Test V8 Communication speed", FieldForm.BUTTON, - "Tests V8 communication speeds", 2 + R.string.test_v8_communication_speed, FieldForm.BUTTON, + R.string.tests_v8_communication_speeds, 2 ) fun testV8RunSpeeds() { var plugin: V8Plugin? = null; @@ -183,12 +183,12 @@ class SettingsDev : FragmentedStorageFileJson() { @Contextual @Transient - @FormField("V8 Script Testing", FieldForm.GROUP, "Various tests against a custom source", 4) + @FormField(R.string.v8_script_testing, FieldForm.GROUP, R.string.various_tests_against_a_custom_source, 4) val v8ScriptTests: V8ScriptTests = V8ScriptTests(); class V8ScriptTests { @Contextual private var _currentPlugin : JSClient? = null; - @FormField("Inject", FieldForm.BUTTON, "Injects a test source config (local) into V8", 1) + @FormField(R.string.inject, FieldForm.BUTTON, R.string.injects_a_test_source_config_local_into_v8, 1) fun testV8Init() { StateApp.instance.scope.launch(Dispatchers.IO) { try { @@ -204,7 +204,7 @@ class SettingsDev : FragmentedStorageFileJson() { } } } - @FormField("getHome", FieldForm.BUTTON, "Attempts to fetch 2 pages from getHome", 2) + @FormField(R.string.getHome, FieldForm.BUTTON, R.string.attempts_to_fetch_2_pages_from_getHome, 2) fun testV8Home() { runTestPlugin(_currentPlugin) { var home: IPager? = null; @@ -270,10 +270,10 @@ class SettingsDev : FragmentedStorageFileJson() { @Contextual @Transient - @FormField("Other", FieldForm.GROUP, "Others...", 5) + @FormField(R.string.other, FieldForm.GROUP, R.string.others_ellipsis, 5) val otherTests: OtherTests = OtherTests(); class OtherTests { - @FormField("Unsubscribe all", FieldForm.BUTTON, "Removes all subscriptions", -1) + @FormField(R.string.unsubscribe_all, FieldForm.BUTTON, R.string.removes_all_subscriptions, -1) fun unsubscribeAll() { val toUnsub = StateSubscriptions.instance.getSubscriptions(); UIDialogs.toast("Started unsubbing.. (${toUnsub.size})") @@ -282,24 +282,24 @@ class SettingsDev : FragmentedStorageFileJson() { }; UIDialogs.toast("Finished unsubbing.. (${toUnsub.size})") } - @FormField("Clear Downloads", FieldForm.BUTTON, "Deletes all ongoing downloads", 1) + @FormField(R.string.clear_downloads, FieldForm.BUTTON, R.string.deletes_all_ongoing_downloads, 1) fun clearDownloads() { StateDownloads.instance.getDownloading().forEach { StateDownloads.instance.removeDownload(it); }; } - @FormField("Clear All Downloaded", FieldForm.BUTTON, "Deletes all downloaded videos and related files", 2) + @FormField(R.string.clear_all_downloaded, FieldForm.BUTTON, R.string.deletes_all_downloaded_videos_and_related_files, 2) fun clearDownloaded() { StateDownloads.instance.getDownloadedVideos().forEach { StateDownloads.instance.deleteCachedVideo(it.id); }; } - @FormField("Delete Unresolved", FieldForm.BUTTON, "Deletes all unresolved source files", 3) + @FormField(R.string.delete_unresolved, FieldForm.BUTTON, R.string.deletes_all_unresolved_source_files, 3) fun cleanupDownloads() { StateDownloads.instance.cleanupDownloads(); } - @FormField("Fill storage till error", FieldForm.BUTTON, "Writes to disk till no space is left", 4) + @FormField(R.string.fill_storage_till_error, FieldForm.BUTTON, R.string.writes_to_disk_till_no_space_is_left, 4) fun fillStorage(context: Context, scope: CoroutineScope?) { val gigabuffer = ByteArray(1024 * 1024 * 128); var count: Long = 0; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt index 26af7a48..5595ae3e 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt @@ -1,5 +1,6 @@ package com.futo.platformplayer.api.media.platforms.js +import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.serializers.FlexibleBooleanSerializer import com.futo.platformplayer.views.fields.FieldForm @@ -66,15 +67,15 @@ class SourcePluginDescriptor { @Serializable class AppPluginSettings { - @FormField("Visibility", "group", "Enable where this plugin's content are visible.", 2) + @FormField(R.string.visibility, "group", R.string.enable_where_this_plugins_content_are_visible, 2) var tabEnabled = TabEnabled(); @Serializable class TabEnabled { - @FormField("Home", FieldForm.TOGGLE, "Show content in home tab", 1) + @FormField(R.string.home, FieldForm.TOGGLE, R.string.show_content_in_home_tab, 1) var enableHome: Boolean? = null; - @FormField("Search", FieldForm.TOGGLE, "Show content in search results", 2) + @FormField(R.string.search, FieldForm.TOGGLE, R.string.show_content_in_search_results, 2) var enableSearch: Boolean? = null; } diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt index 98003b04..6e093fed 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt @@ -66,8 +66,8 @@ class ButtonField : BigButton, IField { val attrField = method.getAnnotation(FormField::class.java); val attrButtonField = method.getAnnotation(FormFieldButton::class.java); if(attrField != null) { - super.withPrimaryText(attrField.title) - .withSecondaryText(attrField.subtitle) + super.withPrimaryText(context.getString(attrField.title)) + .withSecondaryText(if (attrField.subtitle != -1) context.getString(attrField.subtitle) else "") .withSecondaryTextMaxLines(2); descriptor = attrField; } diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt index 6ae96479..c1118f2f 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt @@ -37,7 +37,7 @@ class DropdownField : TableRow, IField { override val onChanged = Event2(); - constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){ + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs){ inflate(context, R.layout.field_dropdown, this); _spinner = findViewById(R.id.field_spinner); _title = findViewById(R.id.field_title); @@ -77,17 +77,17 @@ class DropdownField : TableRow, IField { return this; } - override fun fromField(obj : Any, field : Field, formField: FormField?) : DropdownField { + override fun fromField(obj: Any, field: Field, formField: FormField?) : DropdownField { this._field = field; this._obj = obj; val attrField = formField ?: field.getAnnotation(FormField::class.java); if(attrField != null) { - _title.text = attrField.title; + _title.text = context.getString(attrField.title); descriptor = attrField; - if(attrField.subtitle.isNotBlank()) { - _description.text = attrField.subtitle; + if(attrField.subtitle != -1) { + _description.text = context.getString(attrField.subtitle); _description.visibility = View.VISIBLE; } else diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt b/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt index e072f455..0376ccb1 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt @@ -6,7 +6,7 @@ import java.lang.reflect.Field @Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) -annotation class FormField(val title : String, val type : String, val subtitle : String = "", val order : Int = 0, val id : String = "") +annotation class FormField(val title: Int, val type: String, val subtitle: Int = -1, val order: Int = 0, val id: String = "") interface IField { var descriptor: FormField?; diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt index d26e09fe..a963da7d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt @@ -101,8 +101,8 @@ class GroupField : LinearLayout, IField { val attrField = formField ?: field.getAnnotation(FormField::class.java); //TODO: Get this to work as default if(attrField != null) { - _title.text = attrField.title; - _subtitle.text = attrField.subtitle; + _title.text = context.getString(attrField.title); + _subtitle.text = if (attrField.subtitle != -1) context.getString(attrField.subtitle) else ""; descriptor = attrField; } else diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt index baeaa31a..d5187a78 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt @@ -42,7 +42,7 @@ class ReadOnlyTextField : TableRow, IField { val attrField = formField ?: field.getAnnotation(FormField::class.java); if(attrField != null) { - _title.text = attrField.title; + _title.text = context.getString(attrField.title); descriptor = attrField; } else @@ -60,7 +60,7 @@ class ReadOnlyTextField : TableRow, IField { val attrField = formField ?: field.getAnnotation(FormField::class.java); if(attrField != null) { - _title.text = attrField.title; + _title.text = context.getString(attrField.title); descriptor = attrField; } else diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt index 0750c869..6f16bed1 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt @@ -64,16 +64,16 @@ class ToggleField : TableRow, IField { val attrField = formField ?: field.getAnnotation(FormField::class.java); if(attrField != null) { - _title.text = attrField.title; + _title.text = context.getString(attrField.title); descriptor = attrField; } else _title.text = field.name; - if(attrField?.subtitle?.isEmpty() != false) + if(attrField == null || attrField.subtitle == -1) _description.visibility = View.GONE; else { - _description.text = attrField.subtitle; + _description.text = context.getString(attrField.subtitle); _description.visibility = View.VISIBLE; } diff --git a/app/src/main/res/layout/activity_add_source.xml b/app/src/main/res/layout/activity_add_source.xml index ad96c703..3cee4845 100644 --- a/app/src/main/res/layout/activity_add_source.xml +++ b/app/src/main/res/layout/activity_add_source.xml @@ -83,7 +83,7 @@ android:textColor="@color/white" android:layout_marginTop="20dp" android:fontFamily="@font/inter_light" - android:text="Permissions" /> + android:text="@string/permissions" /> + android:text="@string/security_warnings" /> + android:text="@string/these_are_warnings_of_plugin_behavior_and_implementation" /> - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_source_options.xml b/app/src/main/res/layout/activity_add_source_options.xml index 3bfeac54..9df6f1d3 100644 --- a/app/src/main/res/layout/activity_add_source_options.xml +++ b/app/src/main/res/layout/activity_add_source_options.xml @@ -52,8 +52,8 @@ android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginBottom="5dp" - app:buttonText="Install by QR" - app:buttonSubText="Install a plugin by scanning a QR code" + app:buttonText="@string/install_by_qr" + app:buttonSubText="@string/install_a_plugin_by_scanning_a_qr_code" app:buttonIcon="@drawable/ic_qr" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_captcha.xml b/app/src/main/res/layout/activity_captcha.xml index 051f346b..5f8a13e3 100644 --- a/app/src/main/res/layout/activity_captcha.xml +++ b/app/src/main/res/layout/activity_captcha.xml @@ -16,7 +16,7 @@ android:layout_height="wrap_content" android:layout_margin="4dp" android:layout_gravity="center_vertical" - android:text="Please enter the captcha and close when finished" /> + android:text="@string/please_enter_the_captcha_and_close_when_finished" />