diff --git a/AnkiDroid/src/main/res/values/01-core.xml b/AnkiDroid/src/main/res/values/01-core.xml index 861e92c4bbfc..ca6e030e65a9 100644 --- a/AnkiDroid/src/main/res/values/01-core.xml +++ b/AnkiDroid/src/main/res/values/01-core.xml @@ -20,11 +20,11 @@ --> - Decks - Card browser - Statistics - Settings - Help + Decks + Card browser + Statistics + Settings + Help Drawing Send feedback @@ -59,36 +59,36 @@ Hard Good Easy - Import - Undo - Redo - Undo stroke + Import + Undo + Redo + Undo stroke Move all to deck - Unbury - Rename deck + Unbury + Rename deck Create shortcut Browse cards Edit description - Add - Add note + Add + Add note Sync account - Hide / delete - Bury card - Bury note - Suspend card - Suspend note - Bury - Suspend - Delete note + Hide / delete + Bury card + Bury note + Suspend card + Suspend note + Bury + Suspend + Delete note Flag Rename flags - Flag card - Edit tags + Flag card + Edit tags Really delete this note and all its cards?\n%s - Lookup in %1$s - Mark note + Lookup in %1$s + Mark note Unmark note - Enable voice playback + Enable voice playback Disable voice playback Create deck Create filtered deck @@ -99,28 +99,28 @@ of the permissions/app info screen will differ between devices. --> Please grant AnkiDroid the ‘Storage’ permission to continue Rebuilding filtered deck… - Rebuild - Empty + Rebuild + Empty Create subdeck Emptying filtered deck… Custom study session Rename the existing custom study deck first Deck Search This deck is empty - Deck Search + Deck Search Invalid deck name Congratulations! You have finished for now. Deck finished for now! %s No cards are due yet Device storage not mounted - Sync + Sync Do you want to cancel the sync? Continue sync Sync cancelled Cancelling…\nThis may take some time. Syncing media - Export deck + Export deck Export collection Nothing @@ -128,10 +128,10 @@ Card types Front template Back template - Styling - Card Browser Appearance - Deck override - Insert field + Styling + Card Browser Appearance + Deck override + Insert field Select field At least one card type is required @@ -149,7 +149,7 @@ Browser appearance - Card info + Card info Read and write to the AnkiDroid database diff --git a/AnkiDroid/src/main/res/values/02-strings.xml b/AnkiDroid/src/main/res/values/02-strings.xml index 2d55d49d2d48..9163d31d9dfe 100644 --- a/AnkiDroid/src/main/res/values/02-strings.xml +++ b/AnkiDroid/src/main/res/values/02-strings.xml @@ -32,22 +32,22 @@ Tag name Add/filter tags - Add tag - Check/uncheck all tags - Filter tags + Add tag + Check/uncheck all tags + Filter tags You haven’t added any tags yet Updated to version %s - Save whiteboard - Enable stylus writing + Save whiteboard + Enable stylus writing Disable stylus writing - Enable whiteboard + Enable whiteboard Disable whiteboard Show whiteboard - Hide whiteboard - Clear whiteboard - Replay audio + Hide whiteboard + Clear whiteboard + Replay audio Card marked as leech and suspended Card marked as leech Unknown error @@ -74,14 +74,14 @@ - Edit note + Edit note Discard No cards created. Please fill in more fields The current note type did not produce any cards.\nPlease choose another note type, or click ‘Cards’ and add a field substitution Cloze deletions will only work on a Cloze note type Saving note Saving note type - Save + Save Close Select %1$s (from “%2$s”) @@ -102,19 +102,19 @@ AnkiDroid card - Copy note - Reposition - Reset progress - Reschedule - Preview - Copy as Markdown + Copy note + Reposition + Reset progress + Reschedule + Preview + Copy as Markdown %1$d of %2$d - Rename + Rename - Check - Check database - Check media - Empty cards + Check + Check database + Check media + Empty cards Type answer: unknown field %s Deleting deck… @@ -152,11 +152,11 @@ Share Save to Saving exported file… - Options - Deck options + Options + Deck options Study options - Set TTS language - Custom study + Set TTS language + Custom study More This is a special deck for studying outside of the normal schedule. Cards will be automatically returned to their original decks after you review them. Deleting this deck from the deck list will return all remaining cards to their original deck. Steps must be numbers greater than 0 @@ -183,7 +183,7 @@ Select image Remove background? - Restore Default + Restore Default Blank @@ -192,7 +192,7 @@ Whiteboard image saved to %s Whiteboard pen color - Whiteboard editor + Whiteboard editor Detected automated test. If you are a human, contact AnkiDroid support @@ -212,14 +212,14 @@ Card Content Error: Failed to load ‘%s’ - Search decks + Search decks Fatal Error AnkiDroid relies on the System WebView which is unavailable. This can happen if the system is installing updates. Please try again in a few minutes.\n\n%s - Font size + Font size - Show toolbar + Show toolbar Format as Bold Format as Italic @@ -249,8 +249,8 @@ You may need to use iManager to allow AnkiDroid to add shortcuts Your home screen does not allow AnkiDroid to add shortcuts Error adding shortcut: %s - Capitalize sentences - Scroll toolbar + Capitalize sentences + Scroll toolbar @@ -279,7 +279,7 @@ Please check your network connection Search using deck name Download aborted, the external storage is not available - Home + Home @@ -393,7 +393,7 @@ opening the system text to speech settings fails"> %d cards unburied - Reposition + Reposition Record Stop diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml index f376554f94c3..bed5ac2fd648 100644 --- a/AnkiDroid/src/main/res/values/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values/03-dialogs.xml @@ -20,7 +20,7 @@ --> Check database? This may take a long time - Delete deck + Delete deck Delete deck? Delete all cards in %1$s? It contains %2$d card @@ -81,7 +81,7 @@ Continue Processing… Create - Delete + Delete Disable Overwrite @@ -188,7 +188,7 @@ Reddit Report a Bug - Support AnkiDroid + Support AnkiDroid Donate Develop Rate diff --git a/AnkiDroid/src/main/res/values/07-cardbrowser.xml b/AnkiDroid/src/main/res/values/07-cardbrowser.xml index 827edb5f235d..024561696057 100644 --- a/AnkiDroid/src/main/res/values/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values/07-cardbrowser.xml @@ -34,23 +34,23 @@ Delete note Delete notes - Change deck + Change deck Delete note? - Filter marked - Filter suspended - Filter by tag - Filter by flag - My searches - Save search + Filter marked + Filter suspended + Filter by tag + Filter by flag + My searches + Save search Choose a saved search Name for the current search You can’t save a search without a name Name exists No note to edit Delete “%1$s”? - Change display order + Change display order Search - Search + Search Choose display order Tags @@ -65,8 +65,8 @@ By reviews By lapses - Select all - Select none + Select all + Select none (new) (filtered) (learning) diff --git a/AnkiDroid/src/main/res/values/09-backup.xml b/AnkiDroid/src/main/res/values/09-backup.xml index c1a117cf7bf3..a707201da866 100644 --- a/AnkiDroid/src/main/res/values/09-backup.xml +++ b/AnkiDroid/src/main/res/values/09-backup.xml @@ -21,7 +21,7 @@ Backup not saved. Not enough space left on your storage. Lower the backup depth or remove some other files. Less than %d MB space on your storage left.\nFree some space to avoid data loss. - Restore from backup + Restore from backup New collection Delete collection and create new one Delete the collection and create a new one? This will drop all your learning progress and delete all cards. diff --git a/AnkiDroid/src/main/res/values/10-preferences.xml b/AnkiDroid/src/main/res/values/10-preferences.xml index be82e628147d..4c057a26ecfa 100644 --- a/AnkiDroid/src/main/res/values/10-preferences.xml +++ b/AnkiDroid/src/main/res/values/10-preferences.xml @@ -222,7 +222,7 @@ Search Limit to Cards selected by - Reschedule + Reschedule Reschedule cards based on my answers in this deck Enable second filter Define custom steps @@ -260,22 +260,22 @@ Move a joystick/motion controller User actions Trigger JavaScript from the review screen - User action 1 - User action 2 - User action 3 - User action 4 - User action 5 - User action 6 - User action 7 - User action 8 - User action 9 + User action 1 + User action 2 + User action 3 + User action 4 + User action 5 + User action 6 + User action 7 + User action 8 + User action 9 Toggle auto advance Select card side Question Answer - Question & Answer + Question & Answer Q: %s Do you want to crop this image? - Crop image + Crop image Current image size is %sMB. Default image size limit is 1MB. Do you want to compress it? "Select image failed, Please retry" App returned an unexpected value. You may need to use a different app Field Contents - Reselect + Reselect diff --git a/AnkiDroid/src/main/res/values/17-model-manager.xml b/AnkiDroid/src/main/res/values/17-model-manager.xml index fbe89fd4b1f8..74d003777247 100644 --- a/AnkiDroid/src/main/res/values/17-model-manager.xml +++ b/AnkiDroid/src/main/res/values/17-model-manager.xml @@ -2,7 +2,7 @@ - Manage note types + Manage note types Manage fields @@ -41,7 +41,7 @@ Edit fields. - Add field + Add field Delete field Rename field Set keyboard language hint @@ -74,7 +74,7 @@ Set ‘%1$s’ default deck to ‘%2$s’ Removed default deck for ‘%s’ Select deck to place new ‘%s’ cards into - Filter decks + Filter decks diff --git a/AnkiDroid/src/main/res/values/18-standard-models.xml b/AnkiDroid/src/main/res/values/18-standard-models.xml index 05c5ec133103..9f4fe8d83be1 100644 --- a/AnkiDroid/src/main/res/values/18-standard-models.xml +++ b/AnkiDroid/src/main/res/values/18-standard-models.xml @@ -2,6 +2,6 @@ - Front - Back + Front + Back diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.kt b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.kt index 4bf44aadfa28..449eba43bda3 100644 --- a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.kt +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.kt @@ -61,7 +61,9 @@ class IssueRegistry : IssueRegistry() { NonPositionalFormatSubstitutions.ISSUE, TranslationTypo.ISSUE, FixedPreferencesTitleLength.PREFERENCES_ISSUE_MAX_LENGTH, + FixedPreferencesTitleLength.MENU_ISSUE_MAX_LENGTH, FixedPreferencesTitleLength.PREFERENCES_ISSUE_TITLE_LENGTH, + FixedPreferencesTitleLength.MENU_ISSUE_TITLE_LENGTH, VariableNamingDetector.ISSUE, InvalidStringFormatDetector.ISSUE, AvoidAlertDialogUsage.ISSUE diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLength.kt b/lint-rules/src/main/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLength.kt index 6189de6e7e5e..0cb131e26028 100644 --- a/lint-rules/src/main/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLength.kt +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLength.kt @@ -33,7 +33,7 @@ import java.lang.IllegalArgumentException import java.util.Locale /** - * Detector for preference's title whose length may be bigger than 41 character. + * Detector for preference's title whose length may be bigger than 41 character and for menu's title whose length may be bigger than 29 characters. * There is an error for each string which is longer than 41 character in English. * There is also an error for each string which does not have `maxLength` set to at most 41. * @@ -42,22 +42,27 @@ import java.util.Locale class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { companion object { private const val PREFERENCES_ID_TITLE_LENGTH = "FixedPreferencesTitleLength" + private const val MENU_ID_TITLE_LENGTH = "FixedMenuTitleLength" private const val PREFERENCES_ID_MAX_LENGTH = "PreferencesTitleMaxLengthAttr" + private const val MENU_ID_MAX_LENGTH = "MenuTitleMaxLengthAttr" private const val PREFERENCES_TITLE_MAX_LENGTH = 41 + private const val MENU_TITLE_MAX_LENGTH = 28 private const val PREFERENCES_DESCRIPTION_TITLE_LENGTH = "Preference titles should be less than $PREFERENCES_TITLE_MAX_LENGTH characters" + private const val MENU_DESCRIPTION_TITLE_LENGTH = "Preference titles should be less than $MENU_TITLE_MAX_LENGTH characters" private const val PREFERENCES_DESCRIPTION_MAX_LENGTH = """Preference titles should contain maxLength="$PREFERENCES_TITLE_MAX_LENGTH" attribute""" + private const val MENU_DESCRIPTION_MAX_LENGTH = """Preference titles should contain maxLength="$MENU_TITLE_MAX_LENGTH" attribute""" - // Around 42 is a hard max on emulators, likely smaller in reality, so use a buffer - private const val PREFERENCES_EXPLANATION_TITLE_LENGTH = - "A title with more than $PREFERENCES_TITLE_MAX_LENGTH characters may fail to display on smaller screens" + // Around 42 (resp. 29) characters is a hard max on emulators in preferences (resp. menu), likely smaller in reality, so use a buffer + private const val PREFERENCES_EXPLANATION_TITLE_LENGTH = "A preference title with more than $PREFERENCES_TITLE_MAX_LENGTH characters may fail to display on smaller screens" + private const val MENU_EXPLANATION_TITLE_LENGTH = "A menu title with more than $MENU_TITLE_MAX_LENGTH characters may fail to display on smaller screens" // Read More: https://support.crowdin.com/file-formats/android-xml/ - private const val PREFERENCES_EXPLANATION_MAX_LENGTH = "Preference Title should contain maxLength attribute " + - "because it fixes translated string length" + private const val PREFERENCES_EXPLANATION_MAX_LENGTH = "Preference Title should contain maxLength attribute because it fixes translated string length" + private const val MENU_EXPLANATION_MAX_LENGTH = "Preference Title should contain maxLength attribute because it fixes translated string length" private val implementation = Implementation(FixedPreferencesTitleLength::class.java, Scope.RESOURCE_FILE_SCOPE) val PREFERENCES_ISSUE_TITLE_LENGTH: Issue = Issue.create( @@ -69,6 +74,16 @@ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { Constants.ANKI_XML_SEVERITY, implementation ) + val MENU_ISSUE_TITLE_LENGTH: Issue = Issue.create( + MENU_ID_TITLE_LENGTH, + MENU_DESCRIPTION_TITLE_LENGTH, + MENU_EXPLANATION_TITLE_LENGTH, + Constants.ANKI_XML_CATEGORY, + Constants.ANKI_XML_PRIORITY, + Constants.ANKI_XML_SEVERITY, + implementation + ) + val PREFERENCES_ISSUE_MAX_LENGTH: Issue = Issue.create( PREFERENCES_ID_MAX_LENGTH, PREFERENCES_DESCRIPTION_MAX_LENGTH, @@ -78,6 +93,15 @@ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { Constants.ANKI_XML_SEVERITY, implementation ) + val MENU_ISSUE_MAX_LENGTH: Issue = Issue.create( + MENU_ID_MAX_LENGTH, + MENU_DESCRIPTION_MAX_LENGTH, + MENU_EXPLANATION_MAX_LENGTH, + Constants.ANKI_XML_CATEGORY, + Constants.ANKI_XML_PRIORITY, + Constants.ANKI_XML_SEVERITY, + implementation + ) private const val ATTR_TITLE = "android:title" private const val ATTR_NAME = "name" private const val ATTR_MAX_LENGTH = "maxLength" @@ -90,6 +114,11 @@ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { */ private val titlesOfPreferenceScreens: MutableSet = HashSet() + /** + * Titles of the resources in the menu/ folder. + */ + private val titlesOfMenuScreens: MutableSet = HashSet() + /** * String resources. * I.e. after the end of [visitElement], it'll map "pref__delete_unused_media_files__title" to the element @@ -105,14 +134,21 @@ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { } override fun visitElement(context: XmlContext, element: Element) { - if ("xml" == context.file.parentFile.name && element.hasAttribute(ATTR_TITLE)) { + if (element.hasAttribute(ATTR_TITLE)) { + val folderName = context.file.parentFile.name + val titlesToHandle = + when (folderName) { + "xml" -> titlesOfPreferenceScreens + "menu" -> titlesOfMenuScreens + else -> return + } /* Add the `android:title`'s resource name (without "@string/") to [stringResources] if the element has this attribute and the file belongs to src/main/res/xml. */ // Removing the "@string/" part. val titleAttribute = element.getAttribute(ATTR_TITLE) val stringName = titleAttribute.substringAfter("@string/", "").ifEmpty { return } // the entry `stringName` may already exists. Losing the first entry is not an issue, as it won't actually hide that there are issues. - titlesOfPreferenceScreens.add(stringName) + titlesToHandle.add(stringName) return } if ("values" == context.file.parentFile.name && @@ -124,23 +160,28 @@ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { } override fun appliesTo(folderType: ResourceFolderType): Boolean { - return folderType == ResourceFolderType.XML || folderType == ResourceFolderType.VALUES + return folderType == ResourceFolderType.XML || folderType == ResourceFolderType.VALUES || folderType == ResourceFolderType.MENU } override fun afterCheckEachProject(context: Context) { - for (title in titlesOfPreferenceScreens) { + checkFolder(context, titlesOfPreferenceScreens, PREFERENCES_ISSUE_MAX_LENGTH, PREFERENCES_ISSUE_MAX_LENGTH, PREFERENCES_ISSUE_TITLE_LENGTH, "Preference", PREFERENCES_TITLE_MAX_LENGTH) + checkFolder(context, titlesOfMenuScreens, MENU_ISSUE_MAX_LENGTH, MENU_ISSUE_MAX_LENGTH, MENU_ISSUE_TITLE_LENGTH, "Menu", MENU_TITLE_MAX_LENGTH) + } + + fun checkFolder(context: Context, titles: Set, missingMaxLengthIssueIssue: Issue, wrongMaxLengthIssue: Issue, stringTooLongIssue: Issue, folder: String, maxLength: Int) { + for (title in titles) { val stringHandle = stringResources[title] ?: throw IllegalArgumentException(title) val stringElement: Element = stringHandle.clientData as Element if (!stringElement.hasAttribute(ATTR_MAX_LENGTH)) { - val message = String.format(Locale.ENGLISH, "Preference title '%s' is missing maxLength=\"%d\" attribute.", title, PREFERENCES_TITLE_MAX_LENGTH) - context.report(PREFERENCES_ISSUE_MAX_LENGTH, stringHandle.resolve(), message) - } else if (stringElement.getAttribute(ATTR_MAX_LENGTH).toInt() > PREFERENCES_TITLE_MAX_LENGTH) { - val message = String.format(Locale.ENGLISH, "Preference title '%s' has maxLength=\"%s\". Its max length should be at most %d.", title, stringElement.getAttribute(ATTR_MAX_LENGTH), PREFERENCES_TITLE_MAX_LENGTH) - context.report(PREFERENCES_ISSUE_MAX_LENGTH, stringHandle.resolve(), message) + val message = String.format(Locale.ENGLISH, "$folder title '%s' is missing maxLength=\"%d\" attribute.", title, maxLength) + context.report(missingMaxLengthIssueIssue, stringHandle.resolve(), message) + } else if (stringElement.getAttribute(ATTR_MAX_LENGTH).toInt() > maxLength) { + val message = String.format(Locale.ENGLISH, "$folder title '%s' has maxLength=\"%s\". Its max length should be at most %d.", title, stringElement.getAttribute(ATTR_MAX_LENGTH), maxLength) + context.report(wrongMaxLengthIssue, stringHandle.resolve(), message) } - if (stringElement.textContent.length > PREFERENCES_TITLE_MAX_LENGTH) { - val message = String.format(Locale.ENGLISH, "Preference title '%s' must be less than %d characters (currently %d).", title, PREFERENCES_TITLE_MAX_LENGTH, stringElement.textContent.length) - context.report(PREFERENCES_ISSUE_TITLE_LENGTH, stringHandle.resolve(), message) + if (stringElement.textContent.length > maxLength) { + val message = String.format(Locale.ENGLISH, "$folder title '%s' must be less than %d characters (currently %d).", title, maxLength, stringElement.textContent.length) + context.report(stringTooLongIssue, stringHandle.resolve(), message) } } }