소개
요즘 번역을 취합해서 JSON으로 만들어야 하는데 생각보다 시간이 걸립니다. 😅 스프레드시트로 히스토리를 관리할 수는 있었지만 트래킹이 쉽지 않고 , 전체 셀 업데이트가 이뤄지면 제가 업데이트해둔 내용이 예전 버전으 로 돌아가 버리는 일도 있고, 변경 고지를 못받고 나중에 수정하다 알게 되기도 했어요. 게다가 협업자마다 사용하는 툴도 달랐습니다. (저는 구글 시트, 상대방은 JSON 혹은 엑셀) 이 간극을 좁히지 않으면 계속 비효율이 쌓일 것 같았습니다.
처음엔 커서를 믿고 다 붙여넣어서 정리시키니 잘 되는 것 같았는데, 이상하게 수치를 바꿔서 수정하거나 갑자기 다국어가 섞인 JSON을 만들어줘서 밤을 꼴딱 새운 적도 있었어요. 이런 시행착오를 겪으면서 더더욱 자동화의 필요성을 절실히 느끼게 되었습니다.
그래서 “이 잡무를 자동화해야겠다!“는 결심을 하게 되었습니다. 💡
진행 방법
업무로 직접 진행하기는 어려우니 샘플로…
시트 스키마 제안
시트 이름: translations
헤더(1행): key | ko | en | note | updated_at
규칙: 키(key)는 중복 금지, 값은 빈 칸 허용. 수정 시 updated_at은 스크립트가 자동 갱신.
Apps Script 설정
시트 → 확장 프로그램 → 앱 스크립트를 열고 아래 코드를 붙여넣습니다.
프로젝트 설정 → 스크립트 속성에 다음 키를 추가합니다.
SLACK_WEBHOOK : 슬랙 인커밍 웹훅 URL
JSON_FOLDER_ID : JSON을 저장할 드라이브 폴더 ID(옵션)
메뉴 실행 → generateJson을 최초 1회 실행하여 권한을 부여합니다.
코드: JSON 생성 + Slack 알림 + 변경 감지(onEdit)
/** CONFIG **/
const PROP = PropertiesService.getScriptProperties();
const SLACK_WEBHOOK = PROP.getProperty('SLACK_WEBHOOK');
const JSON_FOLDER_ID = PROP.getProperty('JSON_FOLDER_ID');
/** 메인: 시트 → JSON 생성 및 드라이브 저장 */
function generateJson() {
const sheet = SpreadsheetApp.getActive().getSheetByName('translations');
const data = sheet.getDataRange().getValues();
const header = data.shift();
const idx = Object.fromEntries(header.map((h, i) => [String(h).trim(), i]));
const obj = {};
data.forEach(row => {
const key = row[idx.key];
if (!key) return;
obj[key] = {
ko: row[idx.ko] ?? '',
en: row[idx.en] ?? '',
note: row[idx.note] ?? ''
};
});
const jsonText = JSON.stringify(obj, null, 2);
const fileId = saveJson(jsonText);
postToSlack({
text: `✅ JSON 생성 완료 — ${Object.keys(obj).length}개 키`,
blocks: [
{ type: 'section', text: { type: 'mrkdwn', text: `*JSON 생성 완료* (키 ${Object.keys(obj).length}개)` } },
{ type: 'context', elements: [ { type: 'mrkdwn', text: `파일 ID: ${fileId}` } ] }
]
});
return jsonText;
}
/** 드라이브에 JSON 저장 */
function saveJson(jsonText) {
const tz = Session.getScriptTimeZone();
const name = `translations_${Utilities.formatDate(new Date(), tz, 'yyyyMMdd_HHmmss')}.json`;
let file;
if (JSON_FOLDER_ID) {
const folder = DriveApp.getFolderById(JSON_FOLDER_ID);
file = folder.createFile(name, jsonText, MimeType.JSON);
} else {
file = DriveApp.createFile(name, jsonText, MimeType.JSON);
}
return file.getId();
}
/** 셀 수정 시 변경 감지 및 슬랙 알림 */
function onEdit(e) {
try {
if (!e) return;
const sheet = e.source.getActiveSheet();
if (sheet.getName() !== 'translations') return; // 대상 시트만 감시
// 단일 셀 편집일 때만 oldValue 제공됨
const row = e.range.getRow();
const col = e.range.getColumn();
if (row === 1) return; // 헤더 제외
const key = sheet.getRange(row, 1).getValue();
const header = sheet.getRange(1, col).getValue();
const newVal = e.value;
const oldVal = e.oldValue; // 단일 셀 수정 시에만 존재
// updated_at 자동 기록
const updatedAtCol = sheet.getRange(1, 1).getDataRegion(SpreadsheetApp.Dimension.COLUMNS).getLastColumn();
const updatedAtHeader = sheet.getRange(1, updatedAtCol).getValue();
if (String(updatedAtHeader).toLowerCase() === 'updated_at') {
sheet.getRange(row, updatedAtCol).setValue(new Date());
}
postToSlack({
text: `✏️ 변경: ${key}`,
blocks: [
{ type: 'section', text: { type: 'mrkdwn', text: `*${sheet.getName()}* — R${row}C${col} • *${key}*` } },
{ type: 'section', fields: [
{ type: 'mrkdwn', text: `*컬럼*
${header}` },
{ type: 'mrkdwn', text: `*셀*
R${row}C${col}` }
] },
{ type: 'section', fields: [
{ type: 'mrkdwn', text: `*이전*
\`${oldVal ?? '(없음)'}\`` },
{ type: 'mrkdwn', text: `*변경*
\`${newVal ?? '(삭제)'}\`` }
] }
]
});
} catch (err) {
postToSlack({ text: `⚠️ onEdit 오류: ${err.message}` });
}
}
/** Slack 웹훅으로 메시지 전송 */
function postToSlack(payload) {
if (!SLACK_WEBHOOK) throw new Error('SLACK_WEBHOOK 스크립트 속성을 설정하세요.');
const res = UrlFetchApp.fetch(SLACK_WEBHOOK, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true,
});
return res.getResponseCode();
}트리거 구성
수정 알림: onEdit(e)는 간단 트리거로 자동 동작합니다. 단, 단일 셀 편집일 때만 e.oldValue가 제공됩니다.
정기 JSON 생성(옵션): generateJson에 시간 기반 트리거를 설치하면 매일/매시간 자동으로 JSON을 생성하고 히스토리를 남길 수 있습니다.
Slack 인커밍 웹훅 설정 요약
워크스페이스에서 Incoming Webhooks 앱 추가
채널을 선택해 Webhook URL 발급
발급받은 URL을 스크립트 속성 SLACK_WEBHOOK에 저장
메시지 포맷은 위 코드의 blocks처럼 마크다운과 블록 레이아웃을 활용하면 가독성이 좋아집니다.
이 정도까지 작업을 해보고 적용은 아직 전이니
이런저런 테스트를 차주에 진행해보려 합니다 ㅎ
지피티와 이야기하다보니 아래 고급화 아이디어까지?
(고급) 다중 셀 변경 추적용 버퍼/감사 로그 패턴
(고급) 매일 18:00 요약 알림 타임 트리거
(고급) JSON 스키마(i18n 네임스페이스) & 검증기
권장 워크플로우
ensureSetup() 한 번 실행해 버퍼/로그 시트를 준비
평소 편집은 translations에서 수행 → onEdit가 변경을 로깅하고 버퍼를 최신화
일일 요약은 시간 트리거로 sendDailySummary 실행
JSON 내보내기는 generateJsonValidated로 실행하여 스키마 검증 후 생성
이렇게 워크플로우까지 추천해줘서 앞으로 이 내용으로 적용해보고 검토해보고 또 개선하며 최적으로 자동화해보려고 합니다. 서버 업데이트까진 어려울 거 같은데 가능할지 고민해봐야겠어요.
화이팅!!
결과와 배운 점
깨달음
자동화는 늘 하고 싶지만 뭐 해야하지 하고 늘 고민했던 부분이어서 막막했었는데. 해야할 게 생각해보니 있어서 기뻤어요.
툴이 다양한데 뭘 쓸 수 있었나 싶었는데 이미 있는 툴을 그냥 쓰는 게 아니라, “내 업무에 맞게 최적화”하는 과정이라는 걸 알게 되었습니다. 🙌
앞으로의 계획:
Cursor와 Slack 같은 도구를 활용해서 수정 사항을 빠르게 알림받고, 소중한 시간을 허투루 쓰지 않고 정리까지 깔끔하게 하는 걸 목표로 하고 있습니다.