Tuesday, June 7, 2011

Applescript To Export And Import iOS Projects To CSV

Two weeks ago I wrote an Applescript to auto-translate an iOS project by extracting all phrases, translating them via Google Translate, and then re-importing the translations back into your project. Unfortunately since publishing the script, Google has announced they are deprecating the Translate API.

So, as an alternative to using Google Translate, I've updated the Applescript to also export to a CSV file so the phrases can be loaded into a Google Spreadsheet, and then re-imported from CSV file once the spreadsheet has been translated.
set EXPORT_TO_CSV to "Export To CSV"
set IMPORT_FROM_CSV to "Import From CSV"
set GOOGLE_TRANSLATE to "Google Translate"
set CLASSES_ONLY to "Classes Only"
set CLASSES_AND_NIBS to "Classes And NIBs"

set question to display dialog "Select Your Activity" buttons {EXPORT_TO_CSV, IMPORT_FROM_CSV, GOOGLE_TRANSLATE} default button 1
set answer to button returned of question
if answer is equal to EXPORT_TO_CSV then
exportToCSV(choose folder with prompt "Select Project Source Folder", ¬
choose folder with prompt "Select Translations Destination Folder", ¬
display dialog "Enter CSV Destination File" default answer "localizations.csv", ¬
display dialog "Enter CSV Language Codes" default answer "fr,es,de,nl,it,pt,pl,ru,sw,zh,ja,ko,ar,cs,el,hi", ¬
display dialog "Export Phrases From" buttons {CLASSES_ONLY, CLASSES_AND_NIBS} default button 1)
else if answer is equal to IMPORT_FROM_CSV then
importFromCSV(choose folder with prompt "Select Project Source Folder", ¬
choose folder with prompt "Select Translations Destination Folder", ¬
choose file with prompt "Enter CSV Source File" of type {"CSV"})
else if answer is equal to GOOGLE_TRANSLATE then
translateProject(choose folder with prompt "Select Project Source Folder", ¬
choose folder with prompt "Select Localization Destination Folder", ¬
display dialog "Enter Destination Language Code" default answer "", ¬
display dialog "Translate Phrases From" buttons {CLASSES_ONLY, CLASSES_AND_NIBS} default button 1)
end if

on importFromCSV(projectFolder, localizationFolder, importFile)
set englishFolder to createFolder(localizationFolder, "en.lproj")
set importLines to paragraphs of readFilePath(importFile as text)

set firstLine to replaceCharacters(item 1 of importLines, "file,key,en,", "")
set firstLine to replaceCharacters(firstLine, "\"file\",\"key\",\"en\",", "")
set firstLine to replaceCharacters(firstLine, "\"", "")

set languageCodes to itemsFromCSV(firstLine)
repeat with languageCode in languageCodes
repeat with englishFile in filesWithExtension(englishFolder, ".strings")
set languageFileName to getFileName(englishFile)
set languageFolder to createFolder(localizationFolder, (languageCode & ".lproj"))
set languageFile to writeFile(languageFolder, languageFileName, "", false)
end repeat
end repeat

set importLines to items 2 thru -1 of importLines
repeat with importLine in importLines
set importLine to replaceCharacters(importLine, "\"\"\"", "\"")
set lineItems to itemsFromCSV(importLine)
set lineItemsCount to count of lineItems
if lineItemsCount is greater than 3 then
set fileName to item 1 of lineItems
set englishKey to item 2 of lineItems
set englishText to item 3 of lineItems
set translatedItems to items 4 through lineItemsCount of lineItems

set languageCodeIndex to 1
repeat with translatedItem in translatedItems
if length of translatedItem is greater than 0 then
set languageCode to item languageCodeIndex of languageCodes
set languageFolder to createFolder(localizationFolder, (languageCode & ".lproj"))
set translatedLine to "\"" & englishKey & "\" = \"" & translatedItem & "\";"
writeFile(languageFolder, fileName, translatedLine & return, true)
end if
if (count of languageCodes) is equal to languageCodeIndex then
exit repeat
end if
set languageCodeIndex to languageCodeIndex + 1
end repeat
end if
end repeat
display dialog "Translations Imported From " & importFile
end importFromCSV

on exportToCSV(projectFolder, localizationFolder, outputFileDialog, languageCodesDialog, phrasesDialog)
set languageCodesString to languageCodesDialog's text returned
if length of languageCodesString is equal to 0 then
display dialog "Language Codes Required"
else
set phrasesAnswer to button returned of phrasesDialog
set languageCodes to itemsFromCSV(languageCodesString)
set outputFile to outputFileDialog's text returned

set headerLine to "file,key,en," & languageCodesString
writeFile(localizationFolder, outputFile, headerLine & return, false)

do shell script "find " & quoted form of (POSIX path of projectFolder) & " -name '*.m' " & ¬
" | xargs genstrings -o " & quoted form of (POSIX path of localizationFolder & "en.lproj")

set englishFolder to createFolder(localizationFolder, "en.lproj")

if phrasesAnswer is equal to "Classes And NIBs" then
set nibFiles to filesWithExtension(projectFolder, ".xib")
repeat with nibFile in nibFiles
set nibFile to parseNib(nibFile, englishFolder)
end repeat
end if

repeat with englishFile in filesWithExtension(englishFolder, ".strings")
set languageFileName to getFileName(englishFile)
set englishLines to paragraphs of readFile(englishFolder, languageFileName)
repeat with englishLine in englishLines
if length of englishLine is greater than 0 then
set englishKey to parseKey(englishLine)
if length of englishKey is greater than 0 then
set newLine to languageFileName
if "\"" is in englishKey then
set newLine to newLine & ",\"\"" & englishKey & "\"\""
else if "," is in englishKey then
set newLine to newLine & ",\"" & englishKey & "\""
else
set newLine to newLine & "," & englishKey
end if

set englishText to parseValue(englishLine)
if "\"" is in englishText then
set newLine to newLine & ",\"\"" & englishText & "\"\""
else if "," is in englishText then
set newLine to newLine & ",\"" & englishText & "\""
else
set newLine to newLine & "," & englishText
end if

repeat with languageCode in languageCodes
set languageFolder to createFolder(localizationFolder, (languageCode & ".lproj"))
set languageFilePath to (languageFolder as text) & languageFileName as text
if fileExists(languageFilePath) then
set fileLines to paragraphs of readFile(languageFolder, languageFileName)
set hasTranslations to false
repeat with fileLine in fileLines
if "\"" & englishKey & "\" =" is in fileLine then
set languageText to parseValue(fileLine)
if "\"" is in languageText then
set newLine to newLine & ",\"\"" & languageText & "\"\""
else if "," is in languageText then
set newLine to newLine & ",\"" & languageText & "\""
else
set newLine to newLine & "," & languageText
end if
set hasTranslations to true
exit repeat
end if
end repeat
if hasTranslations is false then
set newLine to newLine & ","
end if
else
set newLine to newLine & ","
end if
end repeat
writeFile(localizationFolder, outputFile, newLine & return, true)
end if
end if
end repeat
end repeat
display dialog "Translations Exported To " & localizationFolder & outputFile
end if
end exportToCSV

on translateProject(projectFolder, localizationFolder, languageCodeDialog, phrasesDialog)
set languageCode to languageCodeDialog's text returned
if length of languageCode is equal to 0 then
display dialog "Language Code Required"
else
set phrasesAnswer to button returned of phrasesDialog
set englishFolder to createFolder(localizationFolder, "en.lproj")

do shell script "find " & quoted form of (POSIX path of projectFolder) & " -name '*.m' " & ¬
" | xargs genstrings -o " & quoted form of (POSIX path of localizationFolder & "en.lproj")

set nibFiles to filesWithExtension(projectFolder, ".xib")
if phrasesAnswer is equal to "Classes And NIBs" then
repeat with nibFile in nibFiles
set nibFile to parseNib(nibFile, englishFolder)
end repeat
end if

set languageFolder to createFolder(localizationFolder, (languageCode & ".lproj"))

repeat with englishFile in filesWithExtension(englishFolder, ".strings")
set languageFileName to getFileName(englishFile)

set languageFilePath to (languageFolder as text) & languageFileName as text
if fileExists(languageFilePath) is false then
set languageFile to writeFile(languageFolder, languageFileName, "", false)
end if

set fileLines to paragraphs of readFile(englishFolder, languageFileName)
repeat with fileLine in fileLines
if length of fileLine is greater than 0 then
set englishKey to parseKey(fileLine)
set englishText to parseValue(fileLine)

if length of englishText is greater than 0 then
if fileContainsText(languageFolder, languageFileName, "\"" & englishKey & "\" = ") is false then
set encodedText to encodeText(englishText, true, true)

set responseText to GoogleTranslate(encodedText, languageCode)
set translatedText to parseJson(responseText, "translatedText")

repeat until length of translatedText is greater than 0
set errorMessage to parseJson(responseText, "responseDetails")
if length of errorMessage is greater than 0 then
display dialog "Oops!" & space & errorMessage & space & "(Wait a few minutes, then click OK to continue)"
else
display dialog "Oops!" & space & responseText & space & "(Wait a few minutes, then click OK to continue)"
end if

set responseText to GoogleTranslate(encodedText, languageCode)
set translatedText to parseJson(responseText, "translatedText")
end repeat

set translatedLine to "\"" & englishKey & "\" = \"" & (translatedText as Unicode text) & "\";" as Unicode text
writeFile(languageFolder, languageFileName, translatedLine & return, true)
end if
end if
end if
end repeat

end repeat

if phrasesAnswer is equal to "Classes And NIBs" then
repeat with nibFile in nibFiles
importNib(nibFile, languageFolder)
end repeat
end if

display dialog "Translation Complete"
end if
end translateProject

on itemsFromCSV(csvLine)
set csvItems to {}
if "\"" is in csvLine then
set csvItem to ""
set insideQuotes to false
set previousQuote to false
repeat with c in the characters of csvLine
if insideQuotes then
if c as text is equal to "\"" then
if previousQuote then
set csvItem to csvItem & "\""
set previousQuote to false
else
set previousQuote to true
end if
else if c as text is equal to "," then
if previousQuote then
copy csvItem to the end of csvItems
set csvItem to ""
set insideQuotes to false
set previousQuote to false
else
set csvItem to csvItem & c
set previousQuote to false
end if
else
set csvItem to csvItem & c
set previousQuote to false
end if
else
if c as text is equal to "\"" then
set insideQuotes to true
set previousQuote to true
else if c as text is equal to "," then
copy csvItem to the end of csvItems
set csvItem to ""
set previousQuote to false
else
set csvItem to csvItem & c
set previousQuote to false
end if
end if
end repeat
if length of csvItem is greater than 0 then
copy csvItem to the end of csvItems
set csvItem to ""
set previousQuote to false
end if
else
set AppleScript's text item delimiters to ","
set csvItems to text items of csvLine
set AppleScript's text item delimiters to ""
end if
return csvItems
end itemsFromCSV

on parseNib(filePath, targetFolder)
set fileName to getFileName(filePath)
set fileNameWithoutExtension to replaceCharacters(fileName, ".xib", "")
set stringsPath to POSIX path of targetFolder & fileNameWithoutExtension & ".strings"
set sourceNib to POSIX path of filePath
do shell script "ibtool --export-strings-file" & space & ¬
quoted form of stringsPath & space & ¬
quoted form of sourceNib
return stringsPath
end parseNib

on importNib(filePath, targetFolder)
if fileExists(filePath) then
set fileName to getFileName(filePath)
set fileNameWithoutExtension to replaceCharacters(fileName, ".xib", "")
set stringsPath to POSIX path of targetFolder & fileNameWithoutExtension & ".strings"
set targetNib to POSIX path of targetFolder & fileName
set sourceNib to POSIX path of filePath
do shell script "ibtool --strings-file" & space & ¬
quoted form of stringsPath & space & "--write" & space & ¬
quoted form of targetNib & space & ¬
quoted form of sourceNib
end if
end importNib

on getFileName(filePath)
set {name:fileName} to info for file filePath without size
return fileName
end getFileName

on filesWithExtension(fileFolder, fileExtension)
set fileList to {}
tell application "Finder"
set filePaths to every file of entire contents of folder fileFolder as list
repeat with filePath in filePaths
if (filePath as text) ends with fileExtension then
set end of fileList to filePath as text
end if
end repeat
end tell
return fileList
end filesWithExtension

on fileContainsText(fileFolder, fileName, textString)
set fileLines to paragraphs of readFile(fileFolder, fileName)
repeat with fileLine in fileLines
if textString is in fileLine then
return true
end if
end repeat
return false
end fileContainsText

on createFolder(parentFolder, folderName)
tell application "Finder"
if not (exists folder folderName of parentFolder) then
make new folder at parentFolder with properties {name:folderName}
end if
return folder folderName of parentFolder as text
end tell
end createFolder

on writeFile(fileFolder, fileName, textString, shouldAppend)
tell application "Finder"
set filePath to (fileFolder as text) & fileName as text
try
set fileAccess to open for access file filePath with write permission
if shouldAppend is false then
set eof of fileAccess to 0
write (ASCII character 254) & (ASCII character 255) to fileAccess starting at eof -- UTF-16 BOM
else
set fileDoesExist to exists file filePath
if fileDoesExist is false then
set eof of fileAccess to 0
write (ASCII character 254) & (ASCII character 255) to fileAccess starting at eof -- UTF-16 BOM
end if
end if
write textString to fileAccess starting at eof as Unicode text
close access fileAccess
on error
try
close access file filePath
end try
end try
return filePath as text
end tell
end writeFile

on readFile(fileFolder, fileName)
set filePath to (fileFolder as text) & fileName as text
return readFilePath(filePath)
end readFile

on readFilePath(filePath)
tell application "Finder"
try
set fileAccess to open for access file filePath
set textContents to read fileAccess as Unicode text
close access fileAccess
return textContents
on error
display dialog "Unable to read filepath " & filePath
try
close access file filePath
end try
end try
return ""
end tell
end readFilePath

on fileExists(filePath)
tell application "Finder"
try
if exists file filePath then
return true
else
return false
end if
on error
return false
end try
end tell
return false
end fileExists

on GoogleTranslate(textString, codeString)
return do shell script "/usr/bin/curl" & space & ¬
"'http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&langpair=en|" & codeString & "&q=" & quoted form of textString & "'"
end GoogleTranslate

on parseKey(textString)
if (textString does not start with "/*") then
set AppleScript's text item delimiters to "="
set textItems to text items of textString
set AppleScript's text item delimiters to ""

if (count of textItems) > 1 then
set textItem to item 1 of textItems

set AppleScript's text item delimiters to "\""
set textValue to text items of textItem
set AppleScript's text item delimiters to ""

if (count of textValue) > 1 then
return trimCharacters((item 2 of textValue), "\"") as Unicode text
end if
end if
end if
return ""
end parseKey

on parseValue(textString)
if (textString does not start with "/*") then
set AppleScript's text item delimiters to "="
set textItems to text items of textString
set AppleScript's text item delimiters to ""

if (count of textItems) > 1 then
set textItem to item 2 of textItems

set AppleScript's text item delimiters to "\""
set textValue to text items of textItem
set AppleScript's text item delimiters to ""

if (count of textValue) > 1 then
set valueString to trimCharacters((item 2 of textValue), ";")
set valueString to trimCharacters(valueString, "\"")
return valueString as Unicode text
end if
end if
end if
return ""
end parseValue

on parseJson(jsonString, jsonElement)
repeat with delimiter1 in {"\":\"", "\": \""}
set AppleScript's text item delimiters to "\"" & jsonElement & delimiter1
set jsonItems to text items of jsonString
set AppleScript's text item delimiters to ""

if (count of jsonItems) > 1 then
set jsonValue to item 2 of jsonItems

repeat with delimiter2 in {"\"}", "\","}
set AppleScript's text item delimiters to delimiter2
set jsonValues to text items of jsonValue
set AppleScript's text item delimiters to ""

if (count of jsonValues) > 1 then
return decodeHTML(decodeURL(item 1 of jsonValues as Unicode text))
end if
end repeat
end if
end repeat
return ""
end parseJson

on trimCharacters(textString, trimCharacter)
repeat until textString does not start with trimCharacter
set textString to text 2 thru -1 of textString
end repeat
repeat until textString does not end with trimCharacter
set textString to text 1 thru -2 of textString
end repeat
return textString
end trimCharacters

on replaceCharacters(textString, searchString, replacementString)
set AppleScript's text item delimiters to the searchString
set the itemList to every text item of textString
set AppleScript's text item delimiters to the replacementString
set textString to the itemList as string
set AppleScript's text item delimiters to ""
return textString
end replaceCharacters

on encodeText(textString, encode_URL_A, encode_URL_B)
set the standardCharacters to "abcdefghijklmnopqrstuvwxyz0123456789"
set the URL_A_chars to "$+!'/?;&@=#%><{}[]\"~`^\\|*" set the URL_B_chars to ".-_:" set the acceptableCharacters to the standardCharacters if encode_URL_A is false then set the acceptableCharacters to the acceptableCharacters & the URL_A_chars if encode_URL_B is false then set the acceptableCharacters to the acceptableCharacters & the URL_B_chars set the encodedText to "" repeat with charString in textString if charString is in the acceptableCharacters then set the encodedText to (the encodedText & charString) as string else set the encodedText to (the encodedText & encodeCharacter(charString)) as string end if end repeat return the encodedText end encodeText on encodeCharacter(charString) set the ASCII_num to (the ASCII number charString) set the hex_list to {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"} set x to item ((ASCII_num div 16) + 1) of the hex_list set y to item ((ASCII_num mod 16) + 1) of the hex_list return ("%" & x & y) as string end encodeCharacter on decodeCharacters(charString) copy charString to {indentifying_char, multiplier_char, remainder_char} set the hexList to "123456789ABCDEF" if the multiplier_char is in "ABCDEF" then set the multiplier_amt to the offset of the multiplier_char in the hexList else set the multiplier_amt to the multiplier_char as integer end if if the remainder_char is in "ABCDEF" then set the remainder_amt to the offset of the remainder_char in the hexList else set the remainder_amt to the remainder_char as integer end if set the ASCII_num to (multiplier_amt * 16) + remainder_amt return (ASCII character ASCII_num) end decodeCharacters on decodeURL(textString) set flag_A to false set flag_B to false set temp_char to "" set the characterList to {} repeat with charString in textString set charString to contents of charString if charString is "%" then set flag_A to true else if flag_A is true then set the temp_char to charString set flag_A to false set flag_B to true else if flag_B is true then set the end of characterList to my decodeCharacters(("%" & temp_char & charString) as string) set the temp_char to "" set flag_A to false set flag_B to false else set the end of characterList to charString end if end repeat return characterList as string end decodeURL on decodeHTML(textString) repeat with entities in {{"\\u0026", "&"}, ¬ {" ", " "}, {"!", "!"}, {""", "\""}, {"#", "#"}, {"$", "$"}, ¬ {"%", "%"}, {"&", "&"}, {"'", "'"}, {"(", "("}, {")", ")"}, ¬ {"*", "*"}, {"+", "+"}, {",", ","}, {"-", "-"}, {".", "."}, ¬ {"/", "/"}, {":", ":"}, {";", ";"}, {"<", "<"}, {"=", "="}, ¬ {">", ">"}, {"?", "?"}, {"@", "@"}, {"[", "["}, {"\", "\\"}, ¬
{"]", "]"}, {"^", "^"}, {"_", "_"}, {"`", "`"}, {"{", "{"}, ¬
{"|", "|"}, {"}", "}"}, {"~", "~"}, {" ", " "}, {"¡", "¡"}, ¬
{"¢", "¢"}, {"£", "£"}, {"¤", "¤"}, {"¥", "¥"}, {"¦", "¦"}, ¬
{"§", "§"}, {"¨", "¨"}, {"©", "©"}, {"ª", "ª"}, {"«", "«"}, ¬
{"®", "®"}, {"¯", "¯"}, {"°", "°"}, {"±", "±"}, {"²", "²"}, ¬
{"³", "³"}, {"´", "´"}, {"µ", "µ"}, {"¶", "¶"}, {"·", "·"}, ¬
{"¸", "¸"}, {"¹", "¹"}, {"º", "º"}, {"»", "»"}, {"¼", "¼"}, ¬
{"½", "½"}, {"¾", "¾"}, {"¿", "¿"}, {"À", "À"}, {"Á", "Á"}, ¬
{"Â", "Â"}, {"Ã", "Ã"}, {"Ä", "Ä"}, {"Å", "Å"}, {"Æ", "Æ"}, ¬
{"Ç", "Ç"}, {"È", "È"}, {"É", "É"}, {"Ê", "Ê"}, {"Ë", "Ë"}, ¬
{"Ì", "Ì"}, {"Í", "Í"}, {"Î", "Î"}, {"Ï", "Ï"}, {"Ð", "Ð"}, ¬
{"Ñ", "Ñ"}, {"Ò", "Ò"}, {"Ó", "Ó"}, {"Ô", "Ô"}, {"Õ", "Õ"}, ¬
{"Ö", "Ö"}, {"×", "×"}, {"Ø", "Ø"}, {"Ù", "Ù"}, {"Ú", "Ú"}, ¬
{"Û", "Û"}, {"Ü", "Ü"}, {"Ý", "Ý"}, {"Þ", "Þ"}, {"ß", "ß"}, ¬
{"à", "à"}, {"á", "á"}, {"â", "â"}, {"ã", "ã"}, {"ä", "ä"}, ¬
{"å", "å"}, {"æ", "æ"}, {"ç", "ç"}, {"è", "è"}, {"é", "é"}, ¬
{"ê", "ê"}, {"ë", "ë"}, {"ì", "ì"}, {"í", "í"}, {"î", "î"}, ¬
{"ï", "ï"}, {"ð", "ð"}, {"ñ", "ñ"}, {"ò", "ò"}, {"ó", "ó"}, ¬
{"ô", "ô"}, {"õ", "õ"}, {"ö", "ö"}, {"÷", "÷"}, {"ø", "ø"}, ¬
{"ù", "ù"}, {"ú", "ú"}, {"û", "û"}, {"ü", "ü"}, {"ý", "ý"}, ¬
{"þ", "þ"}, {"ÿ", "ÿ"}, {"Œ", "Œ"}, {"œ", "œ"}, {"Š", "Š"}, ¬
{"š", "š"}, {"Ÿ", "Ÿ"}, {"ƒ", "ƒ"}, {"–", "–"}, {"—", "—"}, ¬
{"‘", "‘"}, {"’", "’"}, {"‚", "‚"}, {"“", "“"}, {"”", "”"}, ¬
{"„", "„"}, {"†", "†"}, {"‡", "‡"}, {"•", "•"}, {"…", "…"}, ¬
{"‰", "‰"}, {"€", "€"}, {"™", "™"}}
set textString to replaceCharacters(textString, item 1 of entities, item 2 of entities)
end repeat
return textString as string
end decodeHTML

You can download the updated Applescript at TranslateiOS.scpt

0 comments: