Skip to content

Instantly share code, notes, and snippets.

@bagage
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bagage/fa738a988781acc579b2 to your computer and use it in GitHub Desktop.
Save bagage/fa738a988781acc579b2 to your computer and use it in GitHub Desktop.
Make Android GUI localizable (move hardcoded android:text to res/values/string.xml and rename in .java)
#!/usr/bin/env python3
try:
import sys
import readline
import re
import fileinput
from bs4 import BeautifulSoup
except Exception as e:
print("Please install BeautifulSoup: pip3 install beautifulsoup4")
sys.exit(1)
def to_camel_case(name):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
type_conversion = {
"button": "btn",
"checkbox": "box",
"org.runnerup.widget.titlespinner": "",
"textview": "txt",
}
def layout_to_values(filelayout, filestrings):
y = BeautifulSoup(open(filelayout).read())
z = BeautifulSoup("xml")
for line in fileinput.input(filestrings, inplace=True):
if line != "</resources>\n":
sys.stdout.write(line)
for e in y.findAll(attrs={'android:id': not None}):
is_text = e.has_attr("android:text")
if is_text:
if e["android:text"].startswith("@string/"):
is_text = False # no need to treat it
if e.name not in type_conversion:
print(filelayout + ": not handled type: {}".format(e.name))
previous_id = e["android:id"][e["android:id"].find('/') + 1:]
camel_case_id = to_camel_case(previous_id)
if previous_id != camel_case_id:
# new line separator
print()
print(filelayout + ": %sID: %25s -----------> %s" %
("TEXT" if is_text else "", previous_id, camel_case_id))
# s = input(
# "Want to use another ID? (empty to keep this ID{})".format(
# "." if is_text else ", * to remove it"))
# if not is_text and s == "*":
# print(filelayout + ": Remove ID: %25s" %
# (camel_case_id))
# camel_case_id = None
# elif s:
# print(filelayout + ": New ID: %25s -----------> %s" %
# (camel_case_id, s))
# camel_case_id = s
if is_text and e["android:text"]: # check not empty too
new_text_id = camel_case_id
if type_conversion[e.name]:
new_text_id += "_" + type_conversion[e.name]
print(filelayout + ": Text: %25s -----------> %s" %
(e["android:text"], "@string/" + new_text_id))
tag = z.new_tag("string")
tag.string = e["android:text"]
tag["name"] = new_text_id
with open(filestrings, "ab") as strings_file:
strings_file.write(bytes("\t" + str(tag) + "\n", 'UTF-8'))
for line in fileinput.input(filelayout, inplace=True):
# we want to delete it
if camel_case_id is None:
line = line.replace('android:id="@+id/' + previous_id + '"', '')
else:
line = line.replace(previous_id + '"', camel_case_id + '"')
if is_text and e["android:text"]:
line = line.replace(
"android:text=\"" + e["android:text"] + "\"",
"android:text=\"" + "@string/" + new_text_id + "\"")
# this is used to write within file
print(line, end="")
with open(filestrings, "ab") as strings_file:
strings_file.write(bytes("</resources>\n", 'UTF-8'))
def file_converter(filep):
for line in fileinput.input(filep, inplace=True):
pattern = "findViewById(R.id."
if line.find(pattern) != -1:
cast_type = line[line.find('(') + 1:line.find(')')]
name = line[line.find(pattern) + len(pattern) - 5:]
name = name[:name.find(')')]
new_name = "R.id." + to_camel_case(name[5:])
line = line.replace(name, new_name)
print(line, end="")
if len(sys.argv) < 2:
print(
"""Give me some args (.xml or .java)
Example: {} res/values/strings.xml res/layout*/*.xml $(find src -name '*.java')
""".format(sys.argv[0])
)
sys.exit(1)
xml_file = None
for arg in sys.argv[1:]:
if arg[-5:] == ".java":
file_converter(arg)
elif arg[-4:] == ".xml":
if not xml_file:
xml_file = arg
else:
layout_to_values(arg, xml_file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment