aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java23
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java37
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java32
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java27
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable.xml5
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml5
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java11
10 files changed, 172 insertions, 10 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
index 9fa5018..1ab02c3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
@@ -28,6 +28,7 @@ import com.android.tools.lint.checks.PxUsageDetector;
import com.android.tools.lint.checks.ScrollViewChildDetector;
import com.android.tools.lint.checks.SecurityDetector;
import com.android.tools.lint.checks.TextFieldDetector;
+import com.android.tools.lint.checks.TranslationDetector;
import com.android.tools.lint.checks.TypoDetector;
import com.android.tools.lint.checks.TypographyDetector;
import com.android.tools.lint.checks.UseCompoundDrawableDetector;
@@ -153,6 +154,7 @@ abstract class LintFix implements ICompletionProposal {
sFixes.put(PxUsageDetector.PX_ISSUE.getId(), ConvertToDpFix.class);
sFixes.put(TextFieldDetector.ISSUE.getId(), SetAttributeFix.class);
sFixes.put(SecurityDetector.EXPORTED_SERVICE.getId(), SetAttributeFix.class);
+ sFixes.put(TranslationDetector.MISSING.getId(), SetAttributeFix.class);
sFixes.put(DetectMissingPrefix.MISSING_NAMESPACE.getId(), AddPrefixFix.class);
sFixes.put(ScrollViewChildDetector.ISSUE.getId(), SetScrollViewSizeFix.class);
sFixes.put(ObsoleteLayoutParamsDetector.ISSUE.getId(), ObsoleteLayoutParamsFix.class);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
index a07101f..5d38df2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -98,6 +98,7 @@ import java.util.List;
* in the Problems view; perhaps we should use a custom view for these. That would also
* make marker management more obvious.
*/
+@SuppressWarnings("restriction") // DOM model
public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
/** Constructs a new {@link LintFixGenerator} */
public LintFixGenerator() {
@@ -248,7 +249,6 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
*
* @param marker the marker pointing to the error to be suppressed
*/
- @SuppressWarnings("restriction") // XML model
public static void addSuppressAnnotation(IMarker marker) {
String id = EclipseLintClient.getId(marker);
if (id != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
index 896966e..a860c69 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
@@ -18,17 +18,18 @@ package com.android.ide.eclipse.adt.internal.lint;
import static com.android.ide.common.layout.LayoutConstants.ATTR_CONTENT_DESCRIPTION;
import static com.android.ide.common.layout.LayoutConstants.ATTR_INPUT_TYPE;
import static com.android.ide.common.layout.LayoutConstants.VALUE_FALSE;
+import static com.android.tools.lint.detector.api.LintConstants.ATTR_TRANSLATABLE;
import com.android.tools.lint.checks.AccessibilityDetector;
import com.android.tools.lint.checks.InefficientWeightDetector;
import com.android.tools.lint.checks.SecurityDetector;
import com.android.tools.lint.checks.TextFieldDetector;
+import com.android.tools.lint.checks.TranslationDetector;
import com.android.tools.lint.detector.api.LintConstants;
import org.eclipse.core.resources.IMarker;
/** Shared fix class for various builtin attributes */
-@SuppressWarnings("restriction") // DOM model
final class SetAttributeFix extends SetPropertyFix {
private SetAttributeFix(String id, IMarker marker) {
super(id, marker);
@@ -44,6 +45,8 @@ final class SetAttributeFix extends SetPropertyFix {
return LintConstants.ATTR_PERMISSION;
} else if (mId.equals(TextFieldDetector.ISSUE.getId())) {
return ATTR_INPUT_TYPE;
+ } else if (mId.equals(TranslationDetector.MISSING.getId())) {
+ return ATTR_TRANSLATABLE;
} else {
assert false : mId;
return "";
@@ -51,6 +54,15 @@ final class SetAttributeFix extends SetPropertyFix {
}
@Override
+ protected boolean isAndroidAttribute() {
+ if (mId.equals(TranslationDetector.MISSING.getId())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
public String getDisplayString() {
if (mId.equals(AccessibilityDetector.ISSUE.getId())) {
return "Add content description attribute";
@@ -60,6 +72,8 @@ final class SetAttributeFix extends SetPropertyFix {
return "Set input type";
} else if (mId.equals(SecurityDetector.EXPORTED_SERVICE.getId())) {
return "Add permission attribute";
+ } else if (mId.equals(TranslationDetector.MISSING.getId())) {
+ return "Mark this as a non-translatable resource";
} else {
assert false : mId;
return "";
@@ -67,15 +81,37 @@ final class SetAttributeFix extends SetPropertyFix {
}
@Override
+ public String getAdditionalProposalInfo() {
+ String help = super.getAdditionalProposalInfo();
+
+ if (mId.equals(TranslationDetector.MISSING.getId())) {
+ help = "<b>Adds translatable=\"false\" to this &lt;string&gt;.</b><br><br>" + help;
+ }
+
+ return help;
+ }
+
+ @Override
protected boolean invokeCodeCompletion() {
return mId.equals(SecurityDetector.EXPORTED_SERVICE.getId())
|| mId.equals(TextFieldDetector.ISSUE.getId());
}
@Override
+ public boolean selectValue() {
+ if (mId.equals(TranslationDetector.MISSING.getId())) {
+ return false;
+ } else {
+ return super.selectValue();
+ }
+ }
+
+ @Override
protected String getProposal() {
if (mId.equals(InefficientWeightDetector.BASELINE_WEIGHTS.getId())) {
return VALUE_FALSE;
+ } else if (mId.equals(TranslationDetector.MISSING.getId())) {
+ return VALUE_FALSE;
}
return super.getProposal();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java
index 2bfe5e8..8b32734 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java
@@ -48,6 +48,9 @@ abstract class SetPropertyFix extends DocumentFix {
/** Attribute to be added */
protected abstract String getAttribute();
+ /** Whether it's in the android: namespace */
+ protected abstract boolean isAndroidAttribute();
+
protected String getProposal() {
return invokeCodeCompletion() ? "" : "TODO"; //$NON-NLS-1$
}
@@ -70,7 +73,10 @@ abstract class SetPropertyFix extends DocumentFix {
Element element = (Element) node;
String proposal = getProposal();
String localAttribute = getAttribute();
- String prefix = XmlUtils.lookupNamespacePrefix(node, ANDROID_URI);
+ String prefix = null;
+ if (isAndroidAttribute()) {
+ prefix = XmlUtils.lookupNamespacePrefix(node, ANDROID_URI);
+ }
String attribute = prefix != null ? prefix + ':' + localAttribute : localAttribute;
// This does not work even though it should: it does not include the prefix
@@ -78,18 +84,29 @@ abstract class SetPropertyFix extends DocumentFix {
// So workaround instead:
element.setAttribute(attribute, proposal);
- Attr attr = element.getAttributeNodeNS(ANDROID_URI, localAttribute);
+ Attr attr = null;
+ if (isAndroidAttribute()) {
+ attr = element.getAttributeNodeNS(ANDROID_URI, localAttribute);
+ } else {
+ attr = element.getAttributeNode(localAttribute);
+ }
if (attr instanceof IndexedRegion) {
IndexedRegion region = (IndexedRegion) attr;
int offset = region.getStartOffset();
// We only want to select the value part inside the quotes,
// so skip the attribute and =" parts added by WST:
offset += attribute.length() + 2;
- mSelect = new Region(offset, proposal.length());
+ if (selectValue()) {
+ mSelect = new Region(offset, proposal.length());
+ }
}
}
}
+ protected boolean selectValue() {
+ return true;
+ }
+
@Override
public void apply(IDocument document) {
try {
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
index 85995b1..1d9ba2e 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
@@ -670,4 +670,41 @@ public class LintUtils {
return hasManifest;
}
+
+ /**
+ * Look up the locale and region from the given parent folder name and
+ * return it as a combined string, such as "en", "en-rUS", etc, or null if
+ * no language is specified.
+ *
+ * @param folderName the folder name
+ * @return the locale+region string or null
+ */
+ @Nullable
+ public static String getLocaleAndRegion(@NonNull String folderName) {
+ if (folderName.equals("values")) { //$NON-NLS-1$
+ return null;
+ }
+
+ String locale = null;
+
+ for (String qualifier : Splitter.on('-').split(folderName)) {
+ int qualifierLength = qualifier.length();
+ if (qualifierLength == 2) {
+ char first = qualifier.charAt(0);
+ char second = qualifier.charAt(1);
+ if (first >= 'a' && first <= 'z' && second >= 'a' && second <= 'z') {
+ locale = qualifier;
+ }
+ } else if (qualifierLength == 3 && qualifier.charAt(0) == 'r' && locale != null) {
+ char first = qualifier.charAt(1);
+ char second = qualifier.charAt(2);
+ if (first >= 'A' && first <= 'Z' && second >= 'A' && second <= 'Z') {
+ return locale + '-' + qualifier;
+ }
+ break;
+ }
+ }
+
+ return locale;
+ }
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java
index f89fb81..391033e 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java
@@ -30,6 +30,7 @@ import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
@@ -73,6 +74,12 @@ public class TranslationDetector extends ResourceXmlDetector {
"If an application has more than one locale, then all the strings declared in " +
"one language should also be translated in all other languages.\n" +
"\n" +
+ "If the string should *not* be translated, you can add the attribute\n" +
+ "translatable=\"false\" on the <string> element, or you can define all " +
+ "your non-translatable strings in a resource file called \"donottranslate.xml\". " +
+ "Or, you can ignore the issue with a tools:ignore=\"MissingTranslation\" " +
+ "attribute.\n" +
+ "\n" +
"By default this detector allows regions of a language to just provide a " +
"subset of the strings and fall back to the standard language strings. " +
"You can require all regions to provide a full translation by setting the " +
@@ -90,15 +97,19 @@ public class TranslationDetector extends ResourceXmlDetector {
"If a string appears in a specific language translation file, but there is " +
"no corresponding string in the default locale, then this string is probably " +
"unused. (It's technically possible that your application is only intended to " +
- "run in a specific locale, but it's still a good idea to provide a fallback.)",
+ "run in a specific locale, but it's still a good idea to provide a fallback.).\n" +
+ "\n" +
+ "Note that these strings can lead to crashes if the string is looked up on any " +
+ "locale not providing a translation, so it's important to clean them up.",
Category.MESSAGES,
6,
- Severity.WARNING,
+ Severity.FATAL,
TranslationDetector.class,
Scope.ALL_RESOURCES_SCOPE);
private Set<String> mNames;
private Set<String> mTranslatedArrays;
+ private Set<String> mNonTranslatable;
private boolean mIgnoreFile;
private Map<File, Set<String>> mFileToNames;
@@ -457,6 +468,17 @@ public class TranslationDetector extends ResourceXmlDetector {
Attr translatable = element.getAttributeNode(ATTR_TRANSLATABLE);
if (translatable != null && !Boolean.valueOf(translatable.getValue())) {
+ String l = LintUtils.getLocaleAndRegion(context.file.getParentFile().getName());
+ if (l != null) {
+ context.report(EXTRA, context.getLocation(translatable),
+ "Non-translatable resources should only be defined in the base " +
+ "values/ folder", null);
+ } else {
+ if (mNonTranslatable == null) {
+ mNonTranslatable = new HashSet<String>();
+ }
+ mNonTranslatable.add(name);
+ }
return;
}
@@ -489,6 +511,12 @@ public class TranslationDetector extends ResourceXmlDetector {
mNames.add(name);
+ if (mNonTranslatable != null && mNonTranslatable.contains(name)) {
+ String message = String.format("The resource string \"%1$s\" has been marked as " +
+ "translatable=\"false\"", name);
+ context.report(EXTRA, context.getLocation(attribute), message, null);
+ }
+
// TBD: Also make sure that the strings are not empty or placeholders?
}
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java
index 6f1c2e6..39231dd 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java
@@ -34,9 +34,9 @@ public class TranslationDetectorTest extends AbstractCheckTest {
TranslationDetector.COMPLETE_REGIONS = false;
assertEquals(
// Sample files from the Home app
- "values-cs/arrays.xml:3: Warning: \"security_questions\" is translated here but not found in default locale\n" +
+ "values-cs/arrays.xml:3: Error: \"security_questions\" is translated here but not found in default locale\n" +
"=> values-es/strings.xml:12: Also translated here\n" +
- "values-de-rDE/strings.xml:11: Warning: \"continue_skip_label\" is translated here but not found in default locale\n" +
+ "values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale\n" +
"values/strings.xml:20: Error: \"show_all_apps\" is not translated in nl-rNL\n" +
"values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in nl-rNL\n" +
"values/strings.xml:25: Error: \"menu_settings\" is not translated in cs, de-rDE, es, es-rUS, nl-rNL",
@@ -57,7 +57,7 @@ public class TranslationDetectorTest extends AbstractCheckTest {
TranslationDetector.COMPLETE_REGIONS = true;
assertEquals(
// Sample files from the Home app
- "values-de-rDE/strings.xml:11: Warning: \"continue_skip_label\" is translated here but not found in default locale\n" +
+ "values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale\n" +
"values/strings.xml:19: Error: \"home_title\" is not translated in es-rUS\n" +
"values/strings.xml:20: Error: \"show_all_apps\" is not translated in es-rUS, nl-rNL\n" +
"values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in es-rUS, nl-rNL\n" +
@@ -137,4 +137,25 @@ public class TranslationDetectorTest extends AbstractCheckTest {
"res/values-cs/strings.xml=>../LibraryProject/res/values-nl/strings.xml"
));
}
+
+ public void testNonTranslatable1() throws Exception {
+ TranslationDetector.COMPLETE_REGIONS = true;
+ assertEquals(
+ // Sample files from the Home app
+ "values-nb/nontranslatable.xml:3: Error: The resource string \"dummy\" has been " +
+ "marked as translatable=\"false\"",
+
+ lintProject("res/values/nontranslatable.xml",
+ "res/values/nontranslatable2.xml=>res/values-nb/nontranslatable.xml"));
+ }
+
+ public void testNonTranslatable2() throws Exception {
+ TranslationDetector.COMPLETE_REGIONS = true;
+ assertEquals(
+ // Sample files from the Home app
+ "values-nb/nontranslatable.xml:3: Error: Non-translatable resources should only " +
+ "be defined in the base values/ folder",
+
+ lintProject("res/values/nontranslatable.xml=>res/values-nb/nontranslatable.xml"));
+ }
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable.xml
new file mode 100644
index 0000000..f608bff
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="dummy" translatable="false">Ignore Me</string>
+</resources>
+
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml
new file mode 100644
index 0000000..4fcfdc6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="dummy">Ignore Me</string>
+</resources>
+
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java
index b1ee6d6..8379618 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java
@@ -17,6 +17,7 @@
package com.android.tools.lint.detector.api;
import static com.android.tools.lint.detector.api.LintUtils.splitPath;
+import static com.android.tools.lint.detector.api.LintUtils.getLocaleAndRegion;
import com.android.tools.lint.Main;
import com.google.common.collect.Iterables;
@@ -274,4 +275,14 @@ public class LintUtilsTest extends TestCase {
checkEncoding("UTF_32", true /*bom*/, "\r\n");
checkEncoding("UTF_32LE", true /*bom*/, "\r\n");
}
+
+ public void testGetLocaleAndRegion() throws Exception {
+ assertNull(getLocaleAndRegion(""));
+ assertNull(getLocaleAndRegion("values"));
+ assertNull(getLocaleAndRegion("values-xlarge-port"));
+ assertEquals("en", getLocaleAndRegion("values-en"));
+ assertEquals("pt-rPT", getLocaleAndRegion("values-pt-rPT-nokeys"));
+ assertEquals("zh-rCN", getLocaleAndRegion("values-zh-rCN-keyshidden"));
+ assertEquals("ms", getLocaleAndRegion("values-ms-keyshidden"));
+ }
} \ No newline at end of file