Developer's Blog

Localizable.strings を CSV に変換する Python スクリプト書いた

先日リリースした FlickAddress では日中英、3つの言語に対応しています。日英くらいだとチーム内で大体翻訳できるわけですが、中国語となるとチーム内で処理できず、翻訳者の方とやり取りが発生します。するとやり取りの為の中間ファイルを作ったり等、時間がかかって仕方ありません。また日中英だけでも十分大変なのに、さらに多くの言語に対応するとなると、個々の Localizabl.strings を管理していくのでは話になりません。そこで Localizable.strings と CSV を相互に変換するスクリプトを書いてみました。

手順は以下の通りです。

  1. まず各言語用の Localizable.strings を作成
  2. 次に1つの言語分だけ Localizable.strings を記述
  3. 以下のようにスクリプトを実行

ここでは Resources ディレクトリ以下に各 lproj が存在している事を想定しています。またスクリプトが少し長くなったのでエントリの末尾に張っておきます。

$ python ls2csv.py tocsv Resources

そうすると以下のような CSV ファイルが生成されます。一番左に Localizable.strings に定義されているキーの一覧が、続いて各言語のカラムが生成されています。CSV ファイルなら翻訳者の方とのやり取りも簡単ですね。

後は CSV ファイルに必要な翻訳データを入力して、Localizable.strings に再変換を行います。以下のコマンドを実行すると入力された CSV を元に言語毎の Localizable.strings が生成されますので、上書きするなりコピペするなりして使って下さい。

$ python ls2csv.py tols data.csv

これで iPhone アプリを多言語化するのが少しは楽になるのではと思います。「あぁ、めんどくさい」、そう思ったら少し時間を取って効率化。そんな心の余裕を日々持ちたいものですね。

スクリプト本体は例によって Python です。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import os
import re

def ls2csv(ls_dir):
  langs = []
  section_data = {'No Section':{}}
  all_data = []
  
  # get lang list
  res_dir = os.getcwd()
  if not os.path.exists(ls_dir):
    print "指定されたディレクトリ(%s)は存在しません。" % (ls_dir)
    sys.exit(0)
  res_dir = os.path.join(res_dir, ls_dir)
  for (root, dirs, files) in os.walk(res_dir):
    for filename in dirs:
      (basename, ext) = os.path.splitext(filename)
      if ext.lower() == '.lproj':
        langs.append(basename)
  
  # get key list
  key_val_regex = re.compile('"(?P.+)"s+=s+"(?P.+)";')
  section_regex = re.compile('//s+(?P.+)')
  first_lang = True
  current_section_name = None
  for lang in langs:
    lang_res_file = os.path.join(res_dir, '%s.lproj/Localizable.strings' % (lang,))
    content = open(lang_res_file, 'r').read()
    for line in content.split('n'):
      m = section_regex.match(line)
      if m:
        current_section_name = m.group('section_name')
        if first_lang:
          if current_section not in section_data.keys():
            section_data[current_section_name] = {}
            all_data.append({'section_name':current_section_name, 'section_data':section_data[current_section_name]})
      if not current_section_name or current_section_name not in section_data.keys():
        current_section_name = 'No Section'
      current_section = section_data[current_section_name]
      m = key_val_regex.match(line)
      if m:
        key = m.group('key')
        val = m.group('val')
        found = False
        if key not in current_section:
          for section_name in section_data.keys():
            section = section_data[section_name]
            if key in section:
              section[key][lang] = val
              found = True
              break
        if not found:
          if key not in current_section:
            current_section[key] = {}
          current_section[key][lang] = val
  
    first_lang = False
  all_data.append({'section_name':'No Section', 'section_data':section_data['No Section']})
  
  # output csv
  csv = 'key,'
  for i, lang in enumerate(langs):
    csv += lang
    if i < len(langs) - 1:
      csv += ','
  csv += 'n'
  for section in all_data:
    csv += '// %sn' % (section['section_name'])
    for key in section['section_data'].keys():
      csv += '%s,' % (key,)
      for i, lang in enumerate(langs):
        try:
          val = section['section_data'][key][lang]
        except:
          val = ''
        csv += val
        if i < len(langs) - 1:
          csv += ','
      csv += 'n'
    csv += 'n'
  print csv

def csv2ls(csv_file):
  csv_data = open(csv_file, 'r').read().split('n')

  head_line = csv_data[0]
  langs = [lang.replace('"', '') for lang in  head_line.split(',')[1:]]
  all_data = {}
  
  for lang in langs:
    all_data[lang] = []

  # get all sections
  current_section_name = None
  for line in csv_data[1:]:
    params = line.split(',')
    if len(params) != len(langs) + 1:
      current_section_name = line
      for lang in langs:
        all_data[lang].append({'section_name':current_section_name, 'section_data':{}})
    else:
      key = params[0]
      for i, lang in enumerate(langs):
        val = params[i + 1]
        section_data = all_data[lang][-1]['section_data']
        section_data[key] = val

  # output
  for lang_name in all_data:
    lang_name = lang_name.replace('"', '')
    lang_data = all_data[lang_name]
    result = ''
    for section in lang_data:
      result += '%sn' % (section['section_name'].replace('"', ''))
      for key, value in section['section_data'].items():
        key = key.replace('"', '')
        value = value.replace('"', '')
        result += '"%(key)s" = "%(val)s";n' % {'key':key, 'val':value}

    f = open('%s.strings' % (lang_name,), 'w')
    f.write(result)
    f.close()
    

def printUsageAndExit():
    print '''Usage:
    ls2csv.py tocsv [target_dir]
    ls2csv.py tols [csv_file]
'''
    sys.exit(0)

if __name__ == '__main__':
  if len(sys.argv) < 3:
    printUsageAndExit()
  command = sys.argv[1]
  if command == 'tocsv':
    ls2csv(sys.argv[2])
  elif command == 'tols':
    csv2ls(sys.argv[2])
  else:
    printUsageAndExit()

Copyright © 2019 Fenrir Inc. All rights reserved.