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 <tcit@tcit.fr>
This commit is contained in:
parent
8e1082c194
commit
67b906cc96
@ -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
|
||||
|
@ -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<void> {
|
||||
if (
|
||||
errors.length > 0 &&
|
||||
|
@ -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";
|
||||
|
@ -15,7 +15,7 @@
|
||||
"oc": "Occitan",
|
||||
"pl": "Polski",
|
||||
"pt": "Português",
|
||||
"pt_PT": "Português (Portugal)",
|
||||
"pt_BR": "Português brasileiro",
|
||||
"ru": "Русский",
|
||||
"sv": "Svenska"
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<NormalizedCacheObject>, identity: IPerson): Promise<void> {
|
||||
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<any>) {
|
||||
export async function initializeCurrentActor(apollo: ApolloClient<any>): Promise<void> {
|
||||
const actorId = localStorage.getItem(AUTH_USER_ACTOR_ID);
|
||||
|
||||
const result = await apollo.query({
|
||||
@ -59,19 +72,11 @@ export async function initializeCurrentActor(apollo: ApolloClient<any>) {
|
||||
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<NormalizedCacheObject>, identity: IPerson) {
|
||||
await apollo.mutate({
|
||||
mutation: UPDATE_CURRENT_ACTOR_CLIENT,
|
||||
variables: identity,
|
||||
});
|
||||
saveActorData(identity);
|
||||
}
|
||||
|
||||
export async function logout(apollo: ApolloClient<NormalizedCacheObject>) {
|
||||
export async function logout(apollo: ApolloClient<NormalizedCacheObject>): Promise<void> {
|
||||
await apollo.mutate({
|
||||
mutation: UPDATE_CURRENT_USER_CLIENT,
|
||||
variables: {
|
||||
|
@ -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<string> {
|
||||
export async function loadLanguageAsync(lang: string): Promise<string> {
|
||||
// If the same language
|
||||
if (i18n.locale === lang) {
|
||||
return Promise.resolve(setI18nLanguage(lang));
|
||||
@ -63,7 +64,6 @@ async function loadLanguageAsync(lang: string): Promise<string> {
|
||||
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`
|
||||
|
@ -14,7 +14,7 @@
|
||||
<b-field :label="$t('Language')">
|
||||
<b-select
|
||||
:loading="!config || !loggedUser"
|
||||
v-model="$i18n.locale"
|
||||
v-model="locale"
|
||||
:placeholder="$t('Select a language')"
|
||||
>
|
||||
<option v-for="(language, lang) in langs" :value="lang" :key="lang">
|
||||
@ -50,6 +50,7 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Watch } from "vue-property-decorator";
|
||||
import { saveLocaleData } from "@/utils/auth";
|
||||
import { TIMEZONES } from "../../graphql/config";
|
||||
import { USER_SETTINGS, SET_USER_SETTINGS, UPDATE_USER_LOCALE } from "../../graphql/user";
|
||||
import { IConfig } from "../../types/config.model";
|
||||
@ -128,14 +129,17 @@ export default class Preferences extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
@Watch("$i18n.locale")
|
||||
@Watch("locale")
|
||||
async updateLocale(): Promise<void> {
|
||||
await this.$apollo.mutate({
|
||||
mutation: UPDATE_USER_LOCALE,
|
||||
variables: {
|
||||
locale: this.$i18n.locale,
|
||||
},
|
||||
});
|
||||
if (this.locale) {
|
||||
await this.$apollo.mutate({
|
||||
mutation: UPDATE_USER_LOCALE,
|
||||
variables: {
|
||||
locale: this.locale,
|
||||
},
|
||||
});
|
||||
saveLocaleData(this.locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -4,6 +4,26 @@ defmodule Mobilizon.Cldr do
|
||||
"""
|
||||
|
||||
use Cldr,
|
||||
locales: ["cs", "de", "en", "es", "fr", "it", "ja", "nl", "pl", "pt", "ru"],
|
||||
locales: [
|
||||
"ar",
|
||||
"be",
|
||||
"ca",
|
||||
"cs",
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fi",
|
||||
"fr",
|
||||
"gl",
|
||||
"it",
|
||||
"ja",
|
||||
"nl",
|
||||
"oc",
|
||||
"pl",
|
||||
"pt",
|
||||
"ru",
|
||||
"sv"
|
||||
],
|
||||
gettext: Mobilizon.Web.Gettext,
|
||||
providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime, Cldr.Language]
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user