From 67b906cc965381287654bbddd047a2c4e14dcd25 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 28 Oct 2020 18:58:43 +0100 Subject: [PATCH] Fix language change - Load the language files correctly when language is changed - Save user language in localstorage so that we can have it even if disconnected (but still load it from user settings eventually since user might be on a different device) - Load all locales from Cldr with Gettext - Fix pt-PT -> pt-BR - Clean some obsolete config.exs comments Later changes will allow to set the language without an account https://framagit.org/framasoft/mobilizon/-/issues/375 Signed-off-by: Thomas Citharel --- config/config.exs | 4 --- js/src/components/NavBar.vue | 20 ++++++++++-- js/src/constants.ts | 1 + js/src/i18n/langs.json | 2 +- js/src/types/current-user.model.ts | 30 ++++++++--------- js/src/utils/auth.ts | 47 +++++++++++++++------------ js/src/utils/i18n.ts | 6 ++-- js/src/views/Settings/Preferences.vue | 20 +++++++----- lib/mobilizon/cldr.ex | 22 ++++++++++++- 9 files changed, 97 insertions(+), 55 deletions(-) diff --git a/config/config.exs b/config/config.exs index d609a9194..9771bfdf4 100644 --- a/config/config.exs +++ b/config/config.exs @@ -94,15 +94,11 @@ config :mobilizon, Mobilizon.Web.Email.Mailer, hostname: "localhost", # usually 25, 465 or 587 port: 25, - # or {:system, "SMTP_USERNAME"} username: nil, - # or {:system, "SMTP_PASSWORD"} password: nil, # can be `:always` or `:never` tls: :if_available, - # or {":system", ALLOWED_TLS_VERSIONS"} w/ comma seprated values (e.g. "tlsv1.1,tlsv1.2") allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], - # can be `true` retries: 1, # can be `true` no_mx_lookups: false diff --git a/js/src/components/NavBar.vue b/js/src/components/NavBar.vue index 04e6638be..5af8b3f00 100644 --- a/js/src/components/NavBar.vue +++ b/js/src/components/NavBar.vue @@ -108,13 +108,14 @@ import { Component, Vue, Watch } from "vue-property-decorator"; import Logo from "@/components/Logo.vue"; import { GraphQLError } from "graphql"; -import { CURRENT_USER_CLIENT } from "../graphql/user"; +import { loadLanguageAsync } from "@/utils/i18n"; +import { CURRENT_USER_CLIENT, USER_SETTINGS } from "../graphql/user"; import { changeIdentity, logout } from "../utils/auth"; import { CURRENT_ACTOR_CLIENT, IDENTITIES, UPDATE_DEFAULT_ACTOR } from "../graphql/actor"; import { IPerson, Person } from "../types/actor"; import { CONFIG } from "../graphql/config"; import { IConfig } from "../types/config.model"; -import { ICurrentUser, ICurrentUserRole } from "../types/current-user.model"; +import { ICurrentUser, ICurrentUserRole, IUser } from "../types/current-user.model"; import SearchField from "./SearchField.vue"; import RouteName from "../router/name"; @@ -138,6 +139,12 @@ import RouteName from "../router/name"; }, }, config: CONFIG, + loggedUser: { + query: USER_SETTINGS, + skip() { + return this.currentUser.isLoggedIn === false; + }, + }, }, components: { Logo, @@ -151,6 +158,8 @@ export default class NavBar extends Vue { currentUser!: ICurrentUser; + loggedUser!: IUser; + ICurrentUserRole = ICurrentUserRole; identities: IPerson[] = []; @@ -182,6 +191,13 @@ export default class NavBar extends Vue { } } + @Watch("loggedUser") + setSavedLanguage(): void { + if (this.loggedUser?.locale) { + loadLanguageAsync(this.loggedUser.locale); + } + } + async handleErrors(errors: GraphQLError[]): Promise { if ( errors.length > 0 && diff --git a/js/src/constants.ts b/js/src/constants.ts index 94dc781b9..b839c31cf 100644 --- a/js/src/constants.ts +++ b/js/src/constants.ts @@ -4,3 +4,4 @@ export const AUTH_USER_ID = "auth-user-id"; export const AUTH_USER_EMAIL = "auth-user-email"; export const AUTH_USER_ACTOR_ID = "auth-user-actor-id"; export const AUTH_USER_ROLE = "auth-user-role"; +export const USER_LOCALE = "user-locale"; diff --git a/js/src/i18n/langs.json b/js/src/i18n/langs.json index a7c51b6fa..6b567ef9f 100644 --- a/js/src/i18n/langs.json +++ b/js/src/i18n/langs.json @@ -15,7 +15,7 @@ "oc": "Occitan", "pl": "Polski", "pt": "Português", - "pt_PT": "Português (Portugal)", + "pt_BR": "Português brasileiro", "ru": "Русский", "sv": "Svenska" } diff --git a/js/src/types/current-user.model.ts b/js/src/types/current-user.model.ts index f8f028e24..874e20d11 100644 --- a/js/src/types/current-user.model.ts +++ b/js/src/types/current-user.model.ts @@ -16,6 +16,21 @@ export interface ICurrentUser { defaultActor?: IPerson; } +export enum INotificationPendingParticipationEnum { + NONE = "NONE", + DIRECT = "DIRECT", + ONE_DAY = "ONE_DAY", + ONE_HOUR = "ONE_HOUR", +} + +export interface IUserSettings { + timezone: string; + notificationOnDay: boolean; + notificationEachWeek: boolean; + notificationBeforeEvent: boolean; + notificationPendingParticipation: INotificationPendingParticipationEnum; +} + export interface IUser extends ICurrentUser { confirmedAt: Date; confirmationSendAt: Date; @@ -42,18 +57,3 @@ export enum IAuthProvider { GITLAB = "gitlab", TWITTER = "twitter", } - -export enum INotificationPendingParticipationEnum { - NONE = "NONE", - DIRECT = "DIRECT", - ONE_DAY = "ONE_DAY", - ONE_HOUR = "ONE_HOUR", -} - -export interface IUserSettings { - timezone: string; - notificationOnDay: boolean; - notificationEachWeek: boolean; - notificationBeforeEvent: boolean; - notificationPendingParticipation: INotificationPendingParticipationEnum; -} diff --git a/js/src/utils/auth.ts b/js/src/utils/auth.ts index f18f6104a..dbf56b057 100644 --- a/js/src/utils/auth.ts +++ b/js/src/utils/auth.ts @@ -5,6 +5,7 @@ import { AUTH_USER_EMAIL, AUTH_USER_ID, AUTH_USER_ROLE, + USER_LOCALE, } from "@/constants"; import { ILogin, IToken } from "@/types/login.model"; import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user"; @@ -14,7 +15,12 @@ import { IPerson } from "@/types/actor"; import { IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; import { NormalizedCacheObject } from "apollo-cache-inmemory"; -export function saveUserData(obj: ILogin) { +export function saveTokenData(obj: IToken): void { + localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken); + localStorage.setItem(AUTH_REFRESH_TOKEN, obj.refreshToken); +} + +export function saveUserData(obj: ILogin): void { localStorage.setItem(AUTH_USER_ID, `${obj.user.id}`); localStorage.setItem(AUTH_USER_EMAIL, obj.user.email); localStorage.setItem(AUTH_USER_ROLE, obj.user.role); @@ -22,29 +28,36 @@ export function saveUserData(obj: ILogin) { saveTokenData(obj); } -export function saveActorData(obj: IPerson) { +export function saveLocaleData(locale: string): void { + localStorage.setItem(USER_LOCALE, locale); +} + +export function saveActorData(obj: IPerson): void { localStorage.setItem(AUTH_USER_ACTOR_ID, `${obj.id}`); } -export function saveTokenData(obj: IToken) { - localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken); - localStorage.setItem(AUTH_REFRESH_TOKEN, obj.refreshToken); -} - -export function deleteUserData() { - for (const key of [AUTH_USER_ID, AUTH_USER_EMAIL, AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_ROLE]) { +export function deleteUserData(): void { + [AUTH_USER_ID, AUTH_USER_EMAIL, AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_ROLE].forEach((key) => { localStorage.removeItem(key); - } + }); } export class NoIdentitiesException extends Error {} +export async function changeIdentity(apollo: ApolloClient, identity: IPerson): Promise { + await apollo.mutate({ + mutation: UPDATE_CURRENT_ACTOR_CLIENT, + variables: identity, + }); + saveActorData(identity); +} + /** * We fetch from localStorage the latest actor ID used, * then fetch the current identities to set in cache * the current identity used */ -export async function initializeCurrentActor(apollo: ApolloClient) { +export async function initializeCurrentActor(apollo: ApolloClient): Promise { const actorId = localStorage.getItem(AUTH_USER_ACTOR_ID); const result = await apollo.query({ @@ -59,19 +72,11 @@ export async function initializeCurrentActor(apollo: ApolloClient) { const activeIdentity = identities.find((identity: IPerson) => identity.id === actorId) || (identities[0] as IPerson); if (activeIdentity) { - return await changeIdentity(apollo, activeIdentity); + await changeIdentity(apollo, activeIdentity); } } -export async function changeIdentity(apollo: ApolloClient, identity: IPerson) { - await apollo.mutate({ - mutation: UPDATE_CURRENT_ACTOR_CLIENT, - variables: identity, - }); - saveActorData(identity); -} - -export async function logout(apollo: ApolloClient) { +export async function logout(apollo: ApolloClient): Promise { await apollo.mutate({ mutation: UPDATE_CURRENT_USER_CLIENT, variables: { diff --git a/js/src/utils/i18n.ts b/js/src/utils/i18n.ts index cd28c6953..a5605ab4a 100644 --- a/js/src/utils/i18n.ts +++ b/js/src/utils/i18n.ts @@ -1,12 +1,13 @@ import Vue from "vue"; import VueI18n from "vue-i18n"; import { DateFnsPlugin } from "@/plugins/dateFns"; +import { USER_LOCALE } from "@/constants"; import en from "../i18n/en_US.json"; import langs from "../i18n/langs.json"; const DEFAULT_LOCALE = "en_US"; -let language = document.documentElement.getAttribute("lang") as string; +let language = localStorage.getItem(USER_LOCALE) || (document.documentElement.getAttribute("lang") as string); language = language || ((window.navigator as any).userLanguage || window.navigator.language).replace(/-/, "_"); export const locale = language && Object.prototype.hasOwnProperty.call(langs, language) ? language : language.split("-")[0]; @@ -53,7 +54,7 @@ function dateFnsfileForLanguage(lang: string) { Vue.use(DateFnsPlugin, { locale: dateFnsfileForLanguage(locale) }); -async function loadLanguageAsync(lang: string): Promise { +export async function loadLanguageAsync(lang: string): Promise { // If the same language if (i18n.locale === lang) { return Promise.resolve(setI18nLanguage(lang)); @@ -63,7 +64,6 @@ async function loadLanguageAsync(lang: string): Promise { if (loadedLanguages.includes(lang)) { return Promise.resolve(setI18nLanguage(lang)); } - // If the language hasn't been loaded yet const newMessages = await import( /* webpackChunkName: "lang-[request]" */ `@/i18n/${vueI18NfileForLanguage(lang)}.json` diff --git a/js/src/views/Settings/Preferences.vue b/js/src/views/Settings/Preferences.vue index 876daa270..d926f6bba 100644 --- a/js/src/views/Settings/Preferences.vue +++ b/js/src/views/Settings/Preferences.vue @@ -14,7 +14,7 @@