Add support to translations (Tab Translations). (#229)

* Support to translations in ADempiere Vue.

* change popover to translation and add vuex store

* Add structure from vuex store to get translations.

* Add update translations with language.
pull/3759/head
EdwinBetanc0urt 2020-01-20 16:11:30 -04:00 committed by Yamel Senih
parent 45b53ab6ea
commit 8e771a01e8
13 changed files with 514 additions and 125 deletions

View File

@ -45,7 +45,7 @@
},
"dependencies": {
"@adempiere/grpc-access-client": "^1.1.8",
"@adempiere/grpc-data-client": "^1.8.7",
"@adempiere/grpc-data-client": "^1.8.8",
"@adempiere/grpc-dictionary-client": "^1.3.5",
"@adempiere/grpc-enrollment-client": "^1.0.7",
"autoprefixer": "^9.5.1",

View File

@ -80,12 +80,12 @@ export function getEntity({ tableName, recordId, recordUuid }) {
* @param {string} orderByClause
* @param {string} nextPageToken
*/
export function getEntitiesList({ tableName, query, whereClause, conditions = [], orderByClause, nextPageToken }) {
export function getEntitiesList({ tableName, query, whereClause, conditions: conditionsList = [], orderByClause, nextPageToken }) {
return Instance.call(this).requestEntitiesList({
tableName,
query,
whereClause,
conditionsList: conditions,
conditionsList,
orderByClause,
nextPageToken
})
@ -148,7 +148,7 @@ export function getLookupList({ tableName, query }) {
}]
* @param {string} printFormatUuid
*/
export function runProcess({ uuid, reportType, tableName, recordId, parameters: parametersList = [], selection = [], printFormatUuid }) {
export function runProcess({ uuid, reportType, tableName, recordId, parameters: parametersList = [], selection: selectionsList = [], printFormatUuid }) {
// Run Process
return Instance.call(this).requestRunProcess({
uuid,
@ -156,7 +156,7 @@ export function runProcess({ uuid, reportType, tableName, recordId, parameters:
tableName,
recordId,
parametersList,
selectionsList: selection,
selectionsList,
printFormatUuid
})
}
@ -244,7 +244,7 @@ export function getDefaultValueFromServer(query) {
}
export function getContextInfoValueFromServer({ uuid, query }) {
return Instance.call(this).getContextInfoValue({ uuid: uuid, query: query })
return Instance.call(this).getContextInfoValue({ uuid, query })
}
export function getPrivateAccessFromServer({ tableName, recordId, userUuid }) {
@ -314,6 +314,22 @@ export function requestLanguages() {
return Instance.call(this).requestLanguages()
}
/**
* Request translations
* @param {string} tableName
* @param {string} language
* @param {string} recordUuid
* @param {integer} recordId
*/
export function requestTranslations({ tableName, language, recordUuid, recordId }) {
return Instance.call(this).requestTranslations({
tableName,
recordUuid,
recordId,
language
})
}
export function requestDrillTables(tableName) {
return Instance.call(this).requestDrillTables(tableName)
}

View File

@ -0,0 +1,199 @@
<template>
<span>
<el-popover
ref="translatedField"
placement="top"
width="300"
trigger="click"
@show="getTranslation"
>
<div>
<span class="custom-tittle-popover">
{{ name }}
</span>
<template v-if="!isEmptyValue(help)">
: {{ help }}
</template>
</div>
<el-form-item
:required="true"
>
<template slot="label">
{{ $t('language') + ':' }}
</template>
<el-select
v-model="langValue"
size="medium"
style="width: 100%;"
filterable
@change="getTranslation"
>
<!-- <el-option
key="blank-option"
:value="undefined"
label=" "
/> -->
<el-option
v-for="(optionLang, key) in languageList"
:key="key"
:value="optionLang.language"
:label="optionLang.languageName"
/>
</el-select>
</el-form-item>
<el-form-item
label="Translated Value:"
:required="true"
>
<el-input
v-model="translatedValue"
:disabled="isEmptyValue(langValue)"
@change="changeTranslationValue"
/>
</el-form-item>
</el-popover>
<svg-icon
v-popover:translatedField
class-name="international-icon"
icon-class="language"
/>
</span>
</template>
<script>
import { getLanguage } from '@/lang/index'
export default {
name: 'FieldTranslated',
props: {
containerUuid: {
type: String,
required: true
},
columnName: {
type: String,
required: true
},
name: {
type: String,
default: undefined
},
help: {
type: String,
default: undefined
},
recordUuid: {
type: String,
default: undefined
},
tableName: {
type: String,
required: true
}
},
data() {
return {
langValue: undefined,
translatedValue: '',
isLoading: false
}
},
computed: {
languageList() {
return this.$store.getters.getLanguagesList.filter(itemLanguage => {
return !itemLanguage.isBaseLanguage
})
},
icon() {
if (this.isLoading) {
return 'el-icon-loading'
}
return 'el-icon-refresh'
},
getterTranslationValues() {
const values = this.$store.getters.getTranslationByLanguage({
containerUuid: this.containerUuid,
language: this.langValue,
recordUuid: this.recordUuid
})
if (this.isEmptyValue(values)) {
return undefined
}
return values
},
gettterValue() {
const values = this.getterTranslationValues
if (this.isEmptyValue(values)) {
return undefined
}
return values[this.columnName]
}
},
watch: {
gettterValue(newValue, oldValue) {
this.translatedValue = newValue
}
},
created() {
let langMatch = this.languageList.find(itemLanguage => {
return itemLanguage.languageISO === getLanguage()
})
if (langMatch) {
langMatch = langMatch.language
} else {
langMatch = this.languageList[0].language
}
this.langValue = langMatch
},
methods: {
getTranslation() {
if (this.isEmptyValue(this.getterTranslationValues)) {
this.getTranslationsFromServer()
}
},
getTranslationsFromServer() {
this.isLoading = true
this.$store.dispatch('getTranslationsFromServer', {
containerUuid: this.containerUuid,
recordUuid: this.recordUuid,
tableName: this.tableName,
language: this.langValue
})
.finally(() => {
this.isLoading = false
})
},
changeTranslationValue(value) {
this.$store.dispatch('changeTranslationValue', {
containerUuid: this.containerUuid,
language: this.langValue,
columnName: this.columnName,
value
})
}
}
}
</script>
<style lang="scss" scoped>
.custom-tittle-popover {
font-size: 14px;
font-weight: bold;
}
</style>
<style lang="scss">
/**
* Separation between elements (item) of the form
*/
.el-form-item {
margin-bottom: 10px !important;
margin-left: 10px;
margin-right: 10px;
}
/**
* Reduce the spacing between the form element and its label
*/
.el-form--label-top .el-form-item__label {
padding-bottom: 0px !important;
}
</style>

View File

@ -24,27 +24,48 @@
width="300"
trigger="click"
>
<p v-if="field.contextInfo && field.contextInfo.isActive" class="pre-formatted" v-html="field.contextInfo.messageText.msgText" />
<template v-if="field.reference.zoomWindowList.length">
<div class="el-popover__title"> {{ $t('table.ProcessActivity.zoomIn') }}</div>
<template v-for="(zoomItem, index) in field.reference.zoomWindowList">
<el-button :key="index" type="text" @click="redirect({ window: zoomItem, columnName: field.columnName, value: field.value })">{{ zoomItem.name }}</el-button>
</template>
<p
class="pre-formatted"
v-html="field.contextInfo.messageText.msgText"
/>
<div class="el-popover__title">
{{ $t('table.ProcessActivity.zoomIn') }}
</div>
<template v-for="(zoomItem, index) in field.reference.zoomWindowList">
<el-button
:key="index"
type="text"
@click="redirect({ window: zoomItem, columnName: field.columnName, value: field.value })"
>
{{ zoomItem.name }}
</el-button>
</template>
</el-popover>
<el-form-item
v-popover:contextOptions
:label="isFieldOnly()"
:required="isMandatory()"
>
<template slot="label">
<span v-popover:contextOptions>
{{ isFieldOnly() }}
</span>
<field-translated
v-if="field.isTranslated && !isAdvancedQuery"
:name="field.name"
:help="field.help"
:container-uuid="containerUuid"
:column-name="field.columnName"
:record-uuid="field.optionCRUD"
:table-name="field.tableName"
/>
</template>
<component
:is="componentRender"
:ref="field.columnName"
:metadata="{
...field,
panelType: panelType,
inTable: inTable,
isAdvancedQuery: isAdvancedQuery,
panelType,
inTable,
isAdvancedQuery,
// DOM properties
required: isMandatory(),
readonly: isReadOnly(),
@ -62,8 +83,8 @@
:class="classField"
:metadata="{
...field,
panelType: panelType,
inTable: inTable,
panelType,
inTable,
// DOM properties
required: isMandatory(),
readonly: isReadOnly(),
@ -74,6 +95,7 @@
</template>
<script>
import FieldTranslated from '@/components/ADempiere/Field/fieldTranslated'
import { FIELD_ONLY } from '@/components/ADempiere/Field/references'
import { DEFAULT_SIZE } from '@/components/ADempiere/Field/fieldSize'
import { fieldIsDisplayed } from '@/utils/ADempiere'
@ -84,6 +106,9 @@ import { fieldIsDisplayed } from '@/utils/ADempiere'
*/
export default {
name: 'Field',
components: {
FieldTranslated
},
props: {
parentUuid: {
type: String,
@ -309,9 +334,19 @@ export default {
}
},
redirect({ window, columnName, value }) {
this.$store.dispatch('getWindowByUuid', { routes: this.permissionRoutes, windowUuid: window.uuid })
var windowRoute = this.$store.getters.getWindowRoute(window.uuid)
this.$router.push({ name: windowRoute.name, query: { action: 'advancedQuery', tabParent: 0, [columnName]: value }})
this.$store.dispatch('getWindowByUuid', {
routes: this.permissionRoutes,
windowUuid: window.uuid
})
const windowRoute = this.$store.getters.getWindowRoute(window.uuid)
this.$router.push({
name: windowRoute.name,
query: {
action: 'advancedQuery',
tabParent: 0,
[columnName]: value
}
})
}
}
}
@ -326,6 +361,13 @@ export default {
margin-left: 10px;
margin-right: 10px;
}
/**
* Reduce the spacing between the form element and its label
*/
.el-form--label-top .el-form-item__label {
padding-bottom: 0px !important;
}
.in-table {
margin-bottom: 0px !important;
margin-left: 0px;

View File

@ -40,7 +40,7 @@
:container-uuid="containerUuid"
:metadata-field="{
...fieldAttributes,
optionCRUD: optionCRUD
optionCRUD
}"
:record-data-fields="isAdvancedQuery ? undefined : dataRecords[fieldAttributes.columnName]"
:panel-type="panelType"
@ -97,7 +97,7 @@
:container-uuid="containerUuid"
:metadata-field="{
...fieldAttributes,
optionCRUD: optionCRUD
optionCRUD
}"
:record-data-fields="isAdvancedQuery ? undefined : dataRecords[fieldAttributes.columnName]"
:panel-type="panelType"
@ -149,7 +149,7 @@
:container-uuid="containerUuid"
:metadata-field="{
...fieldAttributes,
optionCRUD: optionCRUD
optionCRUD
}"
:record-data-fields="isAdvancedQuery ? undefined : dataRecords[fieldAttributes.columnName]"
:panel-type="panelType"
@ -339,7 +339,7 @@ export default {
}).then(() => {
this.generatePanel(this.getterFieldList)
}).catch(error => {
console.warn(`Field Load Error ${error.code}: ${error.message}`)
console.warn(`Field Load Error: ${error.message}. Code: ${error.code}.`)
})
}
},
@ -349,7 +349,7 @@ export default {
if (fieldsList.length) {
this.fieldGroups = this.sortAndGroup(fieldsList)
}
var firstGroup
let firstGroup
if (this.fieldGroups[0] && this.fieldGroups[0].groupFinal === '') {
firstGroup = this.fieldGroups[0]
this.fieldGroups.shift()

View File

@ -1,4 +1,5 @@
export default {
language: 'Language',
route: {
dashboard: 'Dashboard',
calendar: 'Calendar',

View File

@ -1,4 +1,5 @@
export default {
language: 'Idioma',
route: {
dashboard: 'Panel de control',
documentation: 'Documentación',

View File

@ -2,7 +2,7 @@
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<el-button v-if="isMenuMobile && isMobile" type="text" icon="el-icon-close" style="padding-top: 13px; color: #000000;font-size: 121%;font-weight: 615!important;" @click="isMenuOption()" />
<breadcrumb v-show="!isMenuMobile || device!=='mobile'" id="breadcrumb-container" class="breadcrumb-container" :style="isMobile ? { width: '40%'} : { width: 'auto'} " />
<breadcrumb v-show="!isMenuMobile || device!=='mobile'" id="breadcrumb-container" class="breadcrumb-container" :style="isMobile ? { width: '40%' } : { width: 'auto' } " />
<div v-show="isMenuMobile && isMobile" style="display: inline-flex; float: right;">
<search id="header-search" class="right-menu-item" style="padding-top: 10px;" />
<badge style="padding-top: 6px;" />

View File

@ -0,0 +1,209 @@
import {
requestLanguages,
requestTranslations,
updateEntity
} from '@/api/ADempiere/data'
import { isEmptyValue } from '@/utils/ADempiere/valueUtils'
const languageControl = {
state: {
languagesList: [],
translationsList: []
},
mutations: {
setlanguagesList(state, payload) {
state.languagesList = payload
},
// Add data in container
addTranslationToList(state, payload) {
state.translationsList.push(payload)
},
addTranslationToRecord(state, payload) {
payload.translations.push(payload.translationToAdd)
},
setTranslationToRecord(state, payload) {
payload.currentTranslation.values = payload.newValues
},
addTranslationChangeRecord(state, payload) {
payload.currentTranslation = payload.newTranlation
}
},
actions: {
getLanguagesFromServer({ commit }) {
return new Promise((resolve, reject) => {
requestLanguages()
.then(languageResponse => {
commit('setlanguagesList', languageResponse.languagesList)
resolve(languageResponse.languagesList)
})
.catch(error => {
reject(error)
})
})
},
setTranslation({ state, commit }, {
containerUuid,
tableName,
recordUuid,
recordId,
translations
}) {
const currentTranslation = state.translationsList.find(itemTrannslation => {
return itemTrannslation.containerUuid === containerUuid
})
const newTranlation = {
containerUuid,
tableName,
recordUuid,
recordId,
translations
}
if (currentTranslation) {
if (currentTranslation.recordUuid === recordUuid) {
const translationRecord = currentTranslation.translations.find(itemTrannslation => {
return itemTrannslation.language === translations[0].language
})
if (isEmptyValue(translationRecord)) {
commit('addTranslationToRecord', {
translations: currentTranslation.translations,
translationToAdd: translations[0]
})
} else {
// there is translation for the language and change the values in the translation record
commit('setTranslationToRecord', {
currentTranslation: translationRecord,
newValues: translations[0].values
})
}
} else {
// this same container uuid, and other record
commit('addTranslationChangeRecord', {
currentTranslation,
newTranlation
})
}
} else {
// no translation has been uploaded to this container uuid
commit('addTranslationToList', newTranlation)
}
},
getTranslationsFromServer({ dispatch }, {
containerUuid,
language,
tableName,
recordUuid,
recordId
}) {
return requestTranslations({
recordUuid,
recordId,
tableName,
language
})
.then(translationResponse => {
if (translationResponse.translationsList.length < 1) {
console.warn(translationResponse)
return
}
dispatch('setTranslation', {
containerUuid,
tableName,
recordUuid,
recordId,
translations: [{
language,
translationUuid: translationResponse.translationsList[0].translationUuid,
values: translationResponse.translationsList[0].values
}]
})
return translationResponse.translationsList[0].values
})
.catch(error => {
console.warn(`Error Get Translations List ${error.message}. Code: ${error.code}.`)
})
},
changeTranslationValue({ state, commit }, {
containerUuid,
language,
columnName,
value
}) {
return new Promise(resolve => {
const translationData = state.translationsList.find(itemTranslation => {
return itemTranslation.containerUuid === containerUuid
})
const translationSelected = translationData.translations.find(itemTranslation => {
return itemTranslation.language === language
})
const values = translationSelected.values
// not change value
if (values[columnName] === value) {
resolve(value)
return value
}
updateEntity({
tableName: `${translationData.tableName}_Trl`, // '_Trl' is suffix for translation tables
recordUuid: translationSelected.translationUuid,
attributesList: [{
columnName,
value
}]
})
.then(responseEntity => {
const newValues = {}
Object.keys(values).forEach(key => {
newValues[key] = responseEntity.values[key]
})
commit('setTranslationToRecord', {
currentTranslation: translationSelected,
newValues
})
resolve(newValues)
})
.catch(error => {
console.warn(`Error Update Translation ${error.message}. Code: ${error.code}.`)
})
})
}
},
getters: {
getLanguagesList: (state) => {
return state.languagesList
},
getLanguageByParameter: (state) => (parameter) => {
const list = state.languagesList
list.forEach(language => {
if (language.hasOwnProperty(parameter)) {
return language
}
})
},
getTranslationsList: (state) => {
return state.translationsList
},
getTranslationContainer: (state) => (containerUuid) => {
return state.translationsList.find(itemTranslation => itemTranslation.containerUuid === containerUuid)
},
getTranslationByLanguage: (state, getters) => ({
containerUuid,
language,
recordUuid,
recordId
}) => {
const translationContainer = getters.getTranslationContainer(containerUuid)
if (translationContainer && translationContainer.recordUuid === recordUuid) {
const translationRecord = translationContainer.translations.find(itemTranslation => {
return itemTranslation.language === language
})
if (translationRecord) {
return translationRecord.values
}
return {}
}
return {}
}
}
}
export default languageControl

View File

@ -1,5 +1,4 @@
import { requestLanguages } from '@/api/ADempiere'
const utils = {
state: {
width: 0,
@ -12,7 +11,6 @@ const utils = {
reportType: '',
isShowedTable: false,
recordUuidTable: 0,
languageList: [],
isShowedTabChildren: false,
recordTable: 0,
selectionProcess: []
@ -53,9 +51,6 @@ const utils = {
},
setReportTypeToShareLink(state, payload) {
state.reportType = payload
},
setLanguageList(state, payload) {
state.languageList = payload
}
},
actions: {
@ -103,18 +98,6 @@ const utils = {
},
setReportTypeToShareLink({ commit }, value) {
commit('setReportTypeToShareLink', value)
},
getLanguagesFromServer({ commit }) {
return new Promise((resolve, reject) => {
requestLanguages()
.then(languageResponse => {
commit('setLanguageList', languageResponse.languagesList)
resolve(languageResponse.languagesList)
})
.catch(error => {
reject(error)
})
})
}
},
getters: {
@ -165,17 +148,6 @@ const utils = {
},
getReportType: (state) => {
return state.reportType
},
getLanguageList: (state) => {
return state.languageList
},
getLanguageByParameter: (state) => (parameter) => {
var list = state.languageList
list.forEach(language => {
if (language.hasOwnProperty(parameter)) {
return language
}
})
}
}
}

View File

@ -2,7 +2,9 @@
<div class="user-profi">
<router-link to="/profile/index">
<img v-if="logo" :src="logo" class="sidebar-logo">
<p style="float: right;max-width: 150px;text-overflow: ellipsis;white-space: nowrap;overflow: hidden;">{{ getRol.clientName }}</p>
<p style="float: right;max-width: 150px;text-overflow: ellipsis;white-space: nowrap;overflow: hidden;">
{{ getRol.clientName }}
</p>
</router-link>
<roles-navbar />
</div>

View File

@ -18,7 +18,6 @@
</template>
<script>
import { getLanguage } from '@/lang'
import { showMessage } from '@/utils/ADempiere/notification'
import { resetRouter } from '@/router'
@ -27,9 +26,7 @@ export default {
data() {
return {
value: '',
options: [],
languageList: [],
language: getLanguage()
options: []
}
},
computed: {
@ -39,17 +36,6 @@ export default {
getRolesList() {
return this.$store.getters['user/getRoles']
},
languageCookie() {
return getLanguage()
},
getterLanguageList() {
return this.$store.getters.getLanguageList.map(language => {
return {
value: language.languageIso,
label: language.languageName
}
})
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
@ -91,30 +77,10 @@ export default {
})
this.$router.push({ path: '/' })
},
changeLanguage(languageValue) {
this.language = languageValue
},
getLanguageList(open) {
if (open) {
if (this.getterLanguageList.length) {
this.languageList = this.getterLanguageList
} else {
this.getLanguageData()
}
}
},
getLanguageData() {
this.$store.dispatch('getLanguagesFromServer')
.then(response => {
this.languageList = response.map(language => {
return {
value: language.languageIso,
label: language.languageName
}
})
})
.catch(error => {
console.warn('Error getting language list:', error.message + '. Code: ', error.code)
console.warn(`Error getting language list: ${error.message}. Code: ${error.code}.`)
})
}
}

View File

@ -30,7 +30,7 @@
</el-form-item>
<el-form-item :label="$t('profile.changeLanguage')">
<el-select
v-model="language"
v-model="currentLanguage"
:filterable="true"
value-key="key"
:placeholder="$t('profile.changeLanguagePlaceholder')"
@ -38,10 +38,10 @@
@change="changeLanguage"
>
<el-option
v-for="item in languageList"
v-for="item in getterLanguageList"
:key="item.value"
:label="item.label"
:value="item.value"
:label="item.languageName"
:value="item.languageISO"
/>
</el-select>
</el-form-item>
@ -59,8 +59,7 @@ export default {
return {
valueRol: '',
options: [],
languageList: [],
language: getLanguage()
currentLanguage: getLanguage()
}
},
computed: {
@ -70,16 +69,8 @@ export default {
getRolesList() {
return this.$store.getters['user/getRoles']
},
languageCookie() {
return getLanguage()
},
getterLanguageList() {
return this.$store.getters.getLanguageList.map(language => {
return {
value: language.languageIso,
label: language.languageName
}
})
return this.$store.getters.getLanguagesList
},
isMobile() {
return this.$store.state.app.device === 'mobile'
@ -122,30 +113,20 @@ export default {
})
},
changeLanguage(languageValue) {
this.language = languageValue
this.currentLanguage = languageValue
},
getLanguageList(open) {
if (open) {
if (this.getterLanguageList.length) {
this.languageList = this.getterLanguageList
} else {
this.getLanguageData()
}
this.getLanguageData()
}
},
getLanguageData() {
this.$store.dispatch('getLanguagesFromServer')
.then(response => {
this.languageList = response.map(language => {
return {
value: language.languageIso,
label: language.languageName
}
if (this.isEmptyValue(this.getterLanguageList)) {
this.$store.dispatch('getLanguagesFromServer')
.catch(error => {
console.warn(`Error getting languages list: ${error.message}. Code: ${error.code}.`)
})
})
.catch(error => {
console.warn('Error getting language list:', error.message + '. Code: ', error.code)
})
}
}
}
}