2022-02-16 10:05:35 +00:00
|
|
|
/**
|
2022-05-19 05:56:33 +00:00
|
|
|
* Language files are packaged into the binary, if
|
|
|
|
* newer files are available (if internet available)
|
|
|
|
* then those will replace the current file.
|
|
|
|
*
|
2022-02-16 10:05:35 +00:00
|
|
|
* Language files are fetched from the frappe/books repo
|
|
|
|
* the language files before storage have a ISO timestamp
|
|
|
|
* prepended to the file.
|
|
|
|
*
|
|
|
|
* This timestamp denotes the commit datetime, update of the file
|
|
|
|
* takes place only if a new update has been pushed.
|
|
|
|
*/
|
|
|
|
|
2022-05-19 05:56:33 +00:00
|
|
|
import { constants } from 'fs';
|
|
|
|
import fs from 'fs/promises';
|
|
|
|
import path from 'path';
|
2022-04-21 13:08:36 +00:00
|
|
|
import { splitCsvLine } from 'utils/translationHelpers';
|
|
|
|
import { LanguageMap } from 'utils/types';
|
2022-02-16 10:05:35 +00:00
|
|
|
|
2022-05-19 05:56:33 +00:00
|
|
|
const fetch = require('node-fetch').default;
|
|
|
|
|
2022-02-17 10:12:05 +00:00
|
|
|
const VALENTINES_DAY = 1644796800000;
|
|
|
|
|
2022-05-19 05:56:33 +00:00
|
|
|
export async function getLanguageMap(code: string): Promise<LanguageMap> {
|
|
|
|
const contents = await getContents(code);
|
2022-02-16 10:05:35 +00:00
|
|
|
return getMapFromContents(contents);
|
|
|
|
}
|
|
|
|
|
2022-04-08 06:59:42 +00:00
|
|
|
function getMapFromContents(contents: string): LanguageMap {
|
2022-04-01 23:09:37 +00:00
|
|
|
const lines: string[] = contents.split('\n').slice(1);
|
|
|
|
return lines
|
|
|
|
.map((l) => splitCsvLine(l) as string[])
|
2022-02-16 10:05:35 +00:00
|
|
|
.filter((l) => l.length >= 2)
|
|
|
|
.reduce((acc, l) => {
|
|
|
|
const key = l[0].slice(1, -1);
|
|
|
|
const translation = l[1].slice(1, -1);
|
|
|
|
acc[key] = { translation };
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
const context = l.slice(2)[0];
|
|
|
|
if (context?.length) {
|
|
|
|
acc[key].context = context;
|
2022-02-16 10:05:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
2022-04-08 06:59:42 +00:00
|
|
|
}, {} as LanguageMap);
|
2022-02-16 10:05:35 +00:00
|
|
|
}
|
|
|
|
|
2022-05-19 05:56:33 +00:00
|
|
|
async function getContents(code: string) {
|
|
|
|
let contents = await getContentsIfExists(code);
|
|
|
|
if (contents.length === 0) {
|
|
|
|
contents = (await fetchAndStoreFile(code)) || contents;
|
|
|
|
} else {
|
|
|
|
contents = (await getUpdatedContent(code, contents)) || contents;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!contents || contents.length === 0) {
|
|
|
|
throwCouldNotFetchFile(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
return contents;
|
|
|
|
}
|
2022-02-16 10:05:35 +00:00
|
|
|
|
2022-05-19 05:56:33 +00:00
|
|
|
async function getContentsIfExists(code: string): Promise<string> {
|
|
|
|
const filePath = await getTranslationFilePath(code);
|
|
|
|
if (!filePath) {
|
2022-02-16 10:05:35 +00:00
|
|
|
return '';
|
|
|
|
}
|
2022-05-19 05:56:33 +00:00
|
|
|
|
|
|
|
return await fs.readFile(filePath, { encoding: 'utf-8' });
|
2022-02-16 10:41:08 +00:00
|
|
|
}
|
2022-02-16 10:05:35 +00:00
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
async function fetchAndStoreFile(code: string, date?: Date) {
|
2022-02-17 10:12:05 +00:00
|
|
|
let res = await fetch(
|
|
|
|
`https://api.github.com/repos/frappe/books/contents/translations/${code}.csv`
|
|
|
|
);
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
let contents: string | undefined = undefined;
|
2022-02-17 10:12:05 +00:00
|
|
|
if (res.status === 200) {
|
|
|
|
const resJson = await res.json();
|
|
|
|
contents = Buffer.from(resJson.content, 'base64').toString();
|
|
|
|
} else {
|
|
|
|
res = await fetch(
|
|
|
|
`https://raw.githubusercontent.com/frappe/books/master/translations/${code}.csv`
|
|
|
|
);
|
2022-02-16 10:05:35 +00:00
|
|
|
}
|
|
|
|
|
2022-02-17 10:12:05 +00:00
|
|
|
if (!contents && res.status === 200) {
|
|
|
|
contents = await res.text();
|
2022-02-16 10:41:08 +00:00
|
|
|
}
|
|
|
|
|
2022-02-17 10:12:05 +00:00
|
|
|
if (!date && contents) {
|
|
|
|
date = await getLastUpdated(code);
|
|
|
|
}
|
2022-02-16 10:05:35 +00:00
|
|
|
|
2022-02-17 10:12:05 +00:00
|
|
|
if (contents) {
|
2022-04-01 23:09:37 +00:00
|
|
|
contents = [date!.toISOString(), contents].join('\n');
|
2022-02-17 10:12:05 +00:00
|
|
|
await storeFile(code, contents);
|
|
|
|
}
|
2022-02-16 10:05:35 +00:00
|
|
|
return contents;
|
|
|
|
}
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
async function getUpdatedContent(code: string, contents: string) {
|
|
|
|
const { shouldUpdate, date } = await shouldUpdateFile(code, contents);
|
2022-02-16 10:05:35 +00:00
|
|
|
if (!shouldUpdate) {
|
|
|
|
return contents;
|
|
|
|
}
|
|
|
|
|
|
|
|
return await fetchAndStoreFile(code, date);
|
|
|
|
}
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
async function shouldUpdateFile(code: string, contents: string) {
|
2022-02-16 10:05:35 +00:00
|
|
|
const date = await getLastUpdated(code);
|
|
|
|
const oldDate = new Date(contents.split('\n')[0]);
|
2022-02-17 10:12:05 +00:00
|
|
|
const shouldUpdate = date > oldDate || +oldDate === VALENTINES_DAY;
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
return { shouldUpdate, date };
|
2022-02-16 10:05:35 +00:00
|
|
|
}
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
async function getLastUpdated(code: string): Promise<Date> {
|
2022-02-16 10:05:35 +00:00
|
|
|
const url = `https://api.github.com/repos/frappe/books/commits?path=translations%2F${code}.csv&page=1&per_page=1`;
|
2022-04-01 23:09:37 +00:00
|
|
|
const resJson = await fetch(url).then((res: Response) => res.json());
|
2022-02-16 10:05:35 +00:00
|
|
|
|
2022-02-17 10:12:05 +00:00
|
|
|
try {
|
|
|
|
return new Date(resJson[0].commit.author.date);
|
|
|
|
} catch {
|
|
|
|
return new Date(VALENTINES_DAY);
|
2022-02-16 10:05:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-19 05:56:33 +00:00
|
|
|
async function getTranslationFilePath(code: string) {
|
|
|
|
let filePath = path.join(
|
|
|
|
process.resourcesPath,
|
|
|
|
`../translations/${code}.csv`
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
await fs.access(filePath, constants.R_OK);
|
|
|
|
} catch {
|
|
|
|
/**
|
|
|
|
* This will be used for in Development mode
|
|
|
|
*/
|
|
|
|
filePath = path.join(__dirname, `../translations/${code}.csv`);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
await fs.access(filePath, constants.R_OK);
|
|
|
|
} catch {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
return filePath;
|
2022-02-16 10:05:35 +00:00
|
|
|
}
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
function throwCouldNotFetchFile(code: string) {
|
2022-02-17 10:12:05 +00:00
|
|
|
throw new Error(`Could not fetch translations for '${code}'.`);
|
2022-02-16 10:05:35 +00:00
|
|
|
}
|
|
|
|
|
2022-04-01 23:09:37 +00:00
|
|
|
async function storeFile(code: string, contents: string) {
|
2022-05-19 05:56:33 +00:00
|
|
|
const filePath = await getTranslationFilePath(code);
|
|
|
|
if (!filePath) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-02-16 10:41:08 +00:00
|
|
|
const dirname = path.dirname(filePath);
|
|
|
|
await fs.mkdir(dirname, { recursive: true });
|
2022-02-16 10:05:35 +00:00
|
|
|
await fs.writeFile(filePath, contents, { encoding: 'utf-8' });
|
|
|
|
}
|