Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/language switcher #2811

Merged
merged 9 commits into from
Dec 30, 2022
52 changes: 11 additions & 41 deletions packages/docs/src/components/header/components/LanguageDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
>
<div class="language-dropdown__content">
<va-list-item
v-for="(option, id) in options"
v-for="(language, id) in languages"
:key="id"
class="language-dropdown__item row align-center py-2 va-link"
:class="{ active: option.code === locale }"
@click="setLanguage(option.code)"
:class="{ active: language.code === locale }"
@click="setLanguage(language.code)"
>
<va-list-item-section :style="{ color: option.code === locale ? colors.textPrimary : colors.primary }">
<span class="dropdown-item__text">{{ option.name }}</span>
<va-list-item-section :style="{ color: language.code === locale ? colors.textPrimary : colors.primary }">
<span class="dropdown-item__text">{{ language.name }}</span>
</va-list-item-section>
</va-list-item>
<va-list-item
Expand All @@ -33,59 +33,29 @@
</template>

<script lang="ts">
import { defineComponent, computed, watch, onMounted } from 'vue'
import { defineComponent, computed } from 'vue'
import { useColors } from 'vuestic-ui/src/main'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'

import { languages } from '../../../locales'
import { useSharedLanguageSwitcher } from '../../../locales/hooks/useLanguageSwitcher'

export default defineComponent({
name: 'DocsLanguageDropdown',
setup () {
const { locale, t } = useI18n()
const router = useRouter()

const getCurrentPathWithoutLocale = () => {
const path = router.currentRoute.value.fullPath
const localeUrlPart = `/${locale.value}`

if (path.slice(0, localeUrlPart.length) === localeUrlPart) { return path.slice(localeUrlPart.length) }

return path
}

const setLanguage = (newLocale: string) => {
if (locale.value === newLocale) { return }

localStorage.setItem('language', newLocale)

const currentPathWithoutLocale = getCurrentPathWithoutLocale()

router.push('/' + newLocale + currentPathWithoutLocale)
}

const options = languages
const { getColors } = useColors()
const colors = computed(getColors)
const currentLanguageName = computed(() => options.find(({ code }) => code === locale.value)?.name)

const setHtmlLang = () => {
if (!document?.documentElement) { return }

document.documentElement.setAttribute('lang', locale.value || 'en')
}

onMounted(setHtmlLang)
watch(locale, setHtmlLang)
const { currentLanguageName, languages, setLanguage } = useSharedLanguageSwitcher()

return {
t,
colors,
options,
setLanguage,
locale,
t,
languages,
currentLanguageName,
setLanguage,
}
},
})
Expand Down
83 changes: 31 additions & 52 deletions packages/docs/src/components/landing/LandingHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
<a href="/" aria-label="go to the main page">
<vuestic-logo height="30" width="150" aria-hidden="true" />
</a>
<div class="menu" @click="onClick(!isHidden)" :style="{position: !isHidden ? 'fixed' : 'absolute'}">
<div class="menu" @click="isHidden = !isHidden" :style="{position: !isHidden ? 'fixed' : 'absolute'}">
<img v-if="!isHidden" src="../../assets/landing/images/hamburger.svg" alt="menu">
<img v-else src="../../assets/landing/images/cross.svg" alt="menu">
</div>
</div>
<nav class="header__links">
<!-- vuestic buttons -->
<va-button
:to="`/${$root.$i18n.locale}/introduction/overview`"
:to="`/${locale}/introduction/overview`"
class="header__links--link"
preset="landingHeader"
>
Expand All @@ -39,7 +39,7 @@
{{ $t('landing.header.buttons.discord') }}
</va-button>
<va-button
:to="`/${$root.$i18n.locale}/introduction/team`"
:to="`/${locale}/introduction/team`"
class="header__links--link"
preset="landingHeader"
target="_blank"
Expand All @@ -59,16 +59,16 @@
<landing-stars-button class="ml-2" repo="epicmaxco/vuestic-ui" />
</nav>
<!-- mobile -->
<nav class="mobile-menu" :class="computedClass">
<nav class="mobile-menu" :class="{'mobile-menu--open': !isHidden}">
<va-list>
<va-list-item>
<va-list-item-section class="mobile-menu__link">
<router-link :to="`/${$root.$i18n.locale}/introduction/overview`">{{ $t('landing.header.buttons.overview') }}</router-link>
<router-link :to="`/${locale}/introduction/overview`">{{ $t('landing.header.buttons.overview') }}</router-link>
</va-list-item-section>
</va-list-item>
<va-list-item>
<va-list-item-section class="mobile-menu__link">
<router-link :to="`/${$root.$i18n.locale}/introduction/overview`">{{ $t('landing.header.buttons.docs') }}</router-link>
<router-link :to="`/${locale}/introduction/overview`">{{ $t('landing.header.buttons.docs') }}</router-link>
</va-list-item-section>
</va-list-item>
<va-list-item>
Expand All @@ -88,21 +88,21 @@
</va-list-label>
<div class="mobile-menu__languages">
<va-list-item
v-for="(option, id) in options"
v-for="(language, id) in languages"
:key="id"
class="mobile-menu__language"
:class="{ active: option.code === currentLanguage }"
@click="setLanguage(option.code)"
:class="{ active: language.code === locale }"
@click="setLanguage(language.code)"
>
<va-list-item-section class="mobile-menu__link">
<span class="language">{{ option.name }}</span>
<span class="language">{{ language.name }}</span>
</va-list-item-section>
</va-list-item>
<va-list-item>
<va-list-item-section class="mobile-menu__link">
<router-link
class="mobile-menu__language"
:to="`/${$root.$i18n.locale}/contribution/translation`"
:to="`/${locale}/contribution/translation`"
>
{{ $t('landing.header.buttons.translation') }}
</router-link>
Expand All @@ -121,61 +121,40 @@
</template>

<script lang="ts">
// @ts-nocheck
import { Options, Vue } from 'vue-class-component'
import { languages } from '../../locales'
import { defineComponent, ref } from 'vue'
import { useI18n } from 'vue-i18n'

import VuesticLogo from '../header/components/VuesticLogo.vue'
import LanguageDropdown from '../header/components/LanguageDropdown.vue'
import LandingStarsButton from './LandingStarsButton.vue'
import LandingThemeSwitchButton from '../ThemeSwitch.vue'
// import LandingThemeSwitchButton from '../ThemeSwitch.vue'

import { useSharedLanguageSwitcher } from '../../locales/hooks/useLanguageSwitcher'

@Options({
export default defineComponent({
name: 'LandingHeader',

components: {
LanguageDropdown,
LandingStarsButton,
VuesticLogo,
LandingThemeSwitchButton,
// LandingThemeSwitchButton,
},
})
export default class Header extends Vue {
value = false
isHidden = true
options = languages

onClick (value: boolean) {
this.isHidden = value
}
setup () {
const { locale } = useI18n()
const isHidden = ref(true)

const { languages, setLanguage } = useSharedLanguageSwitcher()

get computedClass () {
return {
'mobile-menu--open': !this.isHidden,
locale,
languages,
isHidden,
setLanguage,
}
}

setLanguage (locale: any) {
this.$root.$i18n.locale = locale
document.querySelector('html').setAttribute('lang', locale)
localStorage.setItem('VueAppLanguage', locale)
this.$nextTick(() => {
// a little hack to change the same route alias
const path = this.$localizePath(this.$route.fullPath, locale)
this.$router.replace({
path,
hash: `#${+new Date()}`,
}).then(() => this.$router.replace({ hash: '' }))
})
}

get currentLanguage () {
return (this as any).$root.$i18n.locale
}

get currentLanguageName () {
const result = (this as any).options.find(({ code }: any) => code === this.currentLanguage)
return result.name
}
}
},
})
</script>

<style lang="scss" scoped>
Expand Down
27 changes: 27 additions & 0 deletions packages/docs/src/composables/useSharedComposable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { effectScope, onScopeDispose, EffectScope } from 'vue'

export const useSharedComposable = <T extends (...args: any[]) => any>(composable: T): T => {
let subscribers = 0
let state: ReturnType<T> | null = null
let scope: EffectScope | null = null

const dispose = () => {
if (scope && --subscribers <= 0) {
scope.stop()
state = scope = null
}
}

return <T>((...args: any[]) => {
subscribers++

if (!state) {
scope = effectScope(true)
state = scope.run(() => composable(...args))
}

onScopeDispose(dispose)

return state
})
}
65 changes: 65 additions & 0 deletions packages/docs/src/locales/hooks/useLanguageSwitcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { computed, onMounted, watch, ComputedRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'

import { languages } from '../index'
import { useSharedComposable } from '@/composables/useSharedComposable'

export type LanguageSwitcherType = {
languages: Record<string, unknown>[],
currentLanguageName: ComputedRef<string | undefined>
setLanguage: (newLocale: string, replace: boolean) => void
getCurrentPathWithoutLocale: () => void
}

export const useLanguageSwitcher = (): LanguageSwitcherType => {
const router = useRouter()
const { locale, t } = useI18n()

const getCurrentPathWithoutLocale = () => {
const path = router.currentRoute.value.fullPath
const localeUrlPart = `/${locale.value}`
const localeUrlPartLength = localeUrlPart.length

if (path.slice(0, localeUrlPartLength) === localeUrlPart) {
return path.slice(localeUrlPartLength)
}

return path
}

const setLanguage = (newLocale: string, replace = false) => {
if (locale.value === newLocale) { return }

localStorage.setItem('language', newLocale)

const currentPathWithoutLocale = getCurrentPathWithoutLocale()
const path = '/' + newLocale + currentPathWithoutLocale

if (replace) {
router.replace(path)
} else {
router.push(path)
}
}

const currentLanguageName = computed(() => languages.find(({ code }) => code === locale.value)?.name)

const setHtmlLang = () => {
if (!document?.documentElement) { return }

document.documentElement.setAttribute('lang', locale.value || 'en')
}

onMounted(setHtmlLang)
watch(locale, setHtmlLang)

return {
languages,
currentLanguageName,
setLanguage,
getCurrentPathWithoutLocale,
}
}

export const useSharedLanguageSwitcher = useSharedComposable <typeof useLanguageSwitcher>(useLanguageSwitcher)