2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 19:39:07 +00:00

feat: add selectors for language

- remove instance config
- use app level config instead
This commit is contained in:
18alantom 2022-02-17 13:16:36 +05:30
parent ce3d997ed2
commit c83ac376dd
12 changed files with 261 additions and 111 deletions

View File

@ -4,9 +4,7 @@ const {
DEFAULT_DISPLAY_PRECISION, DEFAULT_DISPLAY_PRECISION,
DEFAULT_INTERNAL_PRECISION, DEFAULT_INTERNAL_PRECISION,
DEFAULT_LOCALE, DEFAULT_LOCALE,
DEFAULT_LANGUAGE,
} = require('../../../utils/consts'); } = require('../../../utils/consts');
const { languageCodeMap } = require('@/languageCodeMap');
let dateFormatOptions = (() => { let dateFormatOptions = (() => {
let formats = [ let formats = [
@ -53,14 +51,6 @@ module.exports = {
default: DEFAULT_LOCALE, default: DEFAULT_LOCALE,
description: t`Set the local code, this is used for number formatting.`, description: t`Set the local code, this is used for number formatting.`,
}, },
{
fieldname: 'language',
label: t`Language`,
fieldtype: 'Select',
options: Object.keys(languageCodeMap),
default: DEFAULT_LANGUAGE,
description: t`Set the display language.`,
},
{ {
fieldname: 'displayPrecision', fieldname: 'displayPrecision',
label: t`Display Precision`, label: t`Display Precision`,
@ -104,7 +94,6 @@ module.exports = {
], ],
quickEditFields: [ quickEditFields: [
'locale', 'locale',
'language',
'dateFormat', 'dateFormat',
'displayPrecision', 'displayPrecision',
'hideGetStarted', 'hideGetStarted',

View File

@ -0,0 +1,45 @@
<template>
<FormControl
:df="languageDf"
:value="value"
@change="setLanguageMap"
: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',
},
},
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" :class="inputClasses"
> >
<select <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="{ :class="{
'pointer-events-none': isReadOnly, 'pointer-events-none': isReadOnly,
'text-gray-400': !value, 'text-gray-400': !value,

View File

@ -72,7 +72,6 @@ export async function connectToLocalDatabase(filePath) {
// set file info in config // set file info in config
const { companyName } = frappe.AccountingSettings; const { companyName } = frappe.AccountingSettings;
await setLanguageMap();
let files = config.get('files') || []; let files = config.get('files') || [];
if ( if (
!files.find( !files.find(

View File

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

View File

@ -149,6 +149,12 @@
</div> </div>
</div> </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> </div>
</template> </template>
<script> <script>
@ -160,6 +166,7 @@ import { DB_CONN_FAILURE, IPC_ACTIONS } from '../messages';
import { createNewDatabase, connectToLocalDatabase } from '@/initialization'; import { createNewDatabase, connectToLocalDatabase } from '@/initialization';
import { showErrorDialog } from '../errorHandling'; import { showErrorDialog } from '../errorHandling';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
export default { export default {
name: 'DatabaseSelector', name: 'DatabaseSelector',
@ -211,18 +218,15 @@ export default {
if (!filePath) { if (!filePath) {
return; return;
} }
this.loadingDatabase = true; this.loadingDatabase = true;
const { connectionSuccess, reason } = await connectToLocalDatabase( const { connectionSuccess, reason } = await connectToLocalDatabase(
filePath filePath
); );
this.loadingDatabase = false; this.loadingDatabase = false;
if (connectionSuccess) { if (connectionSuccess) {
this.$emit('database-connect'); this.$emit('database-connect');
return; return;
} }
const title = this.t`DB Connection Error`; const title = this.t`DB Connection Error`;
let content = let content =
this.t`Please select an existing database or create a new one.` + this.t`Please select an existing database or create a new one.` +
@ -231,7 +235,6 @@ export default {
content = this content = this
.t`Can't open database file: ${filePath}, please create a new file.`; .t`Can't open database file: ${filePath}, please create a new file.`;
} }
await showErrorDialog(title, content); await showErrorDialog(title, content);
}, },
getFileLastModified(filePath) { getFileLastModified(filePath) {
@ -239,5 +242,6 @@ export default {
return DateTime.fromJSDate(stats.mtime).toRelative(); return DateTime.fromJSDate(stats.mtime).toRelative();
}, },
}, },
components: { LanguageSelector },
}; };
</script> </script>

View File

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

View File

@ -8,9 +8,10 @@
:emit-change="true" :emit-change="true"
@change="forwardChangeEvent" @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 <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)" @click="checkForUpdates(true)"
> >
Check for Updates Check for Updates
@ -23,11 +24,13 @@
import frappe from 'frappe'; import frappe from 'frappe';
import TwoColumnForm from '@/components/TwoColumnForm'; import TwoColumnForm from '@/components/TwoColumnForm';
import { checkForUpdates } from '@/utils'; import { checkForUpdates } from '@/utils';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
export default { export default {
name: 'TabSystem', name: 'TabSystem',
components: { components: {
TwoColumnForm, TwoColumnForm,
LanguageSelector,
}, },
emits: ['change'], emits: ['change'],
data() { data() {

View File

@ -1,94 +1,112 @@
<template> <template>
<div <div>
class="flex-1 py-10 bg-white" <Slide
:class="{ @primary-clicked="handlePrimary"
'window-drag': platform !== 'Windows', @secondary-clicked="handleSecondary"
}" v-show="index === 0"
> >
<div class="px-12"> <template #title>
<h1 class="text-2xl font-semibold">{{ t`Setup your organization` }}</h1> {{ t`Select your language` }}
</div> </template>
<div class="px-8 mt-5 window-no-drag" v-if="doc"> <template #content>
<div class="flex items-center px-6 py-5 mb-4 border bg-brand rounded-xl"> <div class="flex flex-col justify-center items-center h-96">
<FormControl <LanguageSelector class="w-40" />
:df="meta.getField('companyLogo')" </div>
:value="doc.companyLogo" </template>
@change="(value) => setValue('companyLogo', value)" <template #secondaryButton>
/> {{ t`Cancel` }}
<div class="ml-2"> </template>
<FormControl <template #primaryButton>
ref="companyField" {{ t`Next` }}
:df="meta.getField('companyName')" </template>
:value="doc.companyName" </Slide>
@change="(value) => setValue('companyName', value)" <Slide
:input-class=" :primary-disabled="!valuesFilled || loading"
(classes) => [ @primary-clicked="handlePrimary"
'bg-transparent font-semibold text-xl text-white placeholder-blue-200 focus:outline-none focus:bg-blue-600 px-3 rounded py-1', @secondary-clicked="handleSecondary"
] v-show="index === 1"
" >
:autofocus="true" <template #title>
/> {{ t`Setup your organization` }}
<Popover placement="auto" :show-popup="Boolean(emailError)"> </template>
<template #target> <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 <FormControl
:df="meta.getField('email')" ref="companyField"
:value="doc.email" :df="meta.getField('companyName')"
@change="(value) => setValue('email', value)" :value="doc.companyName"
@change="(value) => setValue('companyName', value)"
:input-class=" :input-class="
(classes) => [ (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> <Popover placement="auto" :show-popup="Boolean(emailError)">
<template #content> <template #target>
<div class="p-2 text-sm"> <FormControl
{{ emailError }} :df="meta.getField('email')"
</div> :value="doc.email"
</template> @change="(value) => setValue('email', value)"
</Popover> :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>
</div> </template>
<TwoColumnForm :fields="fields" :doc="doc" /> <template #secondaryButton>{{ t`Back` }}</template>
</div> <template #primaryButton>{{ t`Submit` }}</template>
<div class="flex justify-between px-8 mt-5 window-no-drag"> </Slide>
<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>
</div> </div>
</template> </template>
<script> <script>
import frappe from 'frappe'; import frappe from 'frappe';
import TwoColumnForm from '@/components/TwoColumnForm'; import TwoColumnForm from '@/components/TwoColumnForm';
import FormControl from '@/components/Controls/FormControl'; import FormControl from '@/components/Controls/FormControl';
import Button from '@/components/Button';
import setupCompany from './setupCompany'; import setupCompany from './setupCompany';
import Popover from '@/components/Popover'; import Popover from '@/components/Popover';
import config from '@/config'; import config from '@/config';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { purgeCache, connectToLocalDatabase } from '@/initialization'; import { purgeCache, connectToLocalDatabase } from '@/initialization';
import { showMessageDialog } from '@/utils'; import { setLanguageMap, showMessageDialog } from '@/utils';
import { import {
handleErrorWithDialog, handleErrorWithDialog,
getErrorMessage, getErrorMessage,
showErrorDialog, showErrorDialog,
} from '../../errorHandling'; } from '../../errorHandling';
import Slide from './Slide.vue';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
export default { export default {
name: 'SetupWizard', name: 'SetupWizard',
emits: ['setup-complete', 'setup-canceled'], emits: ['setup-complete', 'setup-canceled'],
data() { data() {
return { return {
index: 0,
doc: null, doc: null,
loading: false, loading: false,
valuesFilled: false, valuesFilled: false,
@ -104,16 +122,39 @@ export default {
components: { components: {
TwoColumnForm, TwoColumnForm,
FormControl, FormControl,
Button,
Popover, Popover,
Slide,
LanguageSelector,
}, },
async mounted() { async mounted() {
if (config.get('language') !== undefined) {
this.index = 1;
}
this.doc = await frappe.newDoc({ doctype: 'SetupWizard' }); this.doc = await frappe.newDoc({ doctype: 'SetupWizard' });
this.doc.on('change', () => { this.doc.on('change', () => {
this.valuesFilled = this.allValuesFilled(); this.valuesFilled = this.allValuesFilled();
}); });
}, },
methods: { methods: {
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) { setValue(fieldname, value) {
this.emailError = null; this.emailError = null;
this.doc.set(fieldname, value).catch((e) => { 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

@ -4,9 +4,9 @@ import { DEFAULT_LOCALE } from 'frappe/utils/consts';
import countryList from '~/fixtures/countryInfo.json'; import countryList from '~/fixtures/countryInfo.json';
import generateTaxes from '../../../models/doctype/Tax/RegionalEntries'; import generateTaxes from '../../../models/doctype/Tax/RegionalEntries';
import regionalModelUpdates from '../../../models/regionalModelUpdates'; import regionalModelUpdates from '../../../models/regionalModelUpdates';
import { callInitializeMoneyMaker, setLanguageMap } from '../../utils'; import { callInitializeMoneyMaker } from '../../utils';
export default async function setupCompany(setupWizardValues, language) { export default async function setupCompany(setupWizardValues) {
const { const {
companyLogo, companyLogo,
companyName, companyName,
@ -18,7 +18,6 @@ export default async function setupCompany(setupWizardValues, language) {
fiscalYearEnd, fiscalYearEnd,
} = setupWizardValues; } = setupWizardValues;
await setLanguageMap(language);
const accountingSettings = frappe.AccountingSettings; const accountingSettings = frappe.AccountingSettings;
const currency = countryList[country]['currency']; const currency = countryList[country]['currency'];
const locale = countryList[country]['locale'] ?? DEFAULT_LOCALE; const locale = countryList[country]['locale'] ?? DEFAULT_LOCALE;
@ -51,7 +50,7 @@ export default async function setupCompany(setupWizardValues, language) {
await accountingSettings.update({ setupComplete: 1 }); await accountingSettings.update({ setupComplete: 1 });
frappe.AccountingSettings = accountingSettings; frappe.AccountingSettings = accountingSettings;
(await frappe.getSingle('SystemSettings')).update({ locale, language }); (await frappe.getSingle('SystemSettings')).update({ locale });
} }
async function setupGlobalCurrencies(countries) { async function setupGlobalCurrencies(countries) {

View File

@ -4,9 +4,11 @@ import router from '@/router';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe, { t } from 'frappe'; import frappe, { t } from 'frappe';
import { isPesa } from 'frappe/utils'; import { isPesa } from 'frappe/utils';
import { DEFAULT_LANGUAGE } from 'frappe/utils/consts';
import { setLanguageMapOnTranslationString } from 'frappe/utils/translation'; import { setLanguageMapOnTranslationString } from 'frappe/utils/translation';
import lodash from 'lodash'; import lodash from 'lodash';
import { createApp, h } from 'vue'; import { createApp, h } from 'vue';
import config from './config';
import { handleErrorWithDialog } from './errorHandling'; import { handleErrorWithDialog } from './errorHandling';
import { languageCodeMap } from './languageCodeMap'; import { languageCodeMap } from './languageCodeMap';
import { IPC_ACTIONS, IPC_MESSAGES } from './messages'; import { IPC_ACTIONS, IPC_MESSAGES } from './messages';
@ -462,14 +464,7 @@ export async function checkForUpdates(force = false) {
await setLanguageMap(); await setLanguageMap();
} }
export async function setLanguageMap(language) { async function fetchAndSetLanguageMap(code) {
const code = getLanguageCode(language);
if (code === 'en') {
setLanguageMapOnTranslationString(undefined);
return;
}
const { success, message, languageMap } = await ipcRenderer.invoke( const { success, message, languageMap } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_LANGUAGE_MAP, IPC_ACTIONS.GET_LANGUAGE_MAP,
code code
@ -477,13 +472,44 @@ export async function setLanguageMap(language) {
if (!success) { if (!success) {
showToast({ type: 'error', message }); showToast({ type: 'error', message });
return; } else {
setLanguageMapOnTranslationString(languageMap);
} }
setLanguageMapOnTranslationString(languageMap); return success;
} }
export function getLanguageCode(initLanguage) { export async function setLanguageMap(initLanguage) {
const { language } = initLanguage ?? frappe.SystemSettings; const oldLanguage = config.get('language');
return languageCodeMap[language || 'English']; 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 (success && initLanguage !== oldLanguage) {
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];
} }