Skip to content

Commit

Permalink
feat: API 사용해서 호출할 수 있도록 수정 (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoo2001818 authored Oct 26, 2022
1 parent 13971b3 commit 1d94dcf
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/rude-frogs-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"i18next-google-sheet": minor
---

API를 활용한 실행 방법 추가
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,29 @@ i18next-google-sheet는 아래와 같은 파라미터를 받습니다.
- `credentials-json` - (택1) 구글 API 콘솔에서 받은 credentials JSON 본문

JSON의 경우에는 `I18NEXT_CREDENTIALS_JSON`와 같은 환경변수로도 전달할 수 있습니다.

### API 사용법

```js
import { i18nextGoogleSheet } from 'i18next-google-sheet';

const stats = await i18nextGoogleSheet({
path: 'locales/',
range: '시트1',
spreadsheetId: 'ABCDEFG1234567',
credentialsFile: './credentials.json',
});
```

stats 객체에는 아래와 같은 항목들이 포함되어 반환됩니다.

- `added`: 시트에 새로 추가된 항목
- `count`: 영향받은 항목 개수
- `namespaces`: 영향받은 네임스페이스 Set
- (이하 동일)
- `updated`: 번역 파일이 업데이트된 항목
- `reused`: 번역 파일에 다시 추가되어, 시트에서 "사용 여부"가 체크 해제되었다가 다시 체크된 항목
- `pruned`: 번역 파일에서 사라져서, 시트에서 "사용 여부"가 체크 해제된 항목

i18next-google-sheet는 ESM (`type: module`)을 사용하기 때문에, 사용하는 쪽에서도 이를
감안해서 사용해야 합니다.
66 changes: 65 additions & 1 deletion bin/index.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,66 @@
#!/usr/bin/env node
import '../lib/index.js';
import yargs from 'yargs';
import chalk from 'chalk';
import { i18nextGoogleSheet } from '../lib/index.js';

function getNamespacesToString(set) {
const array = Array.from(set);
if (array.length === 0) return '';
return ' (' + array.join(', ') + ')';
}

async function main() {
const argv = await yargs(process.argv.slice(2))
.option('path', {
alias: 'p',
describe: '로컬 locales 폴더 경로',
type: 'string',
})
.option('range', {
describe: '구글 시트에서 스캔할 범위',
default: '시트1',
type: 'string',
})
.option('spreadsheet-id', {
describe: '구글 시트 문서 ID',
type: 'string',
})
.option('credentials-file', {
describe: '구글 API 인증 파일',
type: 'string',
})
.option('credentials-json', {
describe: '구글 API 인증 JSON',
type: 'string',
})
.demandOption(['path', 'range', 'spreadsheet-id'])
.env('I18NEXT')
.help('h')
.alias('h', 'help')
.argv;

const stats = await i18nextGoogleSheet({
path: argv.path,
range: argv.range,
spreadsheet_id: argv.spreadsheetId,
credentials_file: argv.credentialsFile,
credentials_json: argv.credentialsJson,
});

if (['added', 'updated', 'reused', 'pruned'].every((v) => stats[v].count === 0)) {
console.log('No changes detected');
} else {
console.log('Sync complete!');
console.log(chalk.green('Added: ' + stats.added.count + getNamespacesToString(stats.added.namespaces)));
console.log(chalk.blue('Updated: ' + stats.updated.count + getNamespacesToString(stats.updated.namespaces)));
console.log(chalk.yellow('Reused: ' + stats.reused.count + getNamespacesToString(stats.reused.namespaces)));
console.log(chalk.red('Pruned: ' + stats.pruned.count + getNamespacesToString(stats.pruned.namespaces)));
}
}

main()
.catch((e) => {
console.error(chalk.red('Sync failed'));
console.error(e.stack);
process.exit(1);
});
58 changes: 20 additions & 38 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,43 @@
import { google } from 'googleapis';
import { oraPromise } from 'ora';
import yargs from 'yargs';
import { loadFileLocale, saveFileLocale } from './fileLocaleIO.js';
import { createGoogleAuth } from './googleAuth.js';
import { pruneSheetLocale } from './prune.js';
import { loadSheetLocale, saveSheetLocale } from './sheetLocaleIO.js';
import { createProcessStats, ProcessStats } from './types.js';
import { visitLocale } from './visitor.js';

async function main() {
const argv = await yargs(process.argv.slice(2))
.option('path', {
alias: 'p',
describe: '로컬 locales 폴더 경로',
type: 'string',
})
.option('range', {
describe: '구글 시트에서 스캔할 범위',
default: '시트1',
type: 'string',
})
.option('spreadsheet-id', {
describe: '구글 시트 문서 ID',
type: 'string',
})
.option('credentials-file', {
describe: '구글 API 인증 파일',
type: 'string',
})
.option('credentials-json', {
describe: '구글 API 인증 JSON',
type: 'string',
})
.demandOption(['path', 'range', 'spreadsheet-id'])
.env('I18NEXT')
.help('h')
.alias('h', 'help')
.argv;
const googleClient = await createGoogleAuth(argv.credentialsFile, argv.credentialsJson);
export interface I18nextGoogleSheetOptions {
path: string;
range: string;
spreadsheet_id: string;
credentials_file?: string;
credentials_json?: string;
}

export async function i18nextGoogleSheet(options: I18nextGoogleSheetOptions): Promise<ProcessStats> {
const stats = createProcessStats();
const googleClient = await createGoogleAuth(options.credentials_file, options.credentials_json);
const sheets = google.sheets({ version: 'v4', auth: googleClient });
const file_locale = await oraPromise(
loadFileLocale(argv.path),
loadFileLocale(options.path),
'Loading file locales',
);
const { columns, locale: sheet_locale } = await oraPromise(
loadSheetLocale(sheets, argv.spreadsheetId, argv.range),
loadSheetLocale(sheets, options.spreadsheet_id, options.range),
'Loading sheet locales',
);
visitLocale(file_locale, sheet_locale);
pruneSheetLocale(sheet_locale);
visitLocale(file_locale, sheet_locale, stats);
pruneSheetLocale(sheet_locale, stats);
await oraPromise(
saveFileLocale(argv.path, file_locale),
saveFileLocale(options.path, file_locale),
'Saving file locales',
);
await oraPromise(
saveSheetLocale(sheets, argv.spreadsheetId, argv.range, columns, sheet_locale),
saveSheetLocale(sheets, options.spreadsheet_id, options.range, columns, sheet_locale),
'Saving sheet locales',
);
return stats;
}

main();
export default i18nextGoogleSheet;
5 changes: 4 additions & 1 deletion src/prune.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import chalk from 'chalk';
import { SheetLocale } from './sheetLocale.js';
import { ProcessStats } from './types.js';
import { truncateKey } from './utils.js';

export function pruneSheetLocale(sheet_locale: SheetLocale) {
export function pruneSheetLocale(sheet_locale: SheetLocale, stats: ProcessStats) {
for (const entry of sheet_locale.entries) {
if (!entry.has_visited && entry.values.used !== 'FALSE') {
entry.values.used = 'FALSE';
entry.has_changed = true;
console.log(chalk.red('- pruning'), truncateKey(sheet_locale.getIndexKey(entry)));
stats.pruned.count += 1;
stats.pruned.namespaces.add(entry.namespace);
}
}
}
32 changes: 32 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export interface ProcessStatEntry {
count: number;
namespaces: Set<string>;
}

export interface ProcessStats {
added: ProcessStatEntry;
updated: ProcessStatEntry;
reused: ProcessStatEntry;
pruned: ProcessStatEntry;
}

export function createProcessStats(): ProcessStats {
return {
added: {
count: 0,
namespaces: new Set(),
},
updated: {
count: 0,
namespaces: new Set(),
},
reused: {
count: 0,
namespaces: new Set(),
},
pruned: {
count: 0,
namespaces: new Set(),
},
};
}
16 changes: 12 additions & 4 deletions src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import debug from 'debug';
import { FileLocale, FileLocaleLang, FileLocaleNamespace } from './fileLocale.js';
import { SUFFIX_MAP_REV } from './mapping.js';
import { SheetLocale } from './sheetLocale.js';
import { ProcessStats } from './types.js';
import { truncateKey } from './utils.js';

const debugLog = debug('i18next-google-sheet:visitor');
Expand All @@ -13,6 +14,7 @@ export function visitLocaleNamespace(
namespace_name: string,
namespace_data: FileLocaleNamespace,
sheet_locale: SheetLocale,
stats: ProcessStats,
) {
for (const entry_key in namespace_data) {
const [, key, suffix] = /^((?:.|\r|\n)+?)(?:_([a-z]+))?$/.exec(entry_key)!;
Expand All @@ -39,13 +41,17 @@ export function visitLocaleNamespace(
sheet_locale.insert(sheet_entry);
debugLog('Creating new entry', key);
console.log(chalk.green('+ creating'), truncateKey(sheet_locale.getIndexKey(sheet_entry)));
stats.added.count += 1;
stats.added.namespaces.add(sheet_entry.namespace);
}
const target_value = sheet_entry.values[lang_name];
if (target_value != null && target_value.trim() !== '') {
// 업데이트된 데이터 반영
if (namespace_data[entry_key] !== target_value) {
console.log(chalk.blue('~ updating'), truncateKey(sheet_locale.getIndexKey(sheet_entry)), lang_name);
namespace_data[entry_key] = target_value;
stats.updated.count += 1;
stats.updated.namespaces.add(sheet_entry.namespace);
}
}
if (target_value == null) {
Expand All @@ -58,20 +64,22 @@ export function visitLocaleNamespace(
sheet_entry.values.used = 'TRUE';
sheet_entry.has_changed = true;
console.log(chalk.yellow('~ marking as used'), truncateKey(sheet_locale.getIndexKey(sheet_entry)));
stats.reused.count += 1;
stats.reused.namespaces.add(sheet_entry.namespace);
}
sheet_entry.has_visited = true;
}
}

function visitLocaleLang(lang_name: string, lang_data: FileLocaleLang, sheet_locale: SheetLocale) {
function visitLocaleLang(lang_name: string, lang_data: FileLocaleLang, sheet_locale: SheetLocale, stats: ProcessStats) {
for (const namespace_name in lang_data) {
const namespace_data = lang_data[namespace_name];
visitLocaleNamespace(lang_name, namespace_name, namespace_data, sheet_locale);
visitLocaleNamespace(lang_name, namespace_name, namespace_data, sheet_locale, stats);
}
}

export function visitLocale(file_locale: FileLocale, sheet_locale: SheetLocale) {
export function visitLocale(file_locale: FileLocale, sheet_locale: SheetLocale, stats: ProcessStats) {
for (const lang_name in file_locale) {
visitLocaleLang(lang_name, file_locale[lang_name], sheet_locale);
visitLocaleLang(lang_name, file_locale[lang_name], sheet_locale, stats);
}
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"outDir": "lib",
"rootDir": "src",
"types": ["node"],
"allowJs": true
"allowJs": true,
"declaration": true
},
"include": ["src/**/*"]
}

0 comments on commit 1d94dcf

Please sign in to comment.