2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +00:00

Merge pull request #341 from 18alantom/use-translations

Setup Translations - Part 2 Use Translations
This commit is contained in:
Alan 2022-02-17 16:08:14 +05:30 committed by GitHub
commit 03c7b768e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 527 additions and 130 deletions

View File

@ -7,6 +7,8 @@ const {
DEFAULT_DISPLAY_PRECISION,
} = require('./utils/consts');
const { markRaw } = require('vue');
const { ipcRenderer } = require('electron');
const { IPC_ACTIONS } = require('@/messages');
module.exports = {
initializeAndRegister(customModels = {}, force = false) {

View File

@ -93,8 +93,8 @@ module.exports = {
},
],
quickEditFields: [
'dateFormat',
'locale',
'dateFormat',
'displayPrecision',
'hideGetStarted',
'autoReportErrors',

View File

@ -1,3 +1,4 @@
export const DEFAULT_INTERNAL_PRECISION = 11;
export const DEFAULT_DISPLAY_PRECISION = 2;
export const DEFAULT_LOCALE = 'en-IN';
export const DEFAULT_LANGUAGE = 'English';

View File

@ -1,3 +1,9 @@
import {
getIndexFormat,
getIndexList,
getSnippets,
getWhitespaceSanitized,
} from '../../scripts/helpers';
import { ValueError } from '../common/errors';
class TranslationString {
@ -14,19 +20,23 @@ class TranslationString {
return this;
}
#translate(segment) {
const startSpace = segment.match(/^\s+/)?.[0] ?? '';
const endSpace = segment.match(/\s+$/)?.[0] ?? '';
segment = segment.replace(/\s+/g, ' ').trim();
// TODO: implement translation backend
// segment = translate(segment)
return startSpace + segment + endSpace;
}
#formatArg(arg) {
return arg ?? '';
}
#translate() {
let indexFormat = getIndexFormat(this.args[0]);
indexFormat = getWhitespaceSanitized(indexFormat);
const translatedIndexFormat =
this.languageMap[indexFormat]?.translation ?? indexFormat;
this.argList = getIndexList(translatedIndexFormat).map(
(i) => this.argList[i]
);
this.strList = getSnippets(translatedIndexFormat);
}
#stitch() {
if (!(this.args[0] instanceof Array)) {
throw new ValueError(
@ -36,10 +46,15 @@ class TranslationString {
);
}
const strList = this.args[0];
const argList = this.args.slice(1);
return strList
.map((s, i) => this.#translate(s) + this.#formatArg(argList[i]))
this.strList = this.args[0];
this.argList = this.args.slice(1);
if (this.languageMap) {
this.#translate();
}
return this.strList
.map((s, i) => s + this.#formatArg(this.argList[i]))
.join('')
.replace(/\s+/g, ' ')
.trim();
@ -65,3 +80,7 @@ export function T(...args) {
export function t(...args) {
return new TranslationString(...args).s;
}
export function setLanguageMapOnTranslationString(languageMap) {
TranslationString.prototype.languageMap = languageMap;
}

View File

@ -26,6 +26,7 @@
"knex": "^0.95.12",
"lodash": "^4.17.21",
"luxon": "^2.0.2",
"node-fetch": "2",
"pesa": "^1.1.3",
"sqlite3": "npm:@vscode/sqlite3@^5.0.7",
"vue": "^3.2.30",

View File

@ -111,10 +111,11 @@ function printHelp() {
`\tbe removed.\n` +
`\n` +
`Parameters:\n` +
`\tlanguage_code : An ISO 693-1 code which has 2 characters eg: en\n` +
`\tlanguage_code : An ISO 693-1 code or a locale identifier.\n` +
`\n` +
`Reference:\n` +
`\tISO 693-1 codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`
`\tISO 693-1 codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes\n` +
`\tLocale identifier: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation`
);
}
return shouldPrint;
@ -125,7 +126,7 @@ function getLanguageCode() {
if (i === -1) {
return '';
}
return process.argv[i + 1]?.toLowerCase() ?? '';
return process.argv[i + 1] ?? '';
}
function getTranslationFilePath(languageCode) {
@ -213,14 +214,6 @@ async function run() {
const languageCode = getLanguageCode();
console.log();
if (languageCode.length !== 0 && languageCode.length !== 2) {
console.error(
`Invalid language code passed: '${languageCode}'.\n` +
`Please use an ISO 639-1 language code: ${'https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes'}`
);
return;
}
const fileList = await getFileList(root, ignoreList);
const contents = await getFileContents(fileList);
const tMap = await getAllTStringsMap(contents);

View File

@ -33,7 +33,7 @@ function getIndexFormat(inp) {
function getSnippets(string) {
let start = 0;
snippets = [...string.matchAll(/\${[^}]+}/g)].map((m) => {
const snippets = [...string.matchAll(/\${[^}]+}/g)].map((m) => {
let end = m.index;
let snip = string.slice(start, end);
start = end + m[0].length;

View File

@ -18,6 +18,13 @@
@setup-complete="setupComplete"
@setup-canceled="setupCanceled"
/>
<div
id="toast-container"
class="absolute bottom-0 flex flex-col items-end mb-3 pr-6"
style="width: 100%"
>
<div id="toast-target" />
</div>
</div>
</template>
@ -103,7 +110,7 @@ export default {
this.activeScreen = 'SetupWizard';
} else {
this.activeScreen = 'Desk';
checkForUpdates(false);
await checkForUpdates(false);
}
if (!resetRoute) {

View File

@ -16,6 +16,7 @@ import fs from 'fs/promises';
import path from 'path';
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import { sendError } from './contactMothership';
import { getLanguageMap } from './getLanguageMap';
import { IPC_ACTIONS, IPC_CHANNELS, IPC_MESSAGES } from './messages';
import saveHtmlAsPdf from './saveHtmlAsPdf';
@ -220,6 +221,18 @@ ipcMain.handle(IPC_ACTIONS.CHECK_FOR_UPDATES, (event, force) => {
}
});
ipcMain.handle(IPC_ACTIONS.GET_LANGUAGE_MAP, async (event, code) => {
let obj = { languageMap: {}, success: true, message: '' };
try {
obj.languageMap = await getLanguageMap(code, isDevelopment);
} catch (err) {
obj.success = false;
obj.message = err.message;
}
return obj;
});
/* ------------------------------
* Register autoUpdater events lis
* ------------------------------*/

View File

@ -0,0 +1,49 @@
<template>
<FormControl
:df="languageDf"
:value="value"
@change="(v) => setLanguageMap(v, dontReload)"
:input-class="'focus:outline-none rounded ' + inputClass"
/>
</template>
<script>
import config from '@/config';
import { languageCodeMap } from '@/languageCodeMap';
import { setLanguageMap } from '@/utils';
import { DEFAULT_LANGUAGE } from 'frappe/utils/consts';
import FormControl from './FormControl';
export default {
methods: {
setLanguageMap,
},
props: {
inputClass: {
type: String,
default:
'bg-gray-100 active:bg-gray-200 focus:bg-gray-200 px-3 py-2 text-base',
},
dontReload: {
type: Boolean,
default: false,
},
},
components: { FormControl },
computed: {
value() {
return config.get('language') ?? DEFAULT_LANGUAGE;
},
languageDf() {
languageCodeMap;
return {
fieldname: 'language',
label: this.t`Language`,
fieldtype: 'Select',
options: Object.keys(languageCodeMap),
default: config.get('language') ?? DEFAULT_LANGUAGE,
description: this.t`Set the display language.`,
};
},
},
};
</script>

View File

@ -14,7 +14,7 @@
:class="inputClasses"
>
<select
class="appearance-none bg-transparent focus:outline-none w-11/12"
class="appearance-none bg-transparent focus:outline-none w-11/12 cursor-pointer"
:class="{
'pointer-events-none': isReadOnly,
'text-gray-400': !value,

148
src/getLanguageMap.js Normal file
View File

@ -0,0 +1,148 @@
/**
* 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.
*/
const fs = require('fs/promises');
const path = require('path');
const fetch = require('node-fetch').default;
const { splitCsvLine } = require('../scripts/helpers');
const VALENTINES_DAY = 1644796800000;
async function getLanguageMap(code, isDevelopment = false) {
const contents = await getContents(code, isDevelopment);
return getMapFromContents(contents);
}
async function getContents(code, isDevelopment) {
if (isDevelopment) {
const filePath = path.resolve('translations', `${code}.csv`);
const contents = await fs.readFile(filePath, { encoding: 'utf-8' });
return ['', contents].join('\n');
}
let contents = await getContentsIfExists();
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;
}
function getMapFromContents(contents) {
contents = contents.split('\n').slice(1);
return contents
.map(splitCsvLine)
.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 };
const context = l.slice(2);
if (context.length) {
acc.context = context;
}
return acc;
}, {});
}
async function getContentsIfExists(code) {
const filePath = getFilePath(code);
try {
return await fs.readFile(filePath, { encoding: 'utf-8' });
} catch (err) {
if (err.errno !== -2) {
throw err;
}
return '';
}
}
async function fetchAndStoreFile(code, date) {
let res = await fetch(
`https://api.github.com/repos/frappe/books/contents/translations/${code}.csv`
);
let contents = undefined;
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`
);
}
if (!contents && res.status === 200) {
contents = await res.text();
}
if (!date && contents) {
date = await getLastUpdated(code);
}
if (contents) {
contents = [date.toISOString(), contents].join('\n');
await storeFile(code, contents);
}
return contents;
}
async function getUpdatedContent(code, contents) {
const [shouldUpdate, date] = await shouldUpdateFile(code, contents);
if (!shouldUpdate) {
return contents;
}
return await fetchAndStoreFile(code, date);
}
async function shouldUpdateFile(code, contents) {
const date = await getLastUpdated(code);
const oldDate = new Date(contents.split('\n')[0]);
const shouldUpdate = date > oldDate || +oldDate === VALENTINES_DAY;
return [shouldUpdate, date];
}
async function getLastUpdated(code) {
const url = `https://api.github.com/repos/frappe/books/commits?path=translations%2F${code}.csv&page=1&per_page=1`;
const resJson = await fetch(url).then((res) => res.json());
try {
return new Date(resJson[0].commit.author.date);
} catch {
return new Date(VALENTINES_DAY);
}
}
function getFilePath(code) {
return path.resolve(process.resourcesPath, 'translations', `${code}.csv`);
}
function throwCouldNotFetchFile(code) {
throw new Error(`Could not fetch translations for '${code}'.`);
}
async function storeFile(code, contents) {
const filePath = getFilePath(code);
const dirname = path.dirname(filePath);
await fs.mkdir(dirname, { recursive: true });
await fs.writeFile(filePath, contents, { encoding: 'utf-8' });
}
module.exports = { getLanguageMap };

View File

@ -6,7 +6,7 @@ import regionalModelUpdates from '../models/regionalModelUpdates';
import postStart, { setCurrencySymbols } from '../server/postStart';
import { DB_CONN_FAILURE } from './messages';
import runMigrate from './migrate';
import { callInitializeMoneyMaker, getSavePath } from './utils';
import { callInitializeMoneyMaker, getSavePath, setLanguageMap } from './utils';
export async function createNewDatabase() {
const { canceled, filePath } = await getSavePath('books', 'db');

7
src/languageCodeMap.js Normal file
View File

@ -0,0 +1,7 @@
// Language: Language Code in books/translations
export const languageCodeMap = {
English: 'en',
French: 'fr',
German: 'de',
Portuguese: 'pt',
};

View File

@ -4,25 +4,26 @@ import { createApp } from 'vue';
import models from '../models';
import App from './App';
import FeatherIcon from './components/FeatherIcon';
import config from './config';
import { getErrorHandled, handleError } from './errorHandling';
import { IPC_CHANNELS, IPC_MESSAGES } from './messages';
import router from './router';
import { outsideClickDirective } from './ui';
import { showToast, stringifyCircular } from './utils';
import { setLanguageMap, showToast, stringifyCircular } from './utils';
(async () => {
const language = config.get('language');
if (language) {
await setLanguageMap(language);
}
frappe.isServer = true;
frappe.isElectron = true;
frappe.initializeAndRegister(models);
frappe.initializeAndRegister(models, language);
frappe.fetch = window.fetch.bind();
ipcRenderer.send = getErrorHandled(ipcRenderer.send);
ipcRenderer.invoke = getErrorHandled(ipcRenderer.invoke);
frappe.events.on('reload-main-window', () => {
ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
});
window.frappe = frappe;
window.frappe.store = {};

View File

@ -23,6 +23,7 @@ export const IPC_ACTIONS = {
SAVE_DATA: 'save-data',
SHOW_ERROR: 'show-error',
SEND_ERROR: 'send-error',
GET_LANGUAGE_MAP: 'get-language-map',
CHECK_FOR_UPDATES: 'check-for-updates',
};

View File

@ -149,6 +149,12 @@
</div>
</div>
</div>
<div
class="w-full flex justify-end absolute px-8"
style="top: 100%; transform: translateY(-175%)"
>
<LanguageSelector class="w-28" input-class="text-base" />
</div>
</div>
</template>
<script>
@ -160,6 +166,7 @@ import { DB_CONN_FAILURE, IPC_ACTIONS } from '../messages';
import { createNewDatabase, connectToLocalDatabase } from '@/initialization';
import { showErrorDialog } from '../errorHandling';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
export default {
name: 'DatabaseSelector',
@ -211,18 +218,15 @@ export default {
if (!filePath) {
return;
}
this.loadingDatabase = true;
const { connectionSuccess, reason } = await connectToLocalDatabase(
filePath
);
this.loadingDatabase = false;
if (connectionSuccess) {
this.$emit('database-connect');
return;
}
const title = this.t`DB Connection Error`;
let content =
this.t`Please select an existing database or create a new one.` +
@ -231,7 +235,6 @@ export default {
content = this
.t`Can't open database file: ${filePath}, please create a new file.`;
}
await showErrorDialog(title, content);
},
getFileLastModified(filePath) {
@ -239,5 +242,6 @@ export default {
return DateTime.fromJSDate(stats.mtime).toRelative();
},
},
components: { LanguageSelector },
};
</script>

View File

@ -22,13 +22,6 @@
</keep-alive>
</router-view>
</div>
<div
id="toast-container"
class="absolute bottom-0 flex flex-col items-center mb-3"
style="width: calc(100% - 12rem)"
>
<div id="toast-target" />
</div>
</div>
</div>
</template>

View File

@ -63,6 +63,8 @@ import StatusBadge from '@/components/StatusBadge';
import { callInitializeMoneyMaker } from '../../utils';
import { showToast } from '../../utils';
import { h, markRaw } from 'vue';
import { ipcRenderer } from 'electron';
import { IPC_MESSAGES } from '@/messages';
export default {
name: 'Settings',
@ -113,9 +115,12 @@ export default {
fieldnames.includes('displayPrecision') ||
fieldnames.includes('hideGetStarted')
) {
callInitializeMoneyMaker(undefined, true);
this.showReloadToast();
}
if (fieldnames.includes('displayPrecision')) {
callInitializeMoneyMaker(undefined, true);
}
},
methods: {
showReloadToast() {
@ -124,7 +129,7 @@ export default {
actionText: frappe.t`Reload App`,
type: 'info',
action: async () => {
frappe.events.trigger('reload-main-window');
ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW)
},
});
},

View File

@ -8,9 +8,10 @@
:emit-change="true"
@change="forwardChangeEvent"
/>
<div class="flex flex-row justify-end my-4">
<div class="flex flex-row justify-between my-4">
<LanguageSelector class="text-sm" input-class="px-4 py-1.5"/>
<button
class="text-gray-900 text-sm hover:bg-gray-100 rounded-md px-4 py-1.5"
class="text-gray-900 text-sm hover:bg-gray-200 rounded-md px-4 py-1.5"
@click="checkForUpdates(true)"
>
Check for Updates
@ -23,11 +24,13 @@
import frappe from 'frappe';
import TwoColumnForm from '@/components/TwoColumnForm';
import { checkForUpdates } from '@/utils';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
export default {
name: 'TabSystem',
components: {
TwoColumnForm,
LanguageSelector,
},
emits: ['change'],
data() {

View File

@ -1,94 +1,120 @@
<template>
<div
class="flex-1 py-10 bg-white"
:class="{
'window-drag': platform !== 'Windows',
}"
>
<div class="px-12">
<h1 class="text-2xl font-semibold">{{ t`Setup your organization` }}</h1>
</div>
<div class="px-8 mt-5 window-no-drag" v-if="doc">
<div class="flex items-center px-6 py-5 mb-4 border bg-brand rounded-xl">
<FormControl
:df="meta.getField('companyLogo')"
:value="doc.companyLogo"
@change="(value) => setValue('companyLogo', value)"
/>
<div class="ml-2">
<FormControl
ref="companyField"
:df="meta.getField('companyName')"
:value="doc.companyName"
@change="(value) => setValue('companyName', value)"
:input-class="
(classes) => [
'bg-transparent font-semibold text-xl text-white placeholder-blue-200 focus:outline-none focus:bg-blue-600 px-3 rounded py-1',
]
"
:autofocus="true"
/>
<Popover placement="auto" :show-popup="Boolean(emailError)">
<template #target>
<div>
<Slide
@primary-clicked="handlePrimary"
@secondary-clicked="handleSecondary"
v-show="index === 0"
>
<template #title>
{{ t`Select your language` }}
</template>
<template #content>
<div class="flex flex-col justify-center items-center h-96">
<LanguageSelector class="w-40 mt-8" :dont-reload="true" />
<p
class="text-sm mt-2 hover:underline cursor-pointer text-gray-700"
@click="openContributingTranslations"
>
{{ t`I can't find my language.` }}
</p>
</div>
</template>
<template #secondaryButton>
{{ t`Cancel` }}
</template>
<template #primaryButton>
{{ t`Next` }}
</template>
</Slide>
<Slide
:primary-disabled="!valuesFilled || loading"
@primary-clicked="handlePrimary"
@secondary-clicked="handleSecondary"
v-show="index === 1"
>
<template #title>
{{ t`Setup your organization` }}
</template>
<template #content>
<div v-if="doc">
<div
class="flex items-center px-6 py-5 mb-4 border bg-brand rounded-xl"
>
<FormControl
:df="meta.getField('companyLogo')"
:value="doc.companyLogo"
@change="(value) => setValue('companyLogo', value)"
/>
<div class="ml-2">
<FormControl
:df="meta.getField('email')"
:value="doc.email"
@change="(value) => setValue('email', value)"
ref="companyField"
:df="meta.getField('companyName')"
:value="doc.companyName"
@change="(value) => setValue('companyName', value)"
:input-class="
(classes) => [
'text-base bg-transparent text-white placeholder-blue-200 focus:bg-blue-600 focus:outline-none rounded px-3 py-1',
'bg-transparent font-semibold text-xl text-white placeholder-blue-200 focus:outline-none focus:bg-blue-600 px-3 rounded py-1',
]
"
:autofocus="true"
/>
</template>
<template #content>
<div class="p-2 text-sm">
{{ emailError }}
</div>
</template>
</Popover>
<Popover placement="auto" :show-popup="Boolean(emailError)">
<template #target>
<FormControl
:df="meta.getField('email')"
:value="doc.email"
@change="(value) => setValue('email', value)"
:input-class="
(classes) => [
'text-base bg-transparent text-white placeholder-blue-200 focus:bg-blue-600 focus:outline-none rounded px-3 py-1',
]
"
/>
</template>
<template #content>
<div class="p-2 text-sm">
{{ emailError }}
</div>
</template>
</Popover>
</div>
</div>
<TwoColumnForm :fields="fields" :doc="doc" />
</div>
</div>
<TwoColumnForm :fields="fields" :doc="doc" />
</div>
<div class="flex justify-between px-8 mt-5 window-no-drag">
<Button class="text-sm text-grey-900" @click="$emit('setup-canceled')"
>Cancel</Button
>
<Button
@click="submit"
type="primary"
class="text-sm text-white"
:disabled="!valuesFilled || loading"
>
{{ buttonText }}
</Button>
</div>
</template>
<template #secondaryButton>{{ t`Back` }}</template>
<template #primaryButton>{{ t`Submit` }}</template>
</Slide>
</div>
</template>
<script>
import frappe from 'frappe';
import TwoColumnForm from '@/components/TwoColumnForm';
import FormControl from '@/components/Controls/FormControl';
import Button from '@/components/Button';
import setupCompany from './setupCompany';
import Popover from '@/components/Popover';
import config from '@/config';
import path from 'path';
import fs from 'fs';
import { purgeCache, connectToLocalDatabase } from '@/initialization';
import { showMessageDialog } from '@/utils';
import { setLanguageMap, showMessageDialog } from '@/utils';
import {
handleErrorWithDialog,
getErrorMessage,
showErrorDialog,
} from '../../errorHandling';
import Slide from './Slide.vue';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
import { ipcRenderer } from 'electron';
import { IPC_MESSAGES } from '@/messages';
export default {
name: 'SetupWizard',
emits: ['setup-complete', 'setup-canceled'],
data() {
return {
index: 0,
doc: null,
loading: false,
valuesFilled: false,
@ -104,16 +130,45 @@ export default {
components: {
TwoColumnForm,
FormControl,
Button,
Popover,
Slide,
LanguageSelector,
},
async mounted() {
if (config.get('language') !== undefined) {
this.index = 1;
}
this.doc = await frappe.newDoc({ doctype: 'SetupWizard' });
this.doc.on('change', () => {
this.valuesFilled = this.allValuesFilled();
});
},
methods: {
openContributingTranslations() {
ipcRenderer.send(
IPC_MESSAGES.OPEN_EXTERNAL,
'https://github.com/frappe/books/wiki/Contributing-Translations'
);
},
handlePrimary() {
if (this.index === 0) {
this.index = 1;
} else if (this.index === 1) {
this.submit();
}
},
handleSecondary() {
if (this.index === 1) {
this.index = 0;
} else if (this.index === 0) {
this.$emit('setup-canceled');
}
},
async selectLanguage(value) {
const success = await setLanguageMap(value);
this.setValue('language', value);
},
setValue(fieldname, value) {
this.emailError = null;
this.doc.set(fieldname, value).catch((e) => {

View File

@ -0,0 +1,45 @@
<template>
<div
class="flex-1 py-10 bg-white h-screen"
:class="{
'window-drag': platform !== 'Windows',
}"
>
<div class="px-12">
<h1 class="text-2xl font-semibold"><slot name="title"></slot></h1>
</div>
<div class="px-8 mt-5 window-no-drag">
<slot name="content"></slot>
</div>
<div
class="flex justify-between px-8 mt-5 window-no-drag absolute w-full"
style="top: 100%; transform: translateY(-260%)"
>
<Button class="text-sm text-grey-900" @click="$emit('secondary-clicked')">
<slot name="secondaryButton"></slot>
</Button>
<Button
@click="$emit('primary-clicked')"
type="primary"
class="text-sm text-white"
:disabled="primaryDisabled"
>
<slot name="primaryButton"></slot>
</Button>
</div>
</div>
</template>
<script>
import Button from '@/components/Button.vue';
export default {
emits: ['primary-clicked', 'secondary-clicked'],
components: { Button },
props: {
usePrimary: { type: Boolean, default: true },
primaryDisabled: { type: Boolean, default: false },
},
};
</script>

View File

@ -45,7 +45,7 @@ export default async function setupCompany(setupWizardValues) {
await setupGlobalCurrencies(countryList);
await setupChartOfAccounts(bankName, country);
await setupRegionalChanges(country);
updateCompanyNameInConfig();
updateInitializationConfig();
await accountingSettings.update({ setupComplete: 1 });
frappe.AccountingSettings = accountingSettings;
@ -109,7 +109,7 @@ async function setupRegionalChanges(country) {
await frappe.db.migrate();
}
function updateCompanyNameInConfig() {
function updateInitializationConfig(language) {
let filePath = frappe.db.dbPath;
let files = config.get('files', []);
files.forEach((file) => {

View File

@ -4,9 +4,13 @@ import router from '@/router';
import { ipcRenderer } from 'electron';
import frappe, { t } from 'frappe';
import { isPesa } from 'frappe/utils';
import { DEFAULT_LANGUAGE } from 'frappe/utils/consts';
import { setLanguageMapOnTranslationString } from 'frappe/utils/translation';
import lodash from 'lodash';
import { createApp, h } from 'vue';
import config from './config';
import { handleErrorWithDialog } from './errorHandling';
import { languageCodeMap } from './languageCodeMap';
import { IPC_ACTIONS, IPC_MESSAGES } from './messages';
export async function showMessageDialog({
@ -455,8 +459,57 @@ export function stringifyCircular(
});
}
window.showToast = showToast;
export function checkForUpdates(force = false) {
export async function checkForUpdates(force = false) {
ipcRenderer.invoke(IPC_ACTIONS.CHECK_FOR_UPDATES, force);
await setLanguageMap();
}
async function fetchAndSetLanguageMap(code) {
const { success, message, languageMap } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_LANGUAGE_MAP,
code
);
if (!success) {
showToast({ type: 'error', message });
} else {
setLanguageMapOnTranslationString(languageMap);
}
return success;
}
export async function setLanguageMap(initLanguage, dontReload = false) {
const oldLanguage = config.get('language');
initLanguage ??= oldLanguage;
const [code, language, usingDefault] = getLanguageCode(
initLanguage,
oldLanguage
);
let success = true;
if (code === 'en') {
setLanguageMapOnTranslationString(undefined);
} else {
success = await fetchAndSetLanguageMap(code);
}
if (success && !usingDefault) {
config.set('language', language);
}
if (!dontReload && success && initLanguage !== oldLanguage) {
await ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
}
return success;
}
function getLanguageCode(initLanguage, oldLanguage) {
let language = initLanguage ?? oldLanguage;
let usingDefault = false;
if (!language) {
language = DEFAULT_LANGUAGE;
usingDefault = true;
}
return [languageCodeMap[language], language, usingDefault];
}

View File

@ -35,7 +35,7 @@
`An error occurred.`,`Es ist ein Fehler aufgetreten.`
`Application of Funds (Assets)`,`Verwendung der Mittel (Aktiva)`
`Are you sure you want to cancel ${0} ${1}?`,`Sind Sie sicher, dass Sie ${0} ${1} stornieren wollen?`
`Are you sure you want to delete ${0} "${1}"?`,`Sind Sie sicher, dass Sie ${0} "${1}" löschen wollen?`
`Are you sure you want to delete ${0} ${1}?`,`Sind Sie sicher, dass Sie ${0} ${1} löschen wollen?`
`Are you sure you want to submit this Bill?`,`Sind Sie sicher, dass Sie diesen Gesetzentwurf einreichen wollen?`
`Are you sure you want to submit this Invoice?`,`Sind Sie sicher, dass Sie diese Rechnung einreichen möchten?`
`Are you sure you want to submit this Journal Entry?`,`Sind Sie sicher, dass Sie diesen Journaleintrag abschicken wollen?`
@ -304,7 +304,7 @@
`Place of supply`,`Ort der Lieferung`
`Plants and Machineries`,`Anlagen und Maschinen`
`Please fill all values`,`Bitte alle Werte ausfüllen`
`Please select an existing database or create a new one. reason: ${0}, filePath: ${1}`,`Bitte wählen Sie eine vorhandene Datenbank oder erstellen Sie eine neue. reason: ${0}, filePath: ${1}`
`Please select an existing database or create a new one.`,`Bitte wählen Sie eine vorhandene Datenbank oder erstellen Sie eine neue.`
`Please set GSTIN in General Settings.`,`Bitte stellen Sie die GSTIN in den Allgemeinen Einstellungen ein.`
`Postal Code`,`Postleitzahl`
`Postal Expenses`,`Postkosten`
@ -476,5 +476,4 @@
`Year`,`Jahr`
`Yes`,`Ja`
`Your Name`,`Ihr Name`
`name`,`Name`
`reason: ${0}, filePath: ${1}`,`Grund: ${0}, Dateipfad: ${1}`
`name`,`Name`
Can't render this file because it has a wrong number of fields in line 33.

View File

@ -35,7 +35,7 @@
`An error occurred.`,`Une erreur s'est produite.`
`Application of Funds (Assets)`,`Application des fonds (actifs)`
`Are you sure you want to cancel ${0} ${1}?`,`Vous êtes sûr de vouloir annuler ${0} ${1} ?`
`Are you sure you want to delete ${0} "${1}"?`,`Etes-vous sûr de vouloir supprimer ${0} "${1}" ?`
`Are you sure you want to delete ${0} ${1}?`,`Etes-vous sûr de vouloir supprimer ${0} ${1} ?`
`Are you sure you want to submit this Bill?`,`Êtes-vous sûr de vouloir soumettre ce projet de loi ?`
`Are you sure you want to submit this Invoice?`,`Êtes-vous sûr de vouloir soumettre cette facture ?`
`Are you sure you want to submit this Journal Entry?`,`Êtes-vous sûr de vouloir soumettre cette entrée de journal ?`
@ -304,7 +304,7 @@
`Place of supply`,`Lieu d'approvisionnement`
`Plants and Machineries`,`Usines et machineries`
`Please fill all values`,`Veuillez remplir toutes les valeurs`
`Please select an existing database or create a new one. reason: ${0}, filePath: ${1}`,`Veuillez sélectionner une base de données existante ou en créer une nouvelle. reason : ${0}, filePath : ${1}`
`Please select an existing database or create a new one.`,`Veuillez sélectionner une base de données existante ou en créer une nouvelle.`
`Please set GSTIN in General Settings.`,`Veuillez définir le GSTIN dans les paramètres généraux.`
`Postal Code`,`Code postal`
`Postal Expenses`,`Dépenses postales`
@ -476,5 +476,4 @@
`Year`,`Année`
`Yes`,`Oui`
`Your Name`,`Votre nom`
`name`,`nom`
`reason: ${0}, filePath: ${1}`,`raison : ${0}, filePath : ${1}`
`name`,`nom`
Can't render this file because it contains an unexpected character in line 38 and column 39.

View File

@ -35,7 +35,7 @@
`An error occurred.`,`Ocorreu um erro.`
`Application of Funds (Assets)`,`Aplicação de Fundos (Activos)`
`Are you sure you want to cancel ${0} ${1}?`,`Tem a certeza de que quer cancelar ${0} ${1}?`
`Are you sure you want to delete ${0} "${1}"?`,`Tem a certeza que quer apagar ${0} "${1}"?`
`Are you sure you want to delete ${0} ${1}?`,`Tem a certeza que quer apagar ${0} ${1}?`
`Are you sure you want to submit this Bill?`,`Tem a certeza de que quer apresentar esta Proposta de Lei?`
`Are you sure you want to submit this Invoice?`,`Tem a certeza de que quer apresentar esta factura?`
`Are you sure you want to submit this Journal Entry?`,`Tem a certeza de que quer submeter esta Entrada no Jornal?`
@ -304,7 +304,7 @@
`Place of supply`,`Local de fornecimento`
`Plants and Machineries`,`Fábricas e maquinarias`
`Please fill all values`,`Por favor preencha todos os valores`
`Please select an existing database or create a new one. reason: ${0}, filePath: ${1}`,`Por favor seleccione uma base de dados existente ou crie uma nova. razão: ${0}, filePath: ${1}`
`Please select an existing database or create a new one.`,`Por favor seleccione uma base de dados existente ou crie uma nova.`
`Please set GSTIN in General Settings.`,`Por favor, defina GSTIN em Definições Gerais.`
`Postal Code`,`Código Postal`
`Postal Expenses`,`Despesas de correio`
@ -476,5 +476,4 @@
`Year`,`Ano`
`Yes`,`Sim`
`Your Name`,`O seu nome`
`name`,`nome`
`reason: ${0}, filePath: ${1}`,`razão: ${0}, filePath: ${1}`
`name`,`nome`
Can't render this file because it contains an unexpected character in line 38 and column 39.

View File

@ -7965,7 +7965,7 @@ node-emoji@^1.11.0:
dependencies:
lodash "^4.17.21"
node-fetch@^2.6.1:
node-fetch@2, node-fetch@^2.6.1:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==