From 857f9cd8db05433805f14e1b77b1daad601ddbd5 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Wed, 31 Aug 2022 22:33:19 +0200 Subject: [PATCH] Make language settings accessible through GraphQL and set locale directly in Vue --- aleksis/core/assets/app.js | 18 ++-- .../core/assets/components/LanguageForm.vue | 99 +++++++++---------- .../components/availableLanguages.graphql | 7 ++ aleksis/core/schema.py | 26 +++++ aleksis/core/templates/core/vue_base.html | 5 +- aleksis/core/util/frontend_helpers.py | 17 ++++ 6 files changed, 107 insertions(+), 65 deletions(-) create mode 100644 aleksis/core/assets/components/availableLanguages.graphql diff --git a/aleksis/core/assets/app.js b/aleksis/core/assets/app.js index 31555d16d..43ec6d3d5 100644 --- a/aleksis/core/assets/app.js +++ b/aleksis/core/assets/app.js @@ -5,6 +5,7 @@ import "vuetify/dist/vuetify.min.css" import ApolloClient from 'apollo-boost' import VueApollo from 'vue-apollo' +import gql from 'graphql-tag' import "./css/global.scss" import VueI18n from 'vue-i18n' @@ -14,9 +15,8 @@ import messages from "./messages.json" Vue.use(VueI18n) const i18n = new VueI18n({ - locale: JSON.parse(document.getElementById("current-language").textContent), + locale: "en", fallbackLocale: "en", - availableLocales: JSON.parse(document.getElementById("language-info-list").textContent), messages }); @@ -64,10 +64,6 @@ const vuetify = new Vuetify({ }, }, }, - lang: { - locales: JSON.parse(document.getElementById("language-info-list").textContent), - current: JSON.parse(document.getElementById("current-language").textContent), - } }) const apolloClient = new ApolloClient({ @@ -107,8 +103,16 @@ const app = new Vue({ django: window.django, // FIXME: maybe just use window.django in every component or find a suitable way to access this property everywhere showCacheAlert: false, - languageCode: JSON.parse(document.getElementById("current-language").textContent), }), + apollo: { + language: gql`{ language }`, + }, + watch: { + language: function (newLanguage) { + this.$i18n.locale = newLanguage; + this.$vuetify.lang.current = newLanguage; + } + }, components: { "cache-notification": CacheNotification, "language-form": LanguageForm, diff --git a/aleksis/core/assets/components/LanguageForm.vue b/aleksis/core/assets/components/LanguageForm.vue index 193329327..b1bb11127 100644 --- a/aleksis/core/assets/components/LanguageForm.vue +++ b/aleksis/core/assets/components/LanguageForm.vue @@ -1,64 +1,55 @@ <template> - <form method="post" ref="form" :action="action" id="language-form"> - <v-text-field - v-show="false" - name="csrfmiddlewaretoken" - :value="csrf_value" - type="hidden" - ></v-text-field> - <v-text-field - v-show="false" - name="next" - :value="next_url" - type="hidden" - ></v-text-field> - <v-text-field - v-show="false" - v-model="language" - name="language" - type="hidden" - ></v-text-field> - <v-menu offset-y> - <template v-slot:activator="{ on, attrs }"> - <v-btn + <v-menu offset-y> + <template v-slot:activator="{ on, attrs }"> + <v-btn depressed v-bind="attrs" v-on="on" color="primary" - > - <v-icon icon color="white">mdi-translate</v-icon> - {{ language }} - </v-btn> - </template> - <v-list id="language-dropdown" class="dropdown-content"> - <v-list-item-group - v-model="language" - color="primary" - > - <v-list-item v-for="language_option in items" :key="language_option[0]" :value="language_option[0]" @click="submit(language_option[0])"> - <v-list-item-title>{{ language_option[1] }}</v-list-item-title> - </v-list-item> - </v-list-item-group> - </v-list> - </v-menu> - </form> + > + <v-icon icon color="white">mdi-translate</v-icon> + {{ $i18n.locale }} + </v-btn> + </template> + <v-list id="language-dropdown" class="dropdown-content" min-width="150"> + <ApolloQuery :query="require('./availableLanguages.graphql')"> + <template v-slot="{ result: { error, data }, isLoading }"> + <v-skeleton-loader + v-if="isLoading" + class="mx-auto" + type="list-item, list-item, list-item" + ></v-skeleton-loader> + <v-list-item-group + v-if="!isLoading" + v-model="$i18n.locale" + color="primary" + > + <v-list-item v-for="languageOption in data.availableLanguages" :key="languageOption.code" + :value="languageOption.code" + @click="setLanguage(languageOption)"> + <v-list-item-title>{{ languageOption.nameTranslated }}</v-list-item-title> + </v-list-item> + </v-list-item-group> + </template> + </ApolloQuery> + </v-list> + </v-menu> </template> <script> - export default { - data: () => ({ - items: JSON.parse(document.getElementById("language-info-list").textContent), - language: JSON.parse(document.getElementById("current-language").textContent), - }), - methods: { - submit: function (new_language) { - this.language = new_language; - this.$nextTick(() => { - this.$refs.form.submit(); - }); - }, +export default { + data: function () { + return { + language: this.$i18n.locale + } + }, + methods: { + setLanguage: function (languageOption) { + document.cookie = languageOption.cookie; + this.$i18n.locale = languageOption.code; + this.$vuetify.lang.current = languageOption.code; }, - props: ["action", "csrf_value", "next_url"], - name: "language-form", - } + }, + name: "language-form", +} </script> diff --git a/aleksis/core/assets/components/availableLanguages.graphql b/aleksis/core/assets/components/availableLanguages.graphql new file mode 100644 index 000000000..b9ae5248f --- /dev/null +++ b/aleksis/core/assets/components/availableLanguages.graphql @@ -0,0 +1,7 @@ +{ + availableLanguages { + code + nameTranslated + cookie + } +} diff --git a/aleksis/core/schema.py b/aleksis/core/schema.py index 7e637aa20..a27cfd3c7 100644 --- a/aleksis/core/schema.py +++ b/aleksis/core/schema.py @@ -1,10 +1,15 @@ +from django.conf import settings +from django.utils import translation + import graphene +from graphene import ObjectType from graphene_django import DjangoObjectType from graphene_django.forms.mutation import DjangoModelFormMutation from .forms import PersonForm from .models import Group, Notification, Person from .util.core_helpers import get_app_module, get_app_packages, has_person +from .util.frontend_helpers import get_language_cookie class NotificationType(DjangoObjectType): @@ -27,6 +32,15 @@ class GroupType(DjangoObjectType): model = Group +class LanguageType(ObjectType): + code = graphene.String(required=True) + name = graphene.String(required=True) + name_local = graphene.String(required=True) + name_translated = graphene.String(required=True) + bidi = graphene.Boolean(required=True) + cookie = graphene.String(required=True) + + class PersonMutation(DjangoModelFormMutation): person = graphene.Field(PersonType) @@ -59,6 +73,9 @@ class Query(graphene.ObjectType): person_by_id = graphene.Field(PersonType, id=graphene.ID()) who_am_i = graphene.Field(PersonType) + language = graphene.String() + available_languages = graphene.List(LanguageType) + def resolve_notifications(root, info, **kwargs): # FIXME do permission stuff return Notification.objects.all() @@ -76,6 +93,15 @@ class Query(graphene.ObjectType): else: return None + def resolve_language(root, info, **kwargs): + return info.context.LANGUAGE_CODE + + def resolve_available_languages(root, info, **kwargs): + return [ + translation.get_language_info(code) | {"cookie": get_language_cookie(code)} + for code, name in settings.LANGUAGES + ] + class Mutation(graphene.ObjectType): update_person = PersonMutation.Field() diff --git a/aleksis/core/templates/core/vue_base.html b/aleksis/core/templates/core/vue_base.html index 037a76a30..3d8f6a7bf 100644 --- a/aleksis/core/templates/core/vue_base.html +++ b/aleksis/core/templates/core/vue_base.html @@ -98,8 +98,7 @@ </v-toolbar-title> <v-spacer></v-spacer> - <language-form action="{% url "set_language" %}" - csrf_value={{ csrf_token }} next_url={{ request.get_full_path }}></language-form> + <language-form></language-form> {% if user.is_authenticated %} <v-menu offset-y> <template v-slot:activator="{ on, attrs }"> @@ -231,8 +230,6 @@ {{ request.user.person.preferences.theme__design|json_script:"design-mode" }} {{ request.site.preferences.theme__primary|json_script:"primary-color" }} {{ request.site.preferences.theme__secondary|json_script:"secondary-color" }} -{{ LANGUAGE_CODE|json_script:"current-language" }} -{{ LANGUAGES|json_script:"language-info-list" }} <script type="text/javascript" src="{% static 'js/search.js' %}"></script> {% render_bundle 'core' %} {% block extra_body %}{% endblock %} diff --git a/aleksis/core/util/frontend_helpers.py b/aleksis/core/util/frontend_helpers.py index 034b1a0be..664707500 100644 --- a/aleksis/core/util/frontend_helpers.py +++ b/aleksis/core/util/frontend_helpers.py @@ -1,5 +1,7 @@ import os +from django.conf import settings + from .core_helpers import get_app_module, get_app_packages @@ -13,3 +15,18 @@ def get_apps_with_assets(): package = ".".join(app.split(".")[:-2]) assets[package] = path return assets + + +def get_language_cookie(code: str) -> str: + """Build a cookie string to set a new language.""" + cookie_parts = [f"{settings.LANGUAGE_COOKIE_NAME}={code}"] + args = dict( + max_age=settings.LANGUAGE_COOKIE_AGE, + path=settings.LANGUAGE_COOKIE_PATH, + domain=settings.LANGUAGE_COOKIE_DOMAIN, + secure=settings.LANGUAGE_COOKIE_SECURE, + httponly=settings.LANGUAGE_COOKIE_HTTPONLY, + samesite=settings.LANGUAGE_COOKIE_SAMESITE, + ) + cookie_parts += [f"{k.replace('_', '-')}={v}" for k, v in args.items() if v] + return "; ".join(cookie_parts) -- GitLab