Skip to content

Commit

Permalink
feat: remove useLocale composition and use router based locale selection
Browse files Browse the repository at this point in the history
  • Loading branch information
rudnovd committed Apr 11, 2024
1 parent 3425188 commit e715b98
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 144 deletions.
81 changes: 24 additions & 57 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<template>
<router-link v-if="showBackButton" class="return-home" to="/">
<header>
<router-link v-show="router.currentRoute.value.path !== '/'" class="return-home" to="/">
<img src="@/assets/icons/arrow_back.svg" alt="back" width="16" height="16" />
{{ t('common.homePage') }}
</router-link>
</header>

<RouterView v-slot="{ Component }">
<Transition name="router" mode="out-in" @enter="onEnter" @after-leave="afterLeave">
<Transition name="router" mode="out-in">
<KeepAlive :include="keepAliveComponents">
<component :is="Component" />
</KeepAlive>
Expand All @@ -19,12 +21,13 @@

<script setup lang="ts">
import { useStore } from '@/store'
import { watchOnce } from '@vueuse/core'
import { useRegisterSW } from 'virtual:pwa-register/vue'
import { computed, defineAsyncComponent, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { isDark, useLocale } from './utilities'
import { useRouter } from 'vue-router'
import { isDark, selectedLocale } from '@/utilities'
import { setLocale } from '@/i18n'
import { watchOnce } from '@vueuse/core'
const BaseNotification = defineAsyncComponent(() => import('@/components/base/BaseNotification.vue'))
const getDataRoutes = ['/damage', '/magic', '/creatures']
Expand All @@ -34,30 +37,8 @@ const router = useRouter()
const route = useRoute()
const { t } = useI18n()
const store = useStore()
const lang = useLocale()
const showBackButton = ref(false)
const { updateServiceWorker, needRefresh } = useRegisterSW({
immediate: false,
onRegistered(registration) {
if (registration) {
/* eslint-disable no-console */
console.log('Service worker registered')
}
},
onRegisterError(error) {
if (error) {
/* eslint-disable no-console */
console.log(`Service worker registartion error: ${error}`)
}
},
})
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (query) => {
isDark.value = query.matches
})
setLocale(selectedLocale.value)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => (isDark.value = matches))
const notificationsButtons = computed(() => [
{
text: t('common.update'),
Expand All @@ -66,33 +47,14 @@ const notificationsButtons = computed(() => [
},
textColor: 'rgb(255, 255, 255)',
},
{
text: t('common.dismiss'),
textColor: 'rgb(255, 255, 255)',
},
])
// Show back button on first visit app
watchOnce(router.currentRoute, () => {
if (Object.keys(route.meta).length && !route.meta.hideBackButton) showBackButton.value = true
// Collect initial data about game
const stop = watch(router.currentRoute, ({ path }) => {
if (!store.isDataLoaded && ['/damage', '/magic', '/creatures'].includes(path)) {
store.loadData(selectedLocale.value)
stop()
}
})
// Collect data about game from files when visit one of getDataRoutes array pages
watchOnce(
() => route.path,
(newPath) => {
if (!store.isDataLoaded && getDataRoutes.includes(newPath)) store.loadData(lang.value)
},
)
const onEnter = () => {
if (Object.keys(route.meta).length && !route.meta.hideBackButton) showBackButton.value = true
else if (showBackButton.value) showBackButton.value = false
}
const afterLeave = () => {
if (showBackButton.value && route.meta.hideBackButton) showBackButton.value = false
}
</script>
<style lang="scss">
Expand All @@ -101,14 +63,19 @@ const afterLeave = () => {
</style>
<style lang="scss" scoped>
header {
height: 1.5rem;
padding-left: 5px;
margin-bottom: 0.5rem;
}
.return-home {
display: inline-flex;
align-items: center;
padding-left: 5px;
font-size: 0.8rem;
font-size: 1rem;
color: var(--color-link);
text-decoration: none;
opacity: 0.3;
opacity: 0.7;
transition: opacity 0.15s;
&:hover {
Expand Down
31 changes: 11 additions & 20 deletions src/components/PageFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@
{{ about.text || t('components.pageFooter.about') }}
</router-link>

<select v-model="locale" name="select-locale">
<select :value="selectedLocale" name="select-locale" @change="updatePage">
<option v-for="availableLocale in locales" :key="availableLocale.name" :value="availableLocale.name">
{{ availableLocale.value }}
</option>
</select>

<slot></slot>

<router-link to="send-error" class="send-error-link">
{{ t('components.pageFooter.foundAnError') }}
</router-link>

<router-link to="#license">
{{ t('components.pageFooter.licenseInformation') }}
</router-link>

<a href="https://github.com/rudnovd/heroes3tools" target="_blank" rel="noopener">
{{ t('components.pageFooter.sourceCode') }}
</a>
Expand Down Expand Up @@ -61,8 +58,8 @@
</template>

<script setup lang="ts">
import i18n from '@/i18n'
import { useStore } from '@/store'
import { type AvailableLocale } from '@/i18n'
import { isDark, selectedLocale } from '@/utilities'
import { isDark, useLocale } from '@/utilities'
import { defineAsyncComponent, watch } from 'vue'
import { useI18n } from 'vue-i18n'
Expand All @@ -86,22 +83,16 @@ const router = useRouter()
const store = useStore()
const locale = useLocale()
const staticCurrentRoute = route.path
const locales = [
{
name: 'en',
value: 'English',
},
{
name: 'ru',
value: 'Русский',
},
const locales: ReadonlyArray<{ name: AvailableLocale; value: string }> = [
{ name: 'en', value: 'English' },
{ name: 'ru', value: 'Русский' },
]
watch(i18n.global.locale, (newLocale) => {
store.loadData(newLocale)
document.title = t(route.meta.title as string)
})
async function updatePage(event: Event) {
const locale = (event.target as HTMLInputElement).value as AvailableLocale
selectedLocale.value = locale
router.go(0)
}
const appVersion = import.meta.env.__APP_VERSION__
</script>
Expand Down
29 changes: 20 additions & 9 deletions src/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { createI18n } from 'vue-i18n'
import { selectedLocale } from './utilities'

export const availableLocales: Readonly<Array<string>> = ['en', 'ru']
export type AvailableLocale = 'en' | 'ru'
export const DEFAULT_LOCALE: AvailableLocale = 'en'
export const AVAILABLE_LOCALES: Readonly<Array<AvailableLocale>> = ['en', 'ru']

const i18n = createI18n({
legacy: false,
locale: 'en',
availableLocales,
locale: DEFAULT_LOCALE,
availableLocales: AVAILABLE_LOCALES,
})

export async function loadLocaleMessages(locale: string) {
return await import(`./locales/${locale}.json`)
}

export function getBrowserLanguage() {
if (!navigator.language) return 'en'
export function getBrowserLanguage(): AvailableLocale {
if (!navigator.language) return DEFAULT_LOCALE

// get first language code (e.g. get 'en' from en-US)
const navigatorLanguage = navigator.language.trim().split(/-|_/)[0]
if (availableLocales.includes(navigatorLanguage)) {
return navigatorLanguage
const browserLanguage = navigator.language.trim().split(/-|_/)[0]
if (AVAILABLE_LOCALES.some((language) => language === browserLanguage)) {
return browserLanguage as AvailableLocale
} else {
return 'en'
return DEFAULT_LOCALE
}
}

export async function setLocale(locale: AvailableLocale) {
selectedLocale.value = locale
const messages = await loadLocaleMessages(locale)
i18n.global.setLocaleMessage(locale, messages.default)
i18n.global.locale.value = locale
document.querySelector('html')?.setAttribute('lang', locale)
}

export default i18n
65 changes: 15 additions & 50 deletions src/router.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,31 @@
import i18n from '@/i18n'
import { AVAILABLE_LOCALES, DEFAULT_LOCALE } from '@/i18n'
import { createRouter, createWebHistory } from 'vue-router'
import { selectedLocale } from './utilities'

const router = createRouter({
history: createWebHistory('/'),
history: createWebHistory(selectedLocale.value === DEFAULT_LOCALE ? '/' : `${selectedLocale.value}`),
routes: [
{
path: '/',
component: () => import('@/views/HomePage.vue'),
meta: {
title: 'pages.home',
hideBackButton: true,
},
},
{
path: '/damage',
component: () => import('@/views/DamageCalculatorPage.vue'),
meta: {
title: 'pages.damageCalculator',
},
},
{
path: '/magic',
component: () => import('@/views/MagicCalculatorPage.vue'),
meta: {
title: 'pages.magicCalculator',
},
},
{
path: '/creatures',
component: () => import('@/views/CreaturesLibraryPage.vue'),
meta: {
title: 'pages.creaturesLibrary',
},
},
{
path: '/send-error',
component: () => import('@/views/SendErrorPage.vue'),
meta: {
title: 'pages.sendError',
},
children: [
{ path: '', component: () => import('@/views/HomePage.vue') },
{ path: 'damage', component: () => import('@/views/DamageCalculatorPage.vue') },
{ path: 'magic', component: () => import('@/views/MagicCalculatorPage.vue') },
{ path: 'creatures', component: () => import('@/views/CreaturesLibraryPage.vue') },
{ path: 'send-error', component: () => import('@/views/SendErrorPage.vue') },
{
path: `:locale(${AVAILABLE_LOCALES.join('|')})/:path(.*)`,
redirect: (to) => to.path.replace(`/${to.params.locale}`, ''),
},
],
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFoundPage.vue'),
meta: {
title: 'pages.pageNotFound',
hideBackButton: true,
},
},
],
scrollBehavior(to, from, savedPosition) {
scrollBehavior(to, _from, savedPosition) {
return new Promise((resolve) => {
if (savedPosition) {
return resolve(savedPosition)
Expand All @@ -68,14 +43,4 @@ router.resolve({
params: { pathMatch: ['not', 'found'] },
}).href

router.beforeEach((to, from, next) => {
if (to.meta.disabled) next({ path: '/' })

next()
})

router.afterEach((to) => {
document.title = i18n.global.t(to.meta.title as string)
})

export default router
4 changes: 1 addition & 3 deletions src/views/DamageCalculatorPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ const activeIndex = ref(0)
const attacker = computed(() => calculators.value[activeIndex.value].attacker)
const defender = computed(() => calculators.value[activeIndex.value].defender)
const howToUseSteps = computed(() => {
return [
const howToUseSteps = [
{
name: 'Pick attacker creature',
targets: [
Expand Down Expand Up @@ -172,7 +171,6 @@ const howToUseSteps = computed(() => {
],
},
]
})
const { ignoreUpdates } = watchIgnorable(
[attacker, defender],
Expand Down
10 changes: 5 additions & 5 deletions src/views/HomePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,23 @@ import(`../locales/head/pages/home-page/${selectedLocale.value}.json`).then((jso
const { t } = useI18n()
const router = useRouter()
const pages = computed(() => [
const pages = [
{
name: t('pages.damageCalculator'),
path: '/damage',
image: 'Tactics',
},
{
name: t('pages.magicCalculator'),
path: '/magic',
path: 'magic',
image: 'Water',
},
{
name: t('pages.creaturesLibrary'),
path: '/creatures',
path: 'creatures',
image: 'Scholar',
},
])
]
</script>

<style lang="scss" scoped>
Expand All @@ -75,7 +75,7 @@ const pages = computed(() => [
grid-template-rows: min-content 1fr min-content;
gap: 16px;
min-width: 300px;
min-height: 100vh;
min-height: calc(100dvh - 2rem);
margin: 0 auto;
content-visibility: auto;
contain-intrinsic-size: 100vh;
Expand Down

0 comments on commit e715b98

Please sign in to comment.