2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 11:29:03 +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_INTERNAL_PRECISION,
DEFAULT_LOCALE,
DEFAULT_LANGUAGE,
} = require('../../../utils/consts');
const { languageCodeMap } = require('@/languageCodeMap');
let dateFormatOptions = (() => {
let formats = [
@ -53,14 +51,6 @@ module.exports = {
default: DEFAULT_LOCALE,
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',
label: t`Display Precision`,
@ -104,7 +94,6 @@ module.exports = {
],
quickEditFields: [
'locale',
'language',
'dateFormat',
'displayPrecision',
'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"
>
<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,

View File

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

View File

@ -4,25 +4,27 @@ 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) {
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

@ -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

@ -61,8 +61,10 @@ import Icon from '@/components/Icon';
import PageHeader from '@/components/PageHeader';
import StatusBadge from '@/components/StatusBadge';
import { callInitializeMoneyMaker } from '../../utils';
import { showToast, setLanguageMap } from '../../utils';
import { showToast } from '../../utils';
import { h, markRaw } from 'vue';
import { ipcRenderer } from 'electron';
import { IPC_MESSAGES } from '@/messages';
export default {
name: 'Settings',
@ -111,8 +113,7 @@ export default {
if (
fieldnames.includes('displayPrecision') ||
fieldnames.includes('hideGetStarted') ||
fieldnames.includes('language')
fieldnames.includes('hideGetStarted')
) {
this.showReloadToast();
}
@ -120,10 +121,6 @@ export default {
if (fieldnames.includes('displayPrecision')) {
callInitializeMoneyMaker(undefined, true);
}
if (fieldnames.includes('language')) {
setLanguageMap();
}
},
methods: {
showReloadToast() {
@ -132,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,112 @@
<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" />
</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';
export default {
name: 'SetupWizard',
emits: ['setup-complete', 'setup-canceled'],
data() {
return {
index: 0,
doc: null,
loading: false,
valuesFilled: false,
@ -104,16 +122,39 @@ 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: {
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

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

View File

@ -4,9 +4,11 @@ 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';
@ -462,14 +464,7 @@ export async function checkForUpdates(force = false) {
await setLanguageMap();
}
export async function setLanguageMap(language) {
const code = getLanguageCode(language);
if (code === 'en') {
setLanguageMapOnTranslationString(undefined);
return;
}
async function fetchAndSetLanguageMap(code) {
const { success, message, languageMap } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_LANGUAGE_MAP,
code
@ -477,13 +472,44 @@ export async function setLanguageMap(language) {
if (!success) {
showToast({ type: 'error', message });
return;
} else {
setLanguageMapOnTranslationString(languageMap);
}
setLanguageMapOnTranslationString(languageMap);
return success;
}
export function getLanguageCode(initLanguage) {
const { language } = initLanguage ?? frappe.SystemSettings;
return languageCodeMap[language || 'English'];
export async function setLanguageMap(initLanguage) {
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 (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];
}