branch bow-v2-go updated (2a5de39 -> ff54968)
This is an automated email from the git hooks/post-receive script. New change to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git from 2a5de39 correction des transactions (on utilisait pas la tx :() ajout de fonction de log new ff54968 ajout du support i18n (ajout du champs lang dans les info du user) The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit ff54968a1aab5ffa5d3cdf6146b4fab7b0121c49 Author: Benjamin <poussin@codelutin.com> Date: Fri Jun 5 00:25:21 2020 +0200 ajout du support i18n (ajout du champs lang dans les info du user) Summary of changes: migrate/004_add_user_lang.sql | 9 +++ pkg/constant/const.go | 4 +- pkg/http/router.go | 5 ++ pkg/http/userResource.go | 24 ++++++++ pkg/model/user.go | 1 + pkg/repository/userRepository.go | 15 ++++- web/package.json | 3 +- web/public/i18n/available.json | 1 + web/public/i18n/fr.json | 3 + web/src/App.vue | 16 +++++- web/src/components/SearchInput.vue | 2 +- web/src/components/preferences/LangEditor.vue | 81 +++++++++++++++++++++++++++ web/src/main.js | 2 + web/src/utils/FetchHelper.js | 18 +++--- web/src/views/Preferences.vue | 4 +- web/yarn.lock | 5 ++ 16 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 migrate/004_add_user_lang.sql create mode 100644 web/public/i18n/available.json create mode 100644 web/public/i18n/fr.json create mode 100644 web/src/components/preferences/LangEditor.vue -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
This is an automated email from the git hooks/post-receive script. New commit to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git commit ff54968a1aab5ffa5d3cdf6146b4fab7b0121c49 Author: Benjamin <poussin@codelutin.com> Date: Fri Jun 5 00:25:21 2020 +0200 ajout du support i18n (ajout du champs lang dans les info du user) --- migrate/004_add_user_lang.sql | 9 +++ pkg/constant/const.go | 4 +- pkg/http/router.go | 5 ++ pkg/http/userResource.go | 24 ++++++++ pkg/model/user.go | 1 + pkg/repository/userRepository.go | 15 ++++- web/package.json | 3 +- web/public/i18n/available.json | 1 + web/public/i18n/fr.json | 3 + web/src/App.vue | 16 +++++- web/src/components/SearchInput.vue | 2 +- web/src/components/preferences/LangEditor.vue | 81 +++++++++++++++++++++++++++ web/src/main.js | 2 + web/src/utils/FetchHelper.js | 18 +++--- web/src/views/Preferences.vue | 4 +- web/yarn.lock | 5 ++ 16 files changed, 178 insertions(+), 15 deletions(-) diff --git a/migrate/004_add_user_lang.sql b/migrate/004_add_user_lang.sql new file mode 100644 index 0000000..a6ac978 --- /dev/null +++ b/migrate/004_add_user_lang.sql @@ -0,0 +1,9 @@ +-- migration des images de bytea en text base64 + +ALTER TABLE bowuser ADD lang TEXT DEFAULT 'en'; +GRANT UPDATE (lang) ON bowUser TO person; + +---- create above / drop below ---- + +REVOKE UPDATE (lang) ON bowUser FROM person; +ALTER TABLE bowuser DROP lang; diff --git a/pkg/constant/const.go b/pkg/constant/const.go index 51ceb3d..2ea47d2 100644 --- a/pkg/constant/const.go +++ b/pkg/constant/const.go @@ -26,9 +26,9 @@ Token le nom utiliser pour le token dans les cookies et les parameters de query const Token = "bow-token" /* -AuthFields la liste des champs necessaire pour l'utilisation du user sur le front et la back' +AuthFields la liste des champs necessaire pour l'utilisation du user sur le front et le back' */ -var AuthFields = []string{"id", "admin", "login", "creationdate", "maxtagincloud", "maxresult", "actions"} +var AuthFields = []string{"id", "admin", "login", "creationdate", "maxtagincloud", "maxresult", "actions", "lang"} /* TokenHeader le nom utiliser pour mettre dans le header de requete http (fallback Authorization) diff --git a/pkg/http/router.go b/pkg/http/router.go index f1299cc..f19d027 100644 --- a/pkg/http/router.go +++ b/pkg/http/router.go @@ -59,6 +59,7 @@ func Start(bowPublicURL string, addr string) { u.HandleFunc("/autofavicon", updateUserAutoFavicon).Methods(http.MethodPut, http.MethodOptions) u.HandleFunc("/maxtagincloud", updateUserMaxTagInCloud).Methods(http.MethodPut, http.MethodOptions) u.HandleFunc("/maxresult", updateUserMaxResult).Methods(http.MethodPut, http.MethodOptions) + u.HandleFunc("/lang", updateLang).Methods(http.MethodPut, http.MethodOptions) s.HandleFunc("/bookmarks", getBookmarks).Methods(http.MethodGet, http.MethodOptions) s.HandleFunc("/bookmarks", addBookmark).Methods(http.MethodPost, http.MethodOptions) @@ -289,6 +290,10 @@ func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // check whether a file exists at the given path _, err = os.Stat(path) if os.IsNotExist(err) { + if strings.HasPrefix(r.URL.Path, "/i18n/") { + http.Error(w, err.Error(), http.StatusNotFound) + return + } // file does not exist, serve index.html http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) return diff --git a/pkg/http/userResource.go b/pkg/http/userResource.go index 5dbe105..8ef2726 100644 --- a/pkg/http/userResource.go +++ b/pkg/http/userResource.go @@ -347,6 +347,30 @@ func updateUserMaxResult(w http.ResponseWriter, r *http.Request) { } } +func updateLang(w http.ResponseWriter, r *http.Request) { + currentUser := r.Context().Value(constant.User).(model.BowUser) + + id := mux.Vars(r)["id"] + + var data map[string]string + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + utils.LogDebug("updateLang", id, data) + + userJSON, err := repository.UpdateLang(currentUser, id, data["lang"]) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + w.Header().Add("Content-Type", "application/json") + io.WriteString(w, userJSON) +} + func confirmUserEmail(w http.ResponseWriter, r *http.Request) { currentUser := r.Context().Value(constant.User).(model.BowUser) diff --git a/pkg/model/user.go b/pkg/model/user.go index 41e913b..f130bb2 100644 --- a/pkg/model/user.go +++ b/pkg/model/user.go @@ -26,6 +26,7 @@ type BowUser struct { MaxTagInCloud int16 `json:"maxtagincloud,omitempty"` MaxResult int16 `json:"maxresult,omitempty"` Actions []Action `json:"actions,omitempty"` + Lang string `json:"lang,omitempty"` } type Token struct { diff --git a/pkg/repository/userRepository.go b/pkg/repository/userRepository.go index cf5a448..daec7d0 100644 --- a/pkg/repository/userRepository.go +++ b/pkg/repository/userRepository.go @@ -23,7 +23,7 @@ all field are send except: func UserJSON(currentUser model.BowUser, id string, fields ...string) (string, error) { var askedFields string if len(fields) == 0 { - askedFields = "id, creationdate, updatedate, admin, login, tokens, emails, unconfirmedemails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions" + askedFields = "id, creationdate, updatedate, admin, login, tokens, emails, unconfirmedemails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions, lang" } else { askedFields = strings.Join(fields, ", ") } @@ -68,7 +68,7 @@ func User(currentUser model.BowUser, id string, fields ...string) (model.BowUser } /* -UserIDFromToken get user id by application token +UserFromToken get user id by application token */ func UserFromToken(token string, fields ...string) (model.BowUser, error) { currentUser := constant.Nobody @@ -312,6 +312,17 @@ func UpdateUserMaxResult(currentUser model.BowUser, id string, value int8) error return err } +/* +UpdateLang met a jour la lang de l'utilisateur +*/ +func UpdateLang(currentUser model.BowUser, id string, value string) (string, error) { + fields := strings.Join(constant.AuthFields, ",") + q := &query{sql: fmt.Sprintf(`WITH __all AS (UPDATE bowuser SET lang=$2 WHERE id=$1 RETURNING %s) SELECT json_agg(__all)->0 AS json FROM __all;`, fields)} + userJSON, err := q.QueryString(currentUser, id, value) + + return userJSON, err +} + /* ConfirmUserEmail verif et confirme un email */ diff --git a/web/package.json b/web/package.json index 2f1b7c5..ab3cdc6 100644 --- a/web/package.json +++ b/web/package.json @@ -15,7 +15,8 @@ "vue-property-decorator": "^8.4.1", "vue-router": "^3.1.6", "vue-select": "^3.9.5", - "vuex": "^3.4.0" + "vuex": "^3.4.0", + "vuex-i18n": "^1.13.1" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.3.0", diff --git a/web/public/i18n/available.json b/web/public/i18n/available.json new file mode 100644 index 0000000..59ed88f --- /dev/null +++ b/web/public/i18n/available.json @@ -0,0 +1 @@ +["en", "fr"] \ No newline at end of file diff --git a/web/public/i18n/fr.json b/web/public/i18n/fr.json new file mode 100644 index 0000000..d185b57 --- /dev/null +++ b/web/public/i18n/fr.json @@ -0,0 +1,3 @@ +{ + "ex: dad or mum -\"little children\"": "ex: papa or maman -\"petits enfants\"" +} diff --git a/web/src/App.vue b/web/src/App.vue index 0ab65db..763f104 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -42,9 +42,23 @@ class App extends Vue { } reload() { - this.$store.commit('user', this.$storage.get('bow-user') || {}) + let user = this.$storage.get('bow-user') || {} + this.$store.commit('user', user) this.$store.commit('authenticated', !!this.$storage.getCookie('bow-token')) this.insertOpenSearch() + if (user.lang && user.lang != this.$i18n.locale()) { + console.log('loading lang...', user.lang) + this.$fetch.i18n(user.lang).then( + data => { + console.log('lang loaded', user.lang) + this.$i18n.add(user.lang, data) + this.$i18n.set(user.lang) + }, + err => { + this.errorMsg = err.cause + } + ) + } } beforeMount() { diff --git a/web/src/components/SearchInput.vue b/web/src/components/SearchInput.vue index 87edf6c..8c5d9e9 100644 --- a/web/src/components/SearchInput.vue +++ b/web/src/components/SearchInput.vue @@ -5,7 +5,7 @@ <TagsInput id="tags" v-model="tags" :availableTags="queryTags" :noCreation="true"></TagsInput> </div> <div> - <label for="fulltext" title='ex: papa or maman -"petits enfants"'>fulltext</label> + <label for="fulltext" :title='$t(`ex: dad or mum -"little children"`)'>fulltext</label> <input id="fulltext" type="text" v-model="fulltext" @keyup.enter="searchFulltext(mFulltext)" /> </div> <form :action="$fetch.createUrl('/opensearch')"> diff --git a/web/src/components/preferences/LangEditor.vue b/web/src/components/preferences/LangEditor.vue new file mode 100644 index 0000000..2ea7de1 --- /dev/null +++ b/web/src/components/preferences/LangEditor.vue @@ -0,0 +1,81 @@ +<template> + <div class="lang-editor"> + <label for="lang">Max result</label> + <select v-model="lang"> + <option v-for="l in availableLang" :key="l">{{ l }}</option> + </select> + <span class="errorMsg">{{ errorMsg }}</span> + </div> +</template> + +<script> +import { Component, Vue } from 'vue-property-decorator' + +@Component({ + name: 'LangEditor', + components: {} +}) +class LangEditor extends Vue { + errorMsg = '' + + availableLang = [] + + timeoutSave = 0 + + cancelTimeoutSave() { + this.timeoutSave && clearTimeout(this.timeoutSave) + this.timeoutSave = 0 + } + + get lang() { + return this.user.lang + } + + set lang(value) { + this.cancelTimeoutSave() + this.timeoutSave = setTimeout( + function(v) { + this.$fetch.put('/users/current/lang', { lang: v }).then( + (user) => { + this.saveUser(user) + + this.errorMsg = 'lang change to ' + v + }, + (err) => { + console.log('ko', err) + this.errorMsg = err.cause + } + ) + }.bind(this), + 800, + value + ) + } + + loadAvailableLang() { + this.$fetch.i18n('available').then( + (data) => { + console.log('LLLLL available', typeof(data), data) + this.availableLang = data + }, + (err) => { + this.errorMsg = err.cause + } + ) + } + + beforeMount() { + console.log('beforeMounted LangEditor') + this.loadAvailableLang() + } +} + +export default LangEditor +</script> + +<style scoped lang="less"> +.lang-editor { + display: flex; + flex-direction: row; +} +</style> diff --git a/web/src/main.js b/web/src/main.js index 8b2e15b..0d5e161 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -8,6 +8,7 @@ import store from './store' import FetchHelper from '@/utils/FetchHelper.js' import StoreHelper from './utils/Store' import VueDOMPurifyHTML from 'vue-dompurify-html' +import vuexI18n from 'vuex-i18n' window.BACKEND_URL = process.env.VUE_APP_BACKEND_URL window.FRONTEND_URL = process.env.BASE_URL @@ -19,6 +20,7 @@ if (typeof window !== 'undefined') { } Vue.use(VueDOMPurifyHTML) +Vue.use(vuexI18n.plugin, store) Vue.$fetch = FetchHelper Vue.$storage = StoreHelper diff --git a/web/src/utils/FetchHelper.js b/web/src/utils/FetchHelper.js index 824aacd..1f82ce9 100644 --- a/web/src/utils/FetchHelper.js +++ b/web/src/utils/FetchHelper.js @@ -9,7 +9,7 @@ let FetchHelper = { headers['Content-Type'] = 'application/json' } - return fetch(this.createUrl(url), { + return fetch(url, { headers, method, credentials: 'include', // en prod a priori le back et le front auront la meme origine donc pas besoin @@ -22,7 +22,7 @@ let FetchHelper = { } if (response.status === 200 || response.status === 201) { - if ((response.headers.get('Content-Type') || '').toLowerCase() === 'application/json') { + if ((response.headers.get('Content-Type') || '').toLowerCase().startsWith('application/json')) { return response.json() } else if (parseInt(response.headers.get('Content-Length')) > 0) { return response.text() @@ -46,24 +46,28 @@ let FetchHelper = { }) }, + i18n(lang) { + return this.fetch(`/i18n/${lang}.json`, 'GET') + }, + get(url) { - return this.fetch(url, 'GET') + return this.fetch(this.createUrl(url), 'GET') }, post(url, body) { - return this.fetch(url, 'POST', {}, body) + return this.fetch(this.createUrl(url), 'POST', {}, body) }, put(url, body) { - return this.fetch(url, 'PUT', {}, body) + return this.fetch(this.createUrl(url), 'PUT', {}, body) }, patch(url, body) { - return this.fetch(url, 'PATCH', {}, body) + return this.fetch(this.createUrl(url), 'PATCH', {}, body) }, delete(url) { - return this.fetch(url, 'DELETE') + return this.fetch(this.createUrl(url), 'DELETE') } } diff --git a/web/src/views/Preferences.vue b/web/src/views/Preferences.vue index 3939760..b891a53 100644 --- a/web/src/views/Preferences.vue +++ b/web/src/views/Preferences.vue @@ -2,6 +2,7 @@ <div class="preferences"> <div>{{ errorMsg }}</div> <a :href="bookmarkletAdd">bookmarklet add</a> Drag and drop this link to your toolbar browser. To bookmark page, select some text in page, and click on it, in your toolbar browser. + <LangEditor></LangEditor> <MaxResultEditor></MaxResultEditor> <MaxTagInCloudEditor></MaxTagInCloudEditor> <Actions :actions="user.actions"></Actions> @@ -12,6 +13,7 @@ <script> // @ is an alias to /src import { Component, Vue } from 'vue-property-decorator' +import LangEditor from '@/components/preferences/LangEditor' import MaxResultEditor from '@/components/preferences/MaxResultEditor' import MaxTagInCloudEditor from '@/components/preferences/MaxTagInCloudEditor' import Actions from '@/components/preferences/Actions' @@ -19,7 +21,7 @@ import TagsEditor from '@/components/preferences/TagsEditor' @Component({ name: 'Preferences', - components: {MaxTagInCloudEditor, MaxResultEditor, Actions, TagsEditor} + components: {LangEditor, MaxTagInCloudEditor, MaxResultEditor, Actions, TagsEditor} }) class Preferences extends Vue { errorMsg = '' diff --git a/web/yarn.lock b/web/yarn.lock index ee8d634..7b0a070 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -8041,6 +8041,11 @@ vue@^2.6.10, vue@^2.6.11: resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35..." integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ== +vuex-i18n@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/vuex-i18n/-/vuex-i18n-1.13.1.tgz#f9f6bf5de44f85..." + integrity sha512-VTy5QAyMI6BJwpRfN5qncWQT0ohKiAYK+iTRW4JxgV9dkNoPMuKKDqExbOm1fzpitdrIoIipC3Zqr5fJ706VQg== + vuex@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.4.0.tgz#20cc086062d750769fce1febb..." -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
participants (1)
-
chorem.org scm