Merge branch 'fix-language-detection' into 'master'
Fix language change Closes #405 et #400 See merge request framasoft/mobilizon!667
This commit is contained in:
commit
ed00d98ed8
|
@ -94,15 +94,11 @@ config :mobilizon, Mobilizon.Web.Email.Mailer,
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
# usually 25, 465 or 587
|
# usually 25, 465 or 587
|
||||||
port: 25,
|
port: 25,
|
||||||
# or {:system, "SMTP_USERNAME"}
|
|
||||||
username: nil,
|
username: nil,
|
||||||
# or {:system, "SMTP_PASSWORD"}
|
|
||||||
password: nil,
|
password: nil,
|
||||||
# can be `:always` or `:never`
|
# can be `:always` or `:never`
|
||||||
tls: :if_available,
|
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"],
|
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
|
||||||
# can be `true`
|
|
||||||
retries: 1,
|
retries: 1,
|
||||||
# can be `true`
|
# can be `true`
|
||||||
no_mx_lookups: false
|
no_mx_lookups: false
|
||||||
|
|
|
@ -108,13 +108,14 @@
|
||||||
import { Component, Vue, Watch } from "vue-property-decorator";
|
import { Component, Vue, Watch } from "vue-property-decorator";
|
||||||
import Logo from "@/components/Logo.vue";
|
import Logo from "@/components/Logo.vue";
|
||||||
import { GraphQLError } from "graphql";
|
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 { changeIdentity, logout } from "../utils/auth";
|
||||||
import { CURRENT_ACTOR_CLIENT, IDENTITIES, UPDATE_DEFAULT_ACTOR } from "../graphql/actor";
|
import { CURRENT_ACTOR_CLIENT, IDENTITIES, UPDATE_DEFAULT_ACTOR } from "../graphql/actor";
|
||||||
import { IPerson, Person } from "../types/actor";
|
import { IPerson, Person } from "../types/actor";
|
||||||
import { CONFIG } from "../graphql/config";
|
import { CONFIG } from "../graphql/config";
|
||||||
import { IConfig } from "../types/config.model";
|
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 SearchField from "./SearchField.vue";
|
||||||
import RouteName from "../router/name";
|
import RouteName from "../router/name";
|
||||||
|
|
||||||
|
@ -138,6 +139,12 @@ import RouteName from "../router/name";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
config: CONFIG,
|
config: CONFIG,
|
||||||
|
loggedUser: {
|
||||||
|
query: USER_SETTINGS,
|
||||||
|
skip() {
|
||||||
|
return this.currentUser.isLoggedIn === false;
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Logo,
|
Logo,
|
||||||
|
@ -151,6 +158,8 @@ export default class NavBar extends Vue {
|
||||||
|
|
||||||
currentUser!: ICurrentUser;
|
currentUser!: ICurrentUser;
|
||||||
|
|
||||||
|
loggedUser!: IUser;
|
||||||
|
|
||||||
ICurrentUserRole = ICurrentUserRole;
|
ICurrentUserRole = ICurrentUserRole;
|
||||||
|
|
||||||
identities: IPerson[] = [];
|
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> {
|
async handleErrors(errors: GraphQLError[]): Promise<void> {
|
||||||
if (
|
if (
|
||||||
errors.length > 0 &&
|
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_EMAIL = "auth-user-email";
|
||||||
export const AUTH_USER_ACTOR_ID = "auth-user-actor-id";
|
export const AUTH_USER_ACTOR_ID = "auth-user-actor-id";
|
||||||
export const AUTH_USER_ROLE = "auth-user-role";
|
export const AUTH_USER_ROLE = "auth-user-role";
|
||||||
|
export const USER_LOCALE = "user-locale";
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"oc": "Occitan",
|
"oc": "Occitan",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"pt_PT": "Português (Portugal)",
|
"pt_BR": "Português brasileiro",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sv": "Svenska"
|
"sv": "Svenska"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,21 @@ export interface ICurrentUser {
|
||||||
defaultActor?: IPerson;
|
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 {
|
export interface IUser extends ICurrentUser {
|
||||||
confirmedAt: Date;
|
confirmedAt: Date;
|
||||||
confirmationSendAt: Date;
|
confirmationSendAt: Date;
|
||||||
|
@ -42,18 +57,3 @@ export enum IAuthProvider {
|
||||||
GITLAB = "gitlab",
|
GITLAB = "gitlab",
|
||||||
TWITTER = "twitter",
|
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_EMAIL,
|
||||||
AUTH_USER_ID,
|
AUTH_USER_ID,
|
||||||
AUTH_USER_ROLE,
|
AUTH_USER_ROLE,
|
||||||
|
USER_LOCALE,
|
||||||
} from "@/constants";
|
} from "@/constants";
|
||||||
import { ILogin, IToken } from "@/types/login.model";
|
import { ILogin, IToken } from "@/types/login.model";
|
||||||
import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user";
|
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 { IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||||
import { NormalizedCacheObject } from "apollo-cache-inmemory";
|
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_ID, `${obj.user.id}`);
|
||||||
localStorage.setItem(AUTH_USER_EMAIL, obj.user.email);
|
localStorage.setItem(AUTH_USER_EMAIL, obj.user.email);
|
||||||
localStorage.setItem(AUTH_USER_ROLE, obj.user.role);
|
localStorage.setItem(AUTH_USER_ROLE, obj.user.role);
|
||||||
|
@ -22,29 +28,36 @@ export function saveUserData(obj: ILogin) {
|
||||||
saveTokenData(obj);
|
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}`);
|
localStorage.setItem(AUTH_USER_ACTOR_ID, `${obj.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveTokenData(obj: IToken) {
|
export function deleteUserData(): void {
|
||||||
localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken);
|
[AUTH_USER_ID, AUTH_USER_EMAIL, AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_ROLE].forEach((key) => {
|
||||||
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]) {
|
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NoIdentitiesException extends Error {}
|
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,
|
* We fetch from localStorage the latest actor ID used,
|
||||||
* then fetch the current identities to set in cache
|
* then fetch the current identities to set in cache
|
||||||
* the current identity used
|
* 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 actorId = localStorage.getItem(AUTH_USER_ACTOR_ID);
|
||||||
|
|
||||||
const result = await apollo.query({
|
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);
|
const activeIdentity = identities.find((identity: IPerson) => identity.id === actorId) || (identities[0] as IPerson);
|
||||||
|
|
||||||
if (activeIdentity) {
|
if (activeIdentity) {
|
||||||
return await changeIdentity(apollo, activeIdentity);
|
await changeIdentity(apollo, activeIdentity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function changeIdentity(apollo: ApolloClient<NormalizedCacheObject>, identity: IPerson) {
|
export async function logout(apollo: ApolloClient<NormalizedCacheObject>): Promise<void> {
|
||||||
await apollo.mutate({
|
|
||||||
mutation: UPDATE_CURRENT_ACTOR_CLIENT,
|
|
||||||
variables: identity,
|
|
||||||
});
|
|
||||||
saveActorData(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function logout(apollo: ApolloClient<NormalizedCacheObject>) {
|
|
||||||
await apollo.mutate({
|
await apollo.mutate({
|
||||||
mutation: UPDATE_CURRENT_USER_CLIENT,
|
mutation: UPDATE_CURRENT_USER_CLIENT,
|
||||||
variables: {
|
variables: {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import VueI18n from "vue-i18n";
|
import VueI18n from "vue-i18n";
|
||||||
import { DateFnsPlugin } from "@/plugins/dateFns";
|
import { DateFnsPlugin } from "@/plugins/dateFns";
|
||||||
|
import { USER_LOCALE } from "@/constants";
|
||||||
import en from "../i18n/en_US.json";
|
import en from "../i18n/en_US.json";
|
||||||
import langs from "../i18n/langs.json";
|
import langs from "../i18n/langs.json";
|
||||||
|
|
||||||
const DEFAULT_LOCALE = "en_US";
|
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(/-/, "_");
|
language = language || ((window.navigator as any).userLanguage || window.navigator.language).replace(/-/, "_");
|
||||||
export const locale =
|
export const locale =
|
||||||
language && Object.prototype.hasOwnProperty.call(langs, language) ? language : language.split("-")[0];
|
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) });
|
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 the same language
|
||||||
if (i18n.locale === lang) {
|
if (i18n.locale === lang) {
|
||||||
return Promise.resolve(setI18nLanguage(lang));
|
return Promise.resolve(setI18nLanguage(lang));
|
||||||
|
@ -63,7 +64,6 @@ async function loadLanguageAsync(lang: string): Promise<string> {
|
||||||
if (loadedLanguages.includes(lang)) {
|
if (loadedLanguages.includes(lang)) {
|
||||||
return Promise.resolve(setI18nLanguage(lang));
|
return Promise.resolve(setI18nLanguage(lang));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the language hasn't been loaded yet
|
// If the language hasn't been loaded yet
|
||||||
const newMessages = await import(
|
const newMessages = await import(
|
||||||
/* webpackChunkName: "lang-[request]" */ `@/i18n/${vueI18NfileForLanguage(lang)}.json`
|
/* webpackChunkName: "lang-[request]" */ `@/i18n/${vueI18NfileForLanguage(lang)}.json`
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<b-field :label="$t('Language')">
|
<b-field :label="$t('Language')">
|
||||||
<b-select
|
<b-select
|
||||||
:loading="!config || !loggedUser"
|
:loading="!config || !loggedUser"
|
||||||
v-model="$i18n.locale"
|
v-model="locale"
|
||||||
:placeholder="$t('Select a language')"
|
:placeholder="$t('Select a language')"
|
||||||
>
|
>
|
||||||
<option v-for="(language, lang) in langs" :value="lang" :key="lang">
|
<option v-for="(language, lang) in langs" :value="lang" :key="lang">
|
||||||
|
@ -50,6 +50,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue, Watch } from "vue-property-decorator";
|
import { Component, Vue, Watch } from "vue-property-decorator";
|
||||||
|
import { saveLocaleData } from "@/utils/auth";
|
||||||
import { TIMEZONES } from "../../graphql/config";
|
import { TIMEZONES } from "../../graphql/config";
|
||||||
import { USER_SETTINGS, SET_USER_SETTINGS, UPDATE_USER_LOCALE } from "../../graphql/user";
|
import { USER_SETTINGS, SET_USER_SETTINGS, UPDATE_USER_LOCALE } from "../../graphql/user";
|
||||||
import { IConfig } from "../../types/config.model";
|
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> {
|
async updateLocale(): Promise<void> {
|
||||||
|
if (this.locale) {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: UPDATE_USER_LOCALE,
|
mutation: UPDATE_USER_LOCALE,
|
||||||
variables: {
|
variables: {
|
||||||
locale: this.$i18n.locale,
|
locale: this.locale,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
saveLocaleData(this.locale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,6 +4,26 @@ defmodule Mobilizon.Cldr do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Cldr,
|
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]
|
providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime, Cldr.Language]
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue