sheets-translate-to-json
Version:
A Node.js library that facilitates the retrieval and conversion of previously translated Google Sheets spreadsheets into structured JSON files, optimizing the management of localized resources.
388 lines (387 loc) • 13.2 kB
JavaScript
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/index.ts
import { GoogleSpreadsheet } from "google-spreadsheet";
import { JWT } from "google-auth-library";
import fs from "fs";
import path from "path";
var SheetManager = class {
constructor(privateKey, clientEmail, sheetId) {
const SCOPES = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive.file"
];
this.jwt = new JWT({
email: clientEmail,
key: privateKey,
scopes: SCOPES
});
this.doc = new GoogleSpreadsheet(sheetId, this.jwt);
}
init(userPath, sheetNames) {
return __async(this, null, function* () {
try {
yield this.jwt.authorize();
if (sheetNames && sheetNames.length > 0) {
const combinedData = {};
for (const sheetName of sheetNames) {
const data = yield this.readByName(sheetName);
if (data && Object.keys(data).length > 0) {
Object.keys(data).forEach((language) => {
if (!combinedData[language]) {
combinedData[language] = {};
}
this.mergeNestedObjects(combinedData[language], data[language]);
});
} else {
console.warn(`No data found in sheet: ${sheetName}`);
}
}
if (Object.keys(combinedData).length > 0) {
this.write(combinedData, userPath);
}
} else {
const allSheetsData = yield this.readAllSheets();
const combinedData = {};
Object.keys(allSheetsData).forEach((sheetName) => {
const sheetData = allSheetsData[sheetName];
Object.keys(sheetData).forEach((language) => {
if (!combinedData[language]) {
combinedData[language] = {};
}
this.mergeNestedObjects(combinedData[language], sheetData[language]);
});
});
if (Object.keys(combinedData).length > 0) {
this.write(combinedData, userPath);
} else {
console.error("No data found in the sheet");
}
}
} catch (err) {
console.error("Error during initialization:", err);
}
});
}
read(sheetPosition = 0) {
return __async(this, null, function* () {
if (sheetPosition < 0) {
sheetPosition = 0;
}
yield this.doc.loadInfo();
const sheet = this.doc.sheetsByIndex[sheetPosition];
return this.processSheet(sheet);
});
}
readByName(sheetName) {
return __async(this, null, function* () {
yield this.doc.loadInfo();
const sheet = this.doc.sheetsByTitle[sheetName];
if (!sheet) {
throw new Error(`Sheet with name "${sheetName}" not found`);
}
return this.processSheet(sheet);
});
}
readAllSheets() {
return __async(this, null, function* () {
yield this.doc.loadInfo();
const allData = {};
for (const sheet of this.doc.sheetsByIndex) {
try {
const data = yield this.processSheet(sheet);
if (data && Object.keys(data).length > 0) {
allData[sheet.title] = data;
}
} catch (error) {
console.error(`Error processing sheet "${sheet.title}":`, error);
}
}
return allData;
});
}
processSheet(sheet) {
return __async(this, null, function* () {
yield sheet.loadHeaderRow();
const colTitles = sheet.headerValues;
const rows = yield sheet.getRows({ limit: sheet.rowCount });
const result = {};
rows.forEach((row) => {
const keyValue = row.get(colTitles[0]);
if (!keyValue || keyValue.trim() === "") {
return;
}
colTitles.slice(1).forEach((title) => {
const key = keyValue.trim();
const value = row.get(title);
const cleanValue = value && value.trim() !== "" ? value.trim() : void 0;
if (!result[title]) {
result[title] = {};
}
if (key.includes(".")) {
this.setNestedValue(result[title], key, cleanValue);
} else {
result[title][key] = cleanValue;
}
});
});
return result;
});
}
setNestedValue(obj, keyPath, value) {
const keys = keyPath.split(".");
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!current[key] || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key];
}
const finalKey = keys[keys.length - 1];
current[finalKey] = value;
}
mergeNestedObjects(target, source) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
const sourceValue = source[key];
const targetValue = target[key];
if (typeof sourceValue === "object" && sourceValue !== null && typeof targetValue === "object" && targetValue !== null) {
this.mergeNestedObjects(targetValue, sourceValue);
} else {
target[key] = sourceValue;
}
}
}
}
write(data, directoryPath) {
if (!fs.existsSync(directoryPath)) {
fs.mkdirSync(directoryPath, { recursive: true });
}
Object.keys(data).forEach((key) => {
const fileName = `${key}.json`;
const filePath = path.join(directoryPath, fileName);
fs.writeFile(filePath, JSON.stringify(data[key], null, 2), (err) => {
if (err) {
console.error(`Error writing file ${filePath}:`, err);
return;
}
console.log(`File written: ${filePath}`);
});
});
}
// Méthode utilitaire pour lister toutes les feuilles disponibles
listSheets() {
return __async(this, null, function* () {
yield this.doc.loadInfo();
return this.doc.sheetsByIndex.map((sheet) => sheet.title);
});
}
readLocal(directoryPath) {
if (!fs.existsSync(directoryPath)) {
throw new Error(`Directory not found: ${directoryPath}`);
}
const result = {};
const files = fs.readdirSync(directoryPath).filter((f) => f.endsWith(".json"));
for (const file of files) {
const language = file.replace(".json", "");
const filePath = path.join(directoryPath, file);
const content = fs.readFileSync(filePath, "utf-8");
result[language] = JSON.parse(content);
}
return result;
}
push(directoryPath, sheetName) {
return __async(this, null, function* () {
const localData = this.readLocal(directoryPath);
if (Object.keys(localData).length === 0) {
console.warn("No JSON files found in directory");
return;
}
yield this.doc.loadInfo();
let sheet;
if (sheetName) {
sheet = this.doc.sheetsByTitle[sheetName];
if (!sheet) {
sheet = yield this.doc.addSheet({ title: sheetName });
}
} else {
sheet = this.doc.sheetsByIndex[0];
}
const languages = Object.keys(localData);
const flatData = this.flattenAllLanguages(localData);
yield sheet.clear();
yield sheet.setHeaderRow(["key", ...languages]);
const rows = flatData.map((entry) => {
const row = { key: entry.key };
for (const lang of languages) {
row[lang] = entry.values[lang] || "";
}
return row;
});
if (rows.length > 0) {
yield sheet.addRows(rows);
}
console.log(`Pushed ${rows.length} keys in ${languages.length} languages to sheet "${sheet.title}"`);
});
}
sync(_0) {
return __async(this, arguments, function* (directoryPath, options = {}) {
const { strategy = "merge", sheetName, createSheet = false } = options;
yield this.doc.loadInfo();
const result = {
added: { local: 0, remote: 0 },
updated: { local: 0, remote: 0 },
languages: []
};
let remoteData;
let sheet;
if (sheetName) {
sheet = this.doc.sheetsByTitle[sheetName];
if (!sheet) {
if (createSheet) {
sheet = yield this.doc.addSheet({ title: sheetName });
remoteData = {};
} else {
throw new Error(`Sheet "${sheetName}" not found. Use createSheet: true to create it.`);
}
} else {
remoteData = yield this.processSheet(sheet);
}
} else {
sheet = this.doc.sheetsByIndex[0];
remoteData = yield this.processSheet(sheet);
}
let localData = {};
if (fs.existsSync(directoryPath)) {
localData = this.readLocal(directoryPath);
}
const allLanguages = /* @__PURE__ */ new Set([
...Object.keys(remoteData),
...Object.keys(localData)
]);
result.languages = [...allLanguages];
const mergedData = {};
const remoteFlat = this.flattenSheetData(remoteData);
const localFlat = this.flattenSheetData(localData);
const allKeys = /* @__PURE__ */ new Set([...Object.keys(remoteFlat), ...Object.keys(localFlat)]);
for (const compositeKey of allKeys) {
const remoteValues = remoteFlat[compositeKey] || {};
const localValues = localFlat[compositeKey] || {};
const isNewLocal = !remoteFlat[compositeKey];
const isNewRemote = !localFlat[compositeKey];
if (isNewLocal) result.added.local++;
if (isNewRemote) result.added.remote++;
for (const lang of allLanguages) {
if (!mergedData[lang]) mergedData[lang] = {};
const remoteVal = remoteValues[lang];
const localVal = localValues[lang];
let finalVal;
if (strategy === "local") {
finalVal = localVal != null ? localVal : remoteVal;
} else if (strategy === "remote") {
finalVal = remoteVal != null ? remoteVal : localVal;
} else {
if (localVal && remoteVal && localVal !== remoteVal) {
finalVal = localVal;
if (!isNewLocal && !isNewRemote) result.updated.local++;
} else {
finalVal = localVal != null ? localVal : remoteVal;
}
}
if (finalVal !== void 0) {
const keyPath = compositeKey;
if (keyPath.includes(".")) {
this.setNestedValue(mergedData[lang], keyPath, finalVal);
} else {
mergedData[lang][keyPath] = finalVal;
}
}
}
}
this.write(mergedData, directoryPath);
const mergedLanguages = Object.keys(mergedData);
const flatMerged = this.flattenAllLanguages(mergedData);
yield sheet.clear();
yield sheet.setHeaderRow(["key", ...mergedLanguages]);
const rows = flatMerged.map((entry) => {
const row = { key: entry.key };
for (const lang of mergedLanguages) {
row[lang] = entry.values[lang] || "";
}
return row;
});
if (rows.length > 0) {
yield sheet.addRows(rows);
}
result.updated.remote = flatMerged.length;
console.log(`Sync complete: ${result.added.local} new local keys, ${result.added.remote} new remote keys`);
return result;
});
}
flattenNestedObject(obj, prefix = "") {
const result = {};
for (const key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
const value = obj[key];
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === "object" && value !== null) {
Object.assign(result, this.flattenNestedObject(value, fullKey));
} else if (typeof value === "string") {
result[fullKey] = value;
}
}
return result;
}
flattenSheetData(data) {
const result = {};
for (const lang of Object.keys(data)) {
const flat = this.flattenNestedObject(data[lang]);
for (const key of Object.keys(flat)) {
if (!result[key]) result[key] = {};
result[key][lang] = flat[key];
}
}
return result;
}
flattenAllLanguages(data) {
const languages = Object.keys(data);
const allKeys = /* @__PURE__ */ new Set();
const flatByLang = {};
for (const lang of languages) {
flatByLang[lang] = this.flattenNestedObject(data[lang]);
for (const key of Object.keys(flatByLang[lang])) {
allKeys.add(key);
}
}
const sortedKeys = [...allKeys].sort();
return sortedKeys.map((key) => ({
key,
values: Object.fromEntries(
languages.map((lang) => [lang, flatByLang[lang][key] || ""])
)
}));
}
};
export {
SheetManager
};
//# sourceMappingURL=index.mjs.map