Because loc: strings can't be supplied for all Views, don't do

that. Instead use the English strings themselves as keys.  Generate a
java array of all R.string.whatever contants at build time, and use
that at runtime to build a hashmap of localizable strings that may be
found in UI widgets.  When one is found and there's a translation,
substitute.
This commit is contained in:
Eric House 2014-04-09 21:09:46 -07:00
parent b4016eaec3
commit 67bca0dd4e
48 changed files with 156 additions and 105 deletions

View file

@ -85,10 +85,12 @@
/>
<exec dir="." executable="../scripts/mk_xml.py"
failonerror="true" output="/dev/null"
failonerror="true"
>
<arg value="-o"/>
<arg value="src/org/eehouse/android/xw4/loc/LocIDsData.java"/>
<arg value="-t"/>
<arg value="${build.target}"/>
</exec>
<exec dir="." executable="../scripts/gen_gcmid.sh"

View file

@ -1489,7 +1489,7 @@
device, and the body that appears when you pull the notifications
down. -->
<string name="notify_title">Moves made</string>
<string name="notify_body_fmt">Activity in game "%1$s"</string>
<string name="notify_body_fmt">Activity in game \"%1$s\"</string>
<!--
############################################################

View file

@ -20,6 +20,8 @@
package org.eehouse.android.xw4.loc;
import android.content.Context;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@ -29,11 +31,12 @@ import org.eehouse.android.xw4.DbgUtils;
public class LocIDs extends LocIDsData {
private static String[] s_keys;
private static HashMap<String, Integer> S_MAP = null;
protected static String getNthKey( int indx )
protected static String getNthKey( Context context, int indx )
{
if ( null == s_keys ) {
Map<String, Integer> map = LocIDsData.S_MAP;
Map<String, Integer> map = getS_MAP( context );
s_keys = new String[map.size()];
Iterator<String> iter = map.keySet().iterator();
for ( int ii = 0; iter.hasNext(); ++ii ) {
@ -44,19 +47,33 @@ public class LocIDs extends LocIDsData {
return s_keys[indx];
}
protected static int getID( String key )
protected static int getID( Context context, String key )
{
int result = LocIDsData.NOT_FOUND;
if ( null != key && LocIDsData.S_MAP.containsKey( key ) ) {
Assert.assertNotNull( LocIDsData.S_MAP );
if ( null != key && getS_MAP(context).containsKey( key ) ) {
// Assert.assertNotNull( LocIDsData.S_MAP );
DbgUtils.logf( "calling get with key %s", key );
result = LocIDsData.S_MAP.get( key ); // NPE
result = getS_MAP( context ).get( key ); // NPE
}
return result;
}
protected static int size()
{
return LocIDsData.S_MAP.size();
return S_IDS.length;
}
protected static HashMap<String, Integer> getS_MAP( Context context )
{
if ( null == S_MAP ) {
S_MAP = new HashMap<String, Integer>(S_IDS.length);
for ( int id : S_IDS ) {
String str = context.getString( id );
S_MAP.put( str, id );
}
LocIDsData.checkStrings( context );
}
return S_MAP;
}
}

View file

@ -58,11 +58,8 @@ public class LocListItem extends LinearLayout implements OnFocusChangeListener {
private void setEnglish()
{
int id = LocIDs.getID( m_pair.getKey() );
String str = m_context.getString( id );
TextView tv = (TextView)findViewById( R.id.english_view );
tv.setText( str );
DbgUtils.logf( "setEnglish: set to %s", str );
tv.setText( m_pair.getKey() );
}
private void setXlated()

View file

@ -32,6 +32,7 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.CheckBox;
import android.widget.Spinner;
import java.util.Iterator;
import java.util.HashMap;
@ -49,7 +50,6 @@ import org.eehouse.android.xw4.XWPrefs;
public class LocUtils {
// Keep this in sync with gen_loc_ids.py and what's used in the menu.xml
// files to mark me-localized strings.
private static final String LOC_PREFIX = "loc:";
private static HashMap<String, String>s_xlations = null;
private static HashMap<Integer, String> s_idsToKeys = null;
private static Boolean s_enabled = null;
@ -58,21 +58,21 @@ public class LocUtils {
void setText( CharSequence text );
}
public static void loadStrings( Context context, AttributeSet as, LocIface view )
{
// There should be a way to look up the index of "strid" but I don't
// have it yet. This got things working.
int count = as.getAttributeCount();
for ( int ii = 0; ii < count; ++ii ) {
if ( "strid".equals( as.getAttributeName(ii) ) ) {
String value = as.getAttributeValue(ii);
Assert.assertTrue( '@' == value.charAt(0) );
int id = Integer.parseInt( value.substring(1) );
view.setText( getString( context, id ) );
break;
}
}
}
// public static void loadStrings( Context context, AttributeSet as, LocIface view )
// {
// // There should be a way to look up the index of "strid" but I don't
// // have it yet. This got things working.
// int count = as.getAttributeCount();
// for ( int ii = 0; ii < count; ++ii ) {
// if ( "strid".equals( as.getAttributeName(ii) ) ) {
// String value = as.getAttributeValue(ii);
// Assert.assertTrue( '@' == value.charAt(0) );
// int id = Integer.parseInt( value.substring(1) );
// view.setText( getString( context, id ) );
// break;
// }
// }
// }
public static View inflate( Context context, int resID )
{
@ -90,15 +90,7 @@ public class LocUtils {
public static void xlateView( Context context, View view )
{
DbgUtils.logf( "xlateView() top level" );
HashSet<String> seenClasses = new HashSet<String>();
HashSet<String> missedClasses = new HashSet<String>();
xlateView( context, view, seenClasses, missedClasses );
int ii = 0;
for ( Iterator<String> iter = seenClasses.iterator();
iter.hasNext(); ) {
DbgUtils.logf( "xlateView: seen class[%d]: %s", ii++, iter.next() );
}
DbgUtils.logf( "xlateView() top level DONE" );
xlateView( context, view, 0 );
}
public static void xlateMenu( Activity activity, Menu menu )
@ -108,13 +100,10 @@ public class LocUtils {
public static String xlateString( Context context, String str )
{
if ( str.startsWith( LOC_PREFIX ) ) {
str = str.substring( LOC_PREFIX.length() );
int id = LocIDs.getID( str );
if ( LocIDs.NOT_FOUND != id ) {
str = getString( context, id );
} else {
DbgUtils.logf( "nothing for %s", str );
if ( LocIDs.getS_MAP( context ).containsKey( str ) ) {
String xlation = getXlation( context, str );
if ( null != xlation ) {
str = xlation;
}
}
return str;
@ -137,7 +126,7 @@ public class LocUtils {
public static String getString( Context context, int id, Object... params )
{
String result = null;
String key = keyForID( id );
String key = keyForID( context, id );
if ( null != key ) {
result = getXlation( context, key );
}
@ -175,7 +164,7 @@ public class LocUtils {
{
loadXlations( context );
Map<String,Integer> map = LocIDsData.S_MAP;
Map<String,Integer> map = LocIDs.getS_MAP( context );
int siz = map.size();
LocSearcher.Pair[] result = new LocSearcher.Pair[siz];
Iterator<String> iter = map.keySet().iterator();
@ -198,15 +187,9 @@ public class LocUtils {
CharSequence ts = item.getTitle();
if ( null != ts ) {
String title = ts.toString();
if ( title.startsWith( LOC_PREFIX ) ) {
String asKey = title.substring( LOC_PREFIX.length() );
int id = LocIDs.getID( asKey );
if ( LocIDs.NOT_FOUND != id ) {
asKey = getString( activity, id );
} else {
DbgUtils.logf( "nothing for %s", asKey );
}
item.setTitle( asKey );
if ( LocIDs.getS_MAP( activity ).containsKey(title) ) {
title = xlateString( activity, title );
item.setTitle( title );
}
}
@ -237,10 +220,10 @@ public class LocUtils {
}
}
private static String keyForID( int id )
private static String keyForID( Context context, int id )
{
if ( null == s_idsToKeys ) {
Map<String,Integer> map = LocIDsData.S_MAP;
Map<String,Integer> map = LocIDs.getS_MAP( context );
HashMap<Integer, String> idsToKeys =
new HashMap<Integer, String>( map.size() );
@ -264,37 +247,34 @@ public class LocUtils {
return s_enabled;
}
private static void xlateView( Context context, View view,
HashSet<String> seen,
HashSet<String> missed )
private static void xlateView( Context context, View view, int depth )
{
String name = view.getClass().getName();
seen.add( name );
if ( view instanceof Button ) {
Button button = (Button)view;
String str = xlateString( context, button.getText().toString() );
button.setText( str );
} else if ( view instanceof TextView ) {
TextView tv = (TextView)view;
tv.setText( xlateString( context, tv.getText().toString() ) );
// } else if ( view instanceof CheckBox ) {
// CheckBox box = (CheckBox)view;
// String str = box.getText().toString();
// str = xlateString( context, str );
// box.setText( str );
} else if ( view instanceof Spinner ) {
Spinner sp = (Spinner)view;
String str = sp.getPrompt().toString();
sp.setPrompt( xlateString( context, str ) );
}
// A Spinner, for instance, ISA ViewGroup, so this is a separate test.
if ( view instanceof ViewGroup ) {
DbgUtils.logf( "xlateView recursing on %s", name );
ViewGroup asGroup = (ViewGroup)view;
int count = asGroup.getChildCount();
for ( int ii = 0; ii < count; ++ii ) {
View child = asGroup.getChildAt( ii );
xlateView( context, child, seen, missed );
}
} else if ( view instanceof Button ) {
Button button = (Button)view;
String str = button.getText().toString();
str = xlateString( context, str );
button.setText( str );
} else if ( view instanceof TextView ) {
TextView tv = (TextView)view;
String str = tv.getText().toString();
str = xlateString( context, str );
tv.setText( str );
} else if ( view instanceof CheckBox ) {
CheckBox box = (CheckBox)view;
String str = box.getText().toString();
str = xlateString( context, str );
box.setText( str );
} else {
missed.add( view.getClass().getName() );
xlateView( context, child, depth + 1 );
}
}
}
}

View file

@ -1,6 +1,6 @@
#!/usr/bin/python
import glob, sys, re, os, getopt
import glob, sys, re, os, getopt, codecs
from lxml import etree
@ -28,6 +28,7 @@ g_xmlTypes = [
g_pairs = {}
STR_REF = re.compile('@string/(.*)$')
CLASS_NAME = re.compile('.*/([^/*]+).java')
def xform(src, dest):
doc = etree.parse(src)
@ -47,37 +48,87 @@ def xform(src, dest):
if not os.path.exists(dir): os.makedirs(dir)
doc.write( dest, pretty_print=True )
def printStrings( pairs, outfile ):
fil = open( outfile, "w" )
# For my records: you CAN harvest a comment!!!
# def loadAndPrint(file):
# prevComment = None
# doc = etree.parse(file)
# for elem in doc.getroot().iter():
# if not isinstance( elem.tag, basestring ):
# prevComment = elem.text
# else:
# print "elem:", elem,
# if prevComment:
# print '//', prevComment
# prevComment = None
# else:
# print
# # doc.write( sys.stdout, pretty_print=True )
def checkText( text ):
text = " ".join(re.split('\s+', text)).replace('"', '\"')
seen = set()
split = re.split( '(%\d\$[sd])', text )
for part in split:
if 4 <= len(part) and '%' == part[0]:
digit = int(part[1:2])
if digit in seen:
print "ERROR: has duplicate format digit %d (text = %s)" % (digit, text)
print "This might not be what you want"
seen.add( digit )
return text
def printStrings( pairs, outfile, target ):
match = CLASS_NAME.match(outfile)
if not match:
print "did you give me a java file?:", outfile
sys.exit(0)
name = match.group(1)
fil = codecs.open( outfile, "w", "utf-8" )
# beginning of the class file
lines = """
/***********************************************************************
* Generated file; do not edit!!!
***********************************************************************/
package org.eehouse.android.xw4.loc;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import org.eehouse.android.xw4.R;
import org.eehouse.android.xw4.DbgUtils;
public class LocIDsData {
public class %s {
public static final int NOT_FOUND = -1;
protected static final int[] S_IDS = {
"""
fil.write( lines % name )
protected static final Map<String, Integer> S_MAP =
Collections.unmodifiableMap(new HashMap<String, Integer>() {{
for key in pairs.keys():
fil.write( " R.string.%s,\n" % (key) )
fil.write( " };\n\n" )
if "debug" == target:
fil.write( " static final String[] strs = {\n")
for key in pairs.keys():
fil.write( " \"%s\",\n" % pairs[key] )
fil.write( " };\n" );
lines = """
protected static void checkStrings( Context context )
{
for ( int ii = 0; ii < strs.length; ++ii ) {
if ( ! strs[ii].equals( context.getString( S_IDS[ii] ) ) ) {
DbgUtils.logf( "unequal strings: \\"%s\\" vs \\"%s\\"",
strs[ii], S_IDS[ii] );
}
}
}
"""
fil.write( lines )
for key in pairs.keys():
fil.write( " put(\"%s\", R.string.%s);\n" % (key, key) )
# Now the end of the class
lines = """
}});
}
/* end generated file */
"""
@ -85,23 +136,27 @@ public class LocIDsData {
def main():
outfile = ''
pairs, rest = getopt.getopt(sys.argv[1:], "o:")
outfileDbg = ''
target=''
pairs, rest = getopt.getopt(sys.argv[1:], "o:t:d:")
for option, value in pairs:
if option == '-o': outfile = value
elif option == '-t': target = value
# Gather all localizable strings
for path in glob.iglob( "res/values/strings.xml" ):
for action, elem in etree.iterparse(path):
if "end" == action and 'string' == elem.tag:
g_pairs[elem.get('name')] = True
if "end" == action and 'string' == elem.tag and elem.text:
text = checkText( elem.text )
g_pairs[elem.get('name')] = text
for subdir, dirs, files in os.walk('res_src'):
for file in files:
src = subdir + '/' + file
dest = src.replace( 'res_src', 'res', 1 )
xform( src, dest )
# for subdir, dirs, files in os.walk('res_src'):
# for file in files:
# src = subdir + '/' + file
# dest = src.replace( 'res_src', 'res', 1 )
# xform( src, dest )
if outfile: printStrings( g_pairs, outfile )
if outfile: printStrings( g_pairs, outfile, target )
main()