i18nの辞書ファイルをspreadsheetで管理する(spreadsheet⇄json相互変換)

4 min read読了の目安(約4300字

モチベーション

とあるプロジェクトで中国語対応することになり、社外の翻訳者とやり取りするようになりました。
それに伴い、辞書ファイル(json)をspreadsheetで管理することになったのですが、それならばspreadsheetとjsonファイルを相互変換できるようにしようということになりました。

前提

  • spreadsheetに書き込む元となるjsonファイルは、Google Driveにアップロードしてください。
  • GASにコピペして実行することを想定しています。
  • jsonファイルは下記のフォーマットのものを使用する想定です。
sample.json
{
  "aaa.bbb": "sample line",
  "aaa.ccc": "sample line 2",
  ...
}

実際のコード

i18-helper.gs
const FILES = [
  {
    lang: "en",
    inputId: "EN_INPUT_ID",
    outputFileName: "output_en.json",
  }, {
    lang: 'ja',
    inputId: "JA_INPUT_ID",
    outputFileName: "output_ja.json",
  },
]
const SPREADSHEET_URL = "SPREADSHEET_URL"
const OUTPUT_FOLDER_ID = "OUTPUT_FOLDER_ID"

/*
 * json to spreadsheet
 */
const jsonToSpreadsheet = () => {
  // get spreadsheet
  const ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL)
  const sheet = ss.getSheets()[0]

  // get all keys
  const allJsonObjs = []
  let allKeys = []
  for (const file of FILES) {
    const jsonObj = getJson(file.inputId)
    const keys = Object.keys(jsonObj)
    allKeys.push(...keys)
    allJsonObjs.push(jsonObj)
  }
  allKeys = allKeys
    .filter((value, index, self) => self.indexOf(value) === index)
    .sort((a, b) => a > b ? 1 : -1)

  // set header
  const keyValues = []
  const header = ["keys"]
  for (const file of FILES) {
    header.push(file.lang)
  }
  keyValues.push(header)

  // set key-value
  for (const key of allKeys) {
    const keyValue = [key]
    for (const targetObj of allJsonObjs) {
      keyValue.push(targetObj[key] || "")
    }
    keyValues.push(keyValue)
  }

  // write data to spreadsheet
  setDataToSheet(sheet, keyValues)
}

const setDataToSheet = (sheet, keyValues) => {
  const lastRow = sheet.getLastRow()
  const startRow = lastRow + 1
  const startCol = 1
  const numRows = keyValues.length
  const numCols = keyValues[0].length
  const range = sheet.getRange(startRow, startCol, numRows, numCols)
  range.setValues(keyValues)
}

const getJson = (fileId) => {
  const file = DriveApp.getFileById(fileId)
  const blob = file.getBlob()
  const data = blob.getDataAsString()
  const jsonObj = JSON.parse(data)
  return jsonObj
}

/*
 * spreadsheet to json
 */
const spreadsheetToJson = () => {
  // get spreadsheet
  const ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL)
  const sheet = ss.getSheets()[0]

  // get data from spreadsheet
  const lastRow = sheet.getLastRow()
  const lastColumn = sheet.getLastColumn()
  const rows = sheet.getRange(1, 1, lastRow, lastColumn).getValues()

  // set data as object
  const outputObjs = []
  for (const file of FILES) {
    const data = {}
    const targetCol = rows[0].findIndex((curr) => curr === file.lang)
    if (targetCol < 0) {
      continue
    }
    for (const row of rows.slice(1)) {
      data[row[0]] = row[targetCol]
    }
    outputObjs.push({ output: file.outputFileName, data })
  }

  // write data to json
  const folder = DriveApp.getFolderById(OUTPUT_FOLDER_ID)
  for (const obj of outputObjs) {
    const file = getFileFromFolder(folder, obj.output)
    if (file !== null) {
      file.setContent(JSON.stringify(obj.data, null, "  "))
    } else {
      folder.createFile(obj.output, JSON.stringify(obj.data, null, "  "), MimeType.PLAIN_TEXT)
    }
  }
}

const getFileFromFolder = (folder, fileName) => {
  const files = folder.getFilesByName(fileName)
  while (files.hasNext()) {
    const file = files.next()
    if (fileName === file.getName()) {
      return file
    }
  }
  return null
}

設定について

  • FILES: 各辞書ファイルの情報。
    • lang: 言語名。
    • inputId: spreadsheetに書き込む元となるjsonファイルのID。Google Driveにアップロードした際のhttps://drive.google.com/file/d/${id}/view${id}の部分にあたります。
    • outputFileName: spreadsheetの内容を書き出すjsonファイル名。同名ファイルがある場合は上書きされます。
  • SPREADSHEET_URL: spreadsheetのURL。https://docs.google.com/spreadsheets/d/${id}/editを入力。
  • OUTPUT_FOLDER_ID: outputFileNameを書き出すフォルダのID。https://drive.google.com/drive/folders/${id}${id}の部分にあたります。

あとがき

エンジニアがjsonファイルを更新しても、翻訳者がspreadsheetを更新しても、シームレスにファイルのやり取りができるようになりました。
エンジニアの立場としては、これまで複数のjsonファイルを見比べていたのが、単一のspreadsheetで見比べられるようになったのがありがたい点ですね。

参考にしたサイト