Python script to create Android/iOS strings from a .csv

My script takes a CSV file of strings and then creates an XML file for Android and a .strings file to be used for iOS. It separates each locale meaning that a file will be created for each language.

This has come in handy so far, making localising mobile apps easier. However, I am always seeking feedback so let me know what you think.

Input

The .csv is provided in the format: key, language1, language2

key,en,ru
welcome_message,hello,здравствуйте
thank_you_message,thank you,спасибо
goodbye_message,goodbye,До свидания


Output

XML - Android

en_strings.xml

<?xml version="1.0" ?>
<resources>
<string name="welcome_message">hello</string>
<string name="thank_you_message">thank you</string>
<string name="goodbye_message">goodbye</string>
</resources>


ru_strings.xml

<?xml version="1.0" ?>
<resources>
<string name="welcome_message">здравствуйте</string>
<string name="thank_you_message">спасибо</string>
<string name="goodbye_message">До свидания</string>
</resources>


.Strings - iOS

en.strings

/*  */
"welcome_message" = "hello";

/*  */
"thank_you_message" = "thank you";

/*  */
"goodbye_message" = "goodbye";


ru.strings

/*  */
"welcome_message" = "здравствуйте";

/*  */
"thank_you_message" = "спасибо";

/*  */
"goodbye_message" = "До свидания";


At the moment I've just hardcoded the origin Strings.csv file and the user defines the target directory, this will be useful when integrating it into automatic builds etc.

# -*- coding: utf-8 -*-
# Script takes a csv and creates strings for Android (.xml) and iOS (.Strings).
# csv in the format [key, language1, langauge2 ......]
# usage - Python converter.py [FILEPATH]

import sys, os, getopt, csv, xml.etree.ElementTree as ET
from xml.dom import minidom

def prettify(elem):
rough_string = ET.tostring(elem, 'utf-8')
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent="\t")

try:
sys.argv[1:]
fileDestination = sys.argv[1]
except IndexError:
print "Error: Please supply an output directory."
print "Usage: converter.py [FILEPATH]"
sys.exit()

# Create directory if it doesn't exists
if not os.path.exists(fileDestination):
os.makedirs(fileDestination)

f = open('Strings.csv')

# Determine the number of languages from the csv
line1 = csv_f.next()
numberOfLocales =  len(line1)

# Create strings for each language
for x in range(1, numberOfLocales):
#Returns to the start of the csv and ignores the first line
f.seek(0)
csv_f.next()
rowIndex = 0

# Android xml
resources = ET.Element("resources")

# Create iOS strings file
iOSFile = open(fileDestination+"/"+line1[x]+".Strings", "w+")

for row in csv_f:
++rowIndex
try:
# Write string to xml
ET.SubElement(resources, "string", name=row[0]).text = row[x].decode('utf-8')
# Write string to iOS .Strings
iOSFile.write("/*  */\n")
iOSFile.write('"'+row[0]+'"'+ ' = ' + '"'+row[x]+'"' + ";\n")
iOSFile.write("\n")
except IndexError:
f.seek(0)
print "There is a problem with the csv file at row {}".format(rowIndex+1) + " with the language {}".format(line1[x])
r = list(csv_f)
print r[rowIndex]
sys.exit()
# Write to Android file
androidFile = open(fileDestination+"/"+line1[x]+"_strings.xml", "w+")
androidFile.write(prettify(resources).encode('utf-8'))

• Non working code is off-topic:++rowIndex, incremeneting like that is not Python – Caridorc Jul 10 '15 at 20:03
• + does nothing ++ does nothing twice -> (i.e. nothing) – Caridorc Jul 10 '15 at 20:06
• Actually I think that is just a bug, upvoting now. – Caridorc Jul 10 '15 at 20:11
• In my system(Windows7), I see that the ru resource files(ru.Strings and ru_strings.xml) are not saved correctly, and they are just empty files, and you should add a function call to close() to the file object to correctly save the resource files. BTW: the English files(en.Strings and en_strings.xml) are correctly saved. – ollydbg23 Apr 18 '16 at 5:10

The Python style for naming is snake_case for functions and variables, and PascalCase for classes. If you have constant variables (values don't change), they should be in UPPER_SNAKE_CASE.

You also shouldn't be importing multiple modules on one line, like you've done here:

import sys, os, getopt, csv, xml.etree.ElementTree as ET


These imports should be separated onto separate lines, as per PEP8, Python's official style guide.

# Script takes a csv and creates strings for Android (.xml) and iOS (.Strings).
# csv in the format [key, language1, langauge2 ......]
# usage - Python converter.py [FILEPATH]


Should be converted into a docstring, like this:

"""
Script takes a csv and creates strings for Android (.xml) and iOS (.Strings).
csv in the format [key, language1, langauge2 ......]
usage - Python converter.py [FILEPATH]
"""


This is not how you should be opening files.

f = open('Strings.csv')


Rather, you should use a with ... as ... context manager. By using a context manager, you can ensure that the file is properly closed. Here's how you'd do that:

with open("Strings.csv") as f:
...


Finally, the below line does nothing.

++rowIndex


It should be changed to row_index += 1, if I understand your intent correctly.

• Thanks Ethan, such a detailed answer! This is the first useful script I've written in Python, I appreciate the guidance! – stepwise_refinement Jul 10 '15 at 20:29
• @stepwise_refinement No problem. Always willing to help out! :) – Ethan Bierlein Jul 10 '15 at 20:30

If a value is there but nobody uses it, is it really there?

A line confused me very much:

sys.argv[1:]


It does nothing, it evals a thing and then the value is discarded, you should remove it.

ET actually follows the official preferred name in the docs, well chosen!

Is an error a success?

Yes, in a philosophical sense, you learn from your error, but in code it is not.

print "Error: Please supply an output directory."
print "Usage: converter.py [FILEPATH]"
sys.exit()


Please use sys.exit(1) to signal error.

Enumerate and for item in iterable

In python explicit indexing is frowned upon:

for x in range(1, numberOfLocales):


It is better to use enumerate:

for locale, index in enumerate(line1):
# Do stuff


Also here:

for row, row_index in enumerate(csv_f):
# ...


So you can avoid manual incrementing.

• Since the OP noted this might get used for automated builds, I'd make note of the fact that using sys.exit(1) will be vital for communicating a failure back to a build script or server. – jpmc26 Jul 11 '15 at 0:54
• Thanks for the response Caridorc! I will definitely implement your suggestions. – stepwise_refinement Jul 11 '15 at 10:00