4
\$\begingroup\$

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")

# Read in output directory
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)

# Read from csv
f = open('Strings.csv')
csv_f = csv.reader(f)

# 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'))
\$\endgroup\$
  • 1
    \$\begingroup\$ Non working code is off-topic:++rowIndex, incremeneting like that is not Python \$\endgroup\$ – Caridorc Jul 10 '15 at 20:03
  • 4
    \$\begingroup\$ + does nothing ++ does nothing twice -> (i.e. nothing) \$\endgroup\$ – Caridorc Jul 10 '15 at 20:06
  • \$\begingroup\$ Actually I think that is just a bug, upvoting now. \$\endgroup\$ – Caridorc Jul 10 '15 at 20:11
  • \$\begingroup\$ 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. \$\endgroup\$ – ollydbg23 Apr 18 '16 at 5:10
6
\$\begingroup\$

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.

These comments at the very top of your file:

# 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.

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

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.

\$\endgroup\$
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ – jpmc26 Jul 11 '15 at 0:54
  • 1
    \$\begingroup\$ Thanks for the response Caridorc! I will definitely implement your suggestions. \$\endgroup\$ – stepwise_refinement Jul 11 '15 at 10:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.