A fork of the Mastodon Android client with Bluesky/ATProto support.
at main 118 lines 4.3 kB view raw
1// run: java tools/VerifyTranslatedStringFormatting.java 2// Reads all localized strings and makes sure they contain valid formatting placeholders matching the original English strings to avoid crashes. 3 4import org.w3c.dom.*; 5import javax.xml.parsers.*; 6import java.io.*; 7import java.util.*; 8import java.util.regex.*; 9 10public class VerifyTranslatedStringFormatting{ 11 // %[argument_index$][flags][width][.precision][t]conversion 12 private static final String formatSpecifier="%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; 13 private static final Pattern fsPattern=Pattern.compile(formatSpecifier); 14 15 private static HashMap<String, List<String>> placeholdersInStrings=new HashMap<>(); 16 private static int errorCount=0; 17 18 public static void main(String[] args) throws Exception{ 19 DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); 20 factory.setNamespaceAware(false); 21 DocumentBuilder builder=factory.newDocumentBuilder(); 22 Document doc; 23 try(FileInputStream in=new FileInputStream("mastodon/src/main/res/values/strings.xml")){ 24 doc=builder.parse(in); 25 } 26 NodeList list=doc.getDocumentElement().getChildNodes(); // why does this stupid NodeList thing exist at all? 27 for(int i=0;i<list.getLength();i++){ 28 if(list.item(i) instanceof Element el){ 29 String name=el.getAttribute("name"); 30 String value; 31 if("string".equals(el.getTagName())){ 32 value=el.getTextContent(); 33 }else if("plurals".equals(el.getTagName())){ 34 value=el.getElementsByTagName("item").item(0).getTextContent(); 35 }else{ 36 System.out.println("Warning: unexpected tag "+name); 37 continue; 38 } 39 ArrayList<String> placeholders=new ArrayList<>(); 40 Matcher matcher=fsPattern.matcher(value); 41 while(matcher.find()){ 42 placeholders.add(matcher.group()); 43 } 44 placeholdersInStrings.put(name, placeholders); 45 } 46 } 47 for(File file:new File("mastodon/src/main/res").listFiles()){ 48 if(file.getName().startsWith("values-")){ 49 File stringsXml=new File(file, "strings.xml"); 50 if(stringsXml.exists()){ 51 processFile(stringsXml); 52 } 53 } 54 } 55 if(errorCount>0){ 56 System.err.println("Found "+errorCount+" problems in localized strings"); 57 System.exit(1); 58 } 59 } 60 61 private static void processFile(File file) throws Exception{ 62 DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); 63 factory.setNamespaceAware(false); 64 DocumentBuilder builder=factory.newDocumentBuilder(); 65 Document doc; 66 try(FileInputStream in=new FileInputStream(file)){ 67 doc=builder.parse(in); 68 } 69 NodeList list=doc.getDocumentElement().getChildNodes(); 70 for(int i=0;i<list.getLength();i++){ 71 if(list.item(i) instanceof Element el){ 72 String name=el.getAttribute("name"); 73 String value; 74 if("string".equals(el.getTagName())){ 75 value=el.getTextContent(); 76 if(!verifyString(value, placeholdersInStrings.get(name))){ 77 errorCount++; 78 System.out.println(file+": string "+name+" is missing placeholders"); 79 } 80 }else if("plurals".equals(el.getTagName())){ 81 NodeList items=el.getElementsByTagName("item"); 82 for(int j=0;j<items.getLength();j++){ 83 Element item=(Element)items.item(j); 84 value=item.getTextContent(); 85 String quantity=item.getAttribute("quantity"); 86 if(!verifyString(value, placeholdersInStrings.get(name))){ 87 // Some languages use zero/one/two for just these numbers so they may skip the placeholder 88 // still make sure that there's no '%' characters to avoid crashes 89 if(List.of("zero", "one", "two").contains(quantity) && !value.contains("%")){ 90 continue; 91 } 92 errorCount++; 93 System.out.println(file+": string "+name+"["+quantity+"] is missing placeholders"); 94 } 95 } 96 }else{ 97 System.out.println("Warning: unexpected tag "+name); 98 continue; 99 } 100 } 101 } 102 } 103 104 private static boolean verifyString(String str, List<String> placeholders){ 105 if(placeholders==null) 106 return true; 107 for(String placeholder:placeholders){ 108 if(placeholder.equals("%,d")){ 109 // %,d and %d are interchangeable but %,d provides nicer formatting 110 if(!str.contains(placeholder) && !str.contains("%d")) 111 return false; 112 }else if(!str.contains(placeholder)){ 113 return false; 114 } 115 } 116 return true; 117 } 118}