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