diff --git a/AnkiDroid/src/main/res/values/01-core.xml b/AnkiDroid/src/main/res/values/01-core.xml index c057c2113962..861e92c4bbfc 100644 --- a/AnkiDroid/src/main/res/values/01-core.xml +++ b/AnkiDroid/src/main/res/values/01-core.xml @@ -21,7 +21,7 @@ Decks - Card browser + Card browser Statistics Settings Help @@ -53,15 +53,15 @@ Type answer - Show answer + Show answer Hide answer - Again - Hard - Good + Again + Hard + Good Easy Import Undo - Redo + Redo Undo stroke Move all to deck Unbury @@ -70,25 +70,25 @@ Browse cards Edit description Add - Add note + Add note Sync account - Hide / delete - Bury card - Bury note - Suspend card - Suspend note - Bury - Suspend - Delete note - Flag + Hide / delete + Bury card + Bury note + Suspend card + Suspend note + Bury + Suspend + Delete note + Flag Rename flags Flag card - Edit tags + Edit tags Really delete this note and all its cards?\n%s Lookup in %1$s - Mark note + Mark note Unmark note - Enable voice playback + Enable voice playback Disable voice playback Create deck Create filtered deck @@ -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 87fcd7bf1a1f..2d55d49d2d48 100644 --- a/AnkiDroid/src/main/res/values/02-strings.xml +++ b/AnkiDroid/src/main/res/values/02-strings.xml @@ -39,15 +39,15 @@ 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 @@ -59,7 +59,7 @@ System WebView Rendering Failure System WebView failed to render card \'%1$s\'.\n %2$s - Flags + Flags Modify today’s new card limit @@ -74,7 +74,7 @@ - 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 @@ -153,9 +153,9 @@ Save to Saving exported file… Options - Deck options + Deck options Study options - Set TTS language + 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. @@ -179,8 +179,8 @@ Error loading page: %s - Background - Select image + Background + Select image Remove background? Restore Default @@ -191,8 +191,8 @@ Failed to save whiteboard image. %s Whiteboard image saved to %s - Whiteboard pen color - Whiteboard editor + Whiteboard pen color + Whiteboard editor Detected automated test. If you are a human, contact AnkiDroid support diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml index c32b7eab7b0b..f376554f94c3 100644 --- a/AnkiDroid/src/main/res/values/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values/03-dialogs.xml @@ -232,7 +232,7 @@ Backup your collection You haven\'t backed up your collection in a while. You should do this now to prevent data loss - Backup + Backup Disable reminder Later Don\'t show again diff --git a/AnkiDroid/src/main/res/values/04-network.xml b/AnkiDroid/src/main/res/values/04-network.xml index 6d02e38f3e7a..756a4d828ea4 100644 --- a/AnkiDroid/src/main/res/values/04-network.xml +++ b/AnkiDroid/src/main/res/values/04-network.xml @@ -53,7 +53,7 @@ Local Remote - One-way sync + One-way sync One way sync requested The requested change will require a full upload of the database when you next synchronize your collection. If you have reviews or other changes waiting on another device that haven’t been synchronized here yet, they will be lost. Continue? Due to a bug in the previously installed version of AnkiDroid, it’s recommended to force a full sync. diff --git a/AnkiDroid/src/main/res/values/05-feedback.xml b/AnkiDroid/src/main/res/values/05-feedback.xml index 1c99d0df3a21..490eb933db08 100644 --- a/AnkiDroid/src/main/res/values/05-feedback.xml +++ b/AnkiDroid/src/main/res/values/05-feedback.xml @@ -22,7 +22,7 @@ - Error reporting mode + Error reporting mode Disclaimer: We don’t collect any personal information such as your email address, phone number, or your phone IMEI. We do collect some information about your device such as the manufacturer, model and version of Android, as well as the nature of reported errors themselves and the steps which led up to them occurring. AnkiDroid Feedback Enter your feedback or information about the nature of any errors you’re reporting. diff --git a/AnkiDroid/src/main/res/values/10-preferences.xml b/AnkiDroid/src/main/res/values/10-preferences.xml index 29a67ba8e6cb..be82e628147d 100644 --- a/AnkiDroid/src/main/res/values/10-preferences.xml +++ b/AnkiDroid/src/main/res/values/10-preferences.xml @@ -314,14 +314,10 @@ this formatter is used if the bind only applies to both the question and the ans

You can restore from a backup and export your collection in the deck list menu. ]]>
- Minutes between automatic backups - Daily backups to keep - Weekly backups to keep - Monthly backups to keep + Minutes between automatic backups + Daily backups to keep + Weekly backups to keep + Monthly backups to keep - Answer again - Answer hard - Answer good - Answer easy - Play media - Close reviewer - Toggle Red Flag - Toggle Orange Flag - Toggle Green Flag - Toggle Blue Flag - Toggle Pink Flag - Toggle Turquoise Flag - Toggle Purple Flag - Remove Flag - Page Up - Page Down - Close reviewer and Sync - Toggle Whiteboard - Show Hint - Show All Hints - Record Voice - Replay Voice - Save Recording + Answer again + Answer hard + Answer good + Answer easy + Play media + Close reviewer + Toggle Red Flag + Toggle Orange Flag + Toggle Green Flag + Toggle Blue Flag + Toggle Pink Flag + Toggle Turquoise Flag + Toggle Purple Flag + Remove Flag + Page Up + Page Down + Close reviewer and Sync + Toggle Whiteboard + Show Hint + Show All Hints + Record Voice + Replay Voice + Save Recording
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 df21649549a1..4bf44aadfa28 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 @@ -60,8 +60,8 @@ class IssueRegistry : IssueRegistry() { PrintStackTraceUsage.ISSUE, NonPositionalFormatSubstitutions.ISSUE, TranslationTypo.ISSUE, - FixedPreferencesTitleLength.ISSUE_MAX_LENGTH, - FixedPreferencesTitleLength.ISSUE_TITLE_LENGTH, + FixedPreferencesTitleLength.PREFERENCES_ISSUE_MAX_LENGTH, + FixedPreferencesTitleLength.PREFERENCES_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 3e255f800aaa..6189de6e7e5e 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 @@ -27,48 +27,52 @@ import com.android.tools.lint.detector.api.ResourceXmlDetector import com.android.tools.lint.detector.api.Scope import com.android.tools.lint.detector.api.XmlContext import com.android.tools.lint.detector.api.XmlScanner -import com.android.utils.Pair -import com.google.common.annotations.VisibleForTesting import com.ichi2.anki.lint.utils.Constants import org.w3c.dom.Element +import java.lang.IllegalArgumentException import java.util.Locale +/** + * Detector for preference's title whose length may be bigger than 41 character. + * 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. + * + * Recall that `title` here is not the title of the preference screen, but the title of the preference entry. That is the text in bold that appears on a single line. + */ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { companion object { - @VisibleForTesting - val ID_TITLE_LENGTH = "FixedPreferencesTitleLength" + private const val PREFERENCES_ID_TITLE_LENGTH = "FixedPreferencesTitleLength" + + private const val PREFERENCES_ID_MAX_LENGTH = "PreferencesTitleMaxLengthAttr" - @VisibleForTesting - val ID_MAX_LENGTH = "PreferencesTitleMaxLengthAttr" - private const val PREFERENCE_TITLE_MAX_LENGTH = 41 + private const val PREFERENCES_TITLE_MAX_LENGTH = 41 - @VisibleForTesting - val DESCRIPTION_TITLE_LENGTH = "Preference titles should be less than $PREFERENCE_TITLE_MAX_LENGTH characters" + private const val PREFERENCES_DESCRIPTION_TITLE_LENGTH = "Preference titles should be less than $PREFERENCES_TITLE_MAX_LENGTH characters" - @VisibleForTesting - val DESCRIPTION_MAX_LENGTH = """Preference titles should contain maxLength="$PREFERENCE_TITLE_MAX_LENGTH" attribute""" + private const val PREFERENCES_DESCRIPTION_MAX_LENGTH = """Preference titles should contain maxLength="$PREFERENCES_TITLE_MAX_LENGTH" attribute""" // Around 42 is a hard max on emulators, likely smaller in reality, so use a buffer - private const val EXPLANATION_TITLE_LENGTH = - "A title with more than $PREFERENCE_TITLE_MAX_LENGTH characters may fail to display on smaller screens" + private const val PREFERENCES_EXPLANATION_TITLE_LENGTH = + "A title with more than $PREFERENCES_TITLE_MAX_LENGTH characters may fail to display on smaller screens" // Read More: https://support.crowdin.com/file-formats/android-xml/ - private const val EXPLANATION_MAX_LENGTH = "Preference Title should contain maxLength attribute " + + private const val PREFERENCES_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 ISSUE_TITLE_LENGTH: Issue = Issue.create( - ID_TITLE_LENGTH, - DESCRIPTION_TITLE_LENGTH, - EXPLANATION_TITLE_LENGTH, + val PREFERENCES_ISSUE_TITLE_LENGTH: Issue = Issue.create( + PREFERENCES_ID_TITLE_LENGTH, + PREFERENCES_DESCRIPTION_TITLE_LENGTH, + PREFERENCES_EXPLANATION_TITLE_LENGTH, Constants.ANKI_XML_CATEGORY, Constants.ANKI_XML_PRIORITY, Constants.ANKI_XML_SEVERITY, implementation ) - val ISSUE_MAX_LENGTH: Issue = Issue.create( - ID_MAX_LENGTH, - DESCRIPTION_MAX_LENGTH, - EXPLANATION_MAX_LENGTH, + val PREFERENCES_ISSUE_MAX_LENGTH: Issue = Issue.create( + PREFERENCES_ID_MAX_LENGTH, + PREFERENCES_DESCRIPTION_MAX_LENGTH, + PREFERENCES_EXPLANATION_MAX_LENGTH, Constants.ANKI_XML_CATEGORY, Constants.ANKI_XML_PRIORITY, Constants.ANKI_XML_SEVERITY, @@ -79,34 +83,44 @@ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { private const val ATTR_MAX_LENGTH = "maxLength" } - private val xmlData: MutableSet = HashSet() - private val valuesData: MutableMap> = HashMap>() + /** + * Titles of the resources in the xml/ folder. + * I.e. after the end of [visitElement], it contains + * "pref__delete_unused_media_files__title" from src/main/res/xml/manage_space + */ + private val titlesOfPreferenceScreens: MutableSet = HashSet() + + /** + * String resources. + * I.e. after the end of [visitElement], it'll map "pref__delete_unused_media_files__title" to the element + * ``` + * Delete unused media files + * ``` + * of src/main/res/values/10-preferences.xml, and its handle. + */ + private val stringResources: MutableMap = HashMap() override fun getApplicableElements(): Collection? { return ALL } override fun visitElement(context: XmlContext, element: Element) { - /* 1. This condition checks that current file has xml as a parent file. - 2. Checks that element contains title attribute or not. - If both of conditions are true then set the value in xmlSet. - */ if ("xml" == context.file.parentFile.name && element.hasAttribute(ATTR_TITLE)) { - val stringName = element.getAttribute(ATTR_TITLE).substring(8) - xmlData.add(stringName) + /* 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) return } - if ("values" != context.file.parentFile.name) { - return + if ("values" == context.file.parentFile.name && + "string" == element.tagName + ) { + // Let's consider the `string`s tag of "src/main/res/values/*.xml" + stringResources[element.getAttribute(ATTR_NAME)] = context.createLocationHandle(element).apply { clientData = element } } - if ("10-preferences.xml" != context.file.name) { - return - } - if ("resources" == element.tagName) { - return - } - val handle: Handle = context.createLocationHandle(element) - handle.clientData = element - valuesData[element.getAttribute(ATTR_NAME)] = Pair.of(element, handle) } override fun appliesTo(folderType: ResourceFolderType): Boolean { @@ -114,22 +128,19 @@ class FixedPreferencesTitleLength : ResourceXmlDetector(), XmlScanner { } override fun afterCheckEachProject(context: Context) { - for (title in xmlData) { - if (!valuesData.containsKey(title)) { - continue - } - val stringData: Pair = valuesData[title]!! - val element = stringData.first - if (!element.hasAttribute(ATTR_MAX_LENGTH)) { - val message = String.format(Locale.ENGLISH, "Preference title '%s' is missing \"maxLength=%d\" attribute", title, PREFERENCE_TITLE_MAX_LENGTH) - context.report(ISSUE_MAX_LENGTH, stringData.second.resolve(), message) - } else if (element.getAttribute(ATTR_MAX_LENGTH) != PREFERENCE_TITLE_MAX_LENGTH.toString()) { - val message = String.format(Locale.ENGLISH, "Preference title '%s' is having maxLength=%s it should contain maxLength=%d", title, element.getAttribute(ATTR_MAX_LENGTH), PREFERENCE_TITLE_MAX_LENGTH) - context.report(ISSUE_MAX_LENGTH, stringData.second.resolve(), message) + for (title in titlesOfPreferenceScreens) { + 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) } - if (element.textContent.length > PREFERENCE_TITLE_MAX_LENGTH) { - val message = String.format(Locale.ENGLISH, "Preference title '%s' must be less than %d characters (currently %d)", title, PREFERENCE_TITLE_MAX_LENGTH, element.textContent.length) - context.report(ISSUE_TITLE_LENGTH, stringData.second.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) } } } diff --git a/lint-rules/src/test/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLengthTest.kt b/lint-rules/src/test/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLengthTest.kt index a4d0cc2ab199..b5e3c9710462 100644 --- a/lint-rules/src/test/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLengthTest.kt +++ b/lint-rules/src/test/java/com/ichi2/anki/lint/rules/FixedPreferencesTitleLengthTest.kt @@ -24,55 +24,44 @@ class FixedPreferencesTitleLengthTest { companion object { @Language("XML") - val stringsXmlValid = """ + val strings1XmlValid = """ app_name pref_cat_general_summ - button_sync + button_sync sync_account +""" + + @Language("XML") + val strings10XmlValid = """ sync_account_summ sync_fetch_missing - Less than limit""" + Less than limit + number_title +""" @Language("XML") - val stringsXmlInvalid = """ - + val strings10XmlInvalid = """ + app_name pref_cat_general_summ - + button_sync sync_account - sync_account_summ - sync_fetch_missing - - This String contains character more than limit £ """ @Language("XML") - val invalidString = - """ - - - - - - - """ + val strings1XmlInvalid = """ + sync_account_summ + sync_fetch_missing + + This String contains character more than limit + + This String contains a £. +""" @Language("XML") - val validString = + val preferenceString = """ @@ -86,35 +75,54 @@ class FixedPreferencesTitleLengthTest { android:targetClass="com.ichi2.anki.MyAccount" android:targetPackage="com.ichi2.anki" /> + """ + android:title = "@string/checkbox_title" /> + +""" } + @Language("XML") + val preferenceWithHardcodedTitle = """ + + + """ + @Test fun showsErrorForInvalidFile() { TestLintTask.lint().allowMissingSdk() .allowCompilationErrors() .files( - TestFiles.xml("res/xml/preference_general_invalid.xml", invalidString), - TestFiles.xml("res/values/10-preferences.xml", stringsXmlInvalid) + TestFiles.xml("res/xml/preference_general_invalid.xml", preferenceString), + TestFiles.xml("res/values/10-preferences.xml", strings10XmlInvalid), + TestFiles.xml("res/values/01-core.xml", strings1XmlInvalid) ) .issues( - FixedPreferencesTitleLength.ISSUE_TITLE_LENGTH, - FixedPreferencesTitleLength.ISSUE_MAX_LENGTH + FixedPreferencesTitleLength.PREFERENCES_ISSUE_TITLE_LENGTH, + FixedPreferencesTitleLength.PREFERENCES_ISSUE_MAX_LENGTH ) .run() .expectErrorCount(3) .expect( - """res/values/10-preferences.xml:12: Error: Preference title 'more_characters_than_limit' must be less than 41 characters (currently 48) [FixedPreferencesTitleLength] - This String contains character more than limit £ - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -res/values/10-preferences.xml:3: Error: Preference title 'app_name' is missing "maxLength=41" attribute [PreferencesTitleMaxLengthAttr] + """res/values/01-core.xml:5: Error: Preference title 'checkbox_title' must be less than 41 characters (currently 46). [FixedPreferencesTitleLength] + This String contains character more than limit + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +res/values/10-preferences.xml:3: Error: Preference title 'app_name' is missing maxLength="41" attribute. [PreferencesTitleMaxLengthAttr] app_name ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -res/values/10-preferences.xml:6: Error: Preference title 'button_sync' is having maxLength=55 it should contain maxLength=41 [PreferencesTitleMaxLengthAttr] +res/values/10-preferences.xml:6: Error: Preference title 'button_sync' has maxLength="55". Its max length should be at most 41. [PreferencesTitleMaxLengthAttr] button_sync ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 errors, 0 warnings""" @@ -126,12 +134,29 @@ res/values/10-preferences.xml:6: Error: Preference title 'button_sync' is having TestLintTask.lint().allowMissingSdk() .allowCompilationErrors() .files( - TestFiles.xml("res/xml/preference_general_valid.xml", validString), - TestFiles.xml("res/values/10-preferences.xml", stringsXmlValid) + TestFiles.xml("res/xml/preference_general_valid.xml", preferenceString), + TestFiles.xml("res/values/10-preferences.xml", strings10XmlValid), + TestFiles.xml("res/values/01-core.xml", strings1XmlValid) + ) + .issues( + FixedPreferencesTitleLength.PREFERENCES_ISSUE_MAX_LENGTH, + FixedPreferencesTitleLength.PREFERENCES_ISSUE_TITLE_LENGTH + ) + .run() + .expectClean() + } + + @Test + fun hardcodedTitleIsNotFlagged() { + TestLintTask.lint().allowMissingSdk() + .allowCompilationErrors() + .files( + TestFiles.xml("res/xml/preference_general_valid.xml", preferenceWithHardcodedTitle), + TestFiles.xml("res/values/01-core.xml", strings1XmlValid) ) .issues( - FixedPreferencesTitleLength.ISSUE_MAX_LENGTH, - FixedPreferencesTitleLength.ISSUE_TITLE_LENGTH + FixedPreferencesTitleLength.PREFERENCES_ISSUE_MAX_LENGTH, + FixedPreferencesTitleLength.PREFERENCES_ISSUE_TITLE_LENGTH ) .run() .expectClean()