Skip to content

Commit

Permalink
added localization
Browse files Browse the repository at this point in the history
  • Loading branch information
oleksandr-dukhovnyy committed Jan 7, 2024
1 parent 7664569 commit abe302c
Show file tree
Hide file tree
Showing 19 changed files with 2,172 additions and 3,343 deletions.
4 changes: 2 additions & 2 deletions components/Catalog/Catalog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ const showSideModal = ref(false);
display: grid;
grid-template-rows: 42px 1fr;
grid-template-columns: 1fr;
gap: 30px 110px;
gap: 30px 70px;
justify-content: space-evenly;
width: 100%;
margin: 60px 0 0;
padding: 0 30px;
@include media-up(xsm) {
grid-template-rows: 1fr;
grid-template-columns: 155px 1fr;
grid-template-columns: 200px 1fr;
}
&__filters {
Expand Down
7 changes: 5 additions & 2 deletions components/Catalog/CatalogModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
'catalog-modal__actions--large-icon': showAddToCartAnimation,
}"
>
<TheButton size="lg" type="error" @click="buy"> buy </TheButton>
<TheButton size="lg" type="error" @click="buy">
{{ t('cta.buy') }}
</TheButton>
<TheButton
size="lg"
type="black"
Expand All @@ -65,7 +67,7 @@
v-if="!showAddToCartAnimation"
style="margin: auto 0 auto 8px"
>
to cart
{{ t('cta.add-to-cart') }}
</span>
</TheButton>
</div>
Expand All @@ -78,6 +80,7 @@
import { useSliderStore } from '~/store/slider';
import { useCartStore } from '~/store/cart';
const { t } = useLocator();
const cartStore = useCartStore();
const sliderStore = useSliderStore();
Expand Down
17 changes: 16 additions & 1 deletion components/Catalog/FiltersGroup.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<ul class="filter-group">
<li
v-for="(item, i) in items"
v-for="(item, i) in preparedItems"
:key="i"
:class="`filter-group--size-${size}`"
@click="selected(item.name)"
Expand Down Expand Up @@ -47,6 +47,21 @@ const emit = defineEmits<{
const selected = (name: string) => {
emit('selected', { value: name, type: props.type });
};
const { t } = useLocator();
const preparedItems = computed(() => {
return props.items.map((item) => {
if (props.type === 'brands') return item;
// 'price low to hight' -> 'price-low-to-hight'
const title = item.title.replace(/\s/g, '-');
return {
...item,
title: t(`filters.${props.type}.${title}`),
};
});
});
</script>

<style lang="scss" scoped>
Expand Down
12 changes: 7 additions & 5 deletions components/FirstScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
></div>
<div class="f-screen__item-cta-text" v-else>
<strong>
sunglasses <br />
for all <br />
life <br />
seasons
{{ t('cta.slogan.0') }} <br />
{{ t('cta.slogan.1') }} <br />
{{ t('cta.slogan.2') }} <br />
{{ t('cta.slogan.3') }}
</strong>
<TheButton size="lg" type="cta" @click="scrollToGoods">
browse
{{ t('cta.action-button') }}
</TheButton>
</div>
</div>
Expand All @@ -31,6 +31,8 @@
import { scrollToGoods } from '~/helpers/scrollToGoods';
import image from '~/utils/image';
const { t } = useLocator();
const getPathToImgs = (index: number | string) =>
image(`LineSlider/1/${index}.jpg`);
Expand Down
9 changes: 7 additions & 2 deletions components/Header/Cart/HCart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
<div class="cart" data-test="cart">
<div class="cart__price">
<HeaderCart />
<TheButton size="lg" type="error-filled" @click="buy"> buy </TheButton>
<TheButton size="lg" type="error-filled" @click="buy">
{{ t('cta.buy') }}
</TheButton>
</div>
<div v-if="CART_ITEMS.length > 0" class="cart__list">
<ProductCard
Expand All @@ -18,7 +20,9 @@
/>
</div>
<div v-else class="cart__empty">
<p>Cart is currently empty</p>
<p>
{{ t('cart.cart-is-empty') }}
</p>
</div>
</div>
</Modal>
Expand All @@ -30,6 +34,7 @@ import { useCartStore } from '~/store/cart';
import { useSliderStore } from '~/store/slider';
import HeaderCart from '~/components/Header/HeaderCart.vue';
const { t } = useLocator();
const cartStore = useCartStore();
const sliderStore = useSliderStore();
Expand Down
20 changes: 14 additions & 6 deletions components/Header/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,23 @@ for (let i = 0; i < userLangs.length; i++) {
}
}
const setSelectedLang = (langValue) => {
langs.value = langs.value.map((lng) => ({
...lng,
selected: lng.value === langValue,
}));
const locator = useLocator();
const setSelectedLang = async (langValue: Localization.Lang['value']) => {
const result = await locator.setLocale(langValue);
if (result) {
langs.value = langs.value.map((lng) => ({
...lng,
selected: lng.value === langValue,
}));
}
};
onMounted(() => {
const latestSelectedLang = localStorage.getItem('lang');
const latestSelectedLang = localStorage.getItem(
'lang'
) as Localization.Lang['value'];
if (latestSelectedLang) setSelectedLang(latestSelectedLang);
});
Expand Down
5 changes: 3 additions & 2 deletions components/Header/HeaderCart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
<script lang="ts" setup>
import { useCartStore } from '~/store/cart';
const cartStore = useCartStore();
const emit = defineEmits<{
(e: 'open-cart'): void;
}>();
const { t } = useLocator();
const cartStore = useCartStore();
</script>

<style lang="scss" scoped>
Expand Down
2 changes: 1 addition & 1 deletion components/Header/HeaderLangs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<span
v-for="lang in langs"
:key="lang.value"
@click="emit('change-current-lang', lang)"
:class="lang.selected ? 'selected' : ''"
@click="emit('change-current-lang', lang)"
>
{{ lang.title }}
</span>
Expand Down
9 changes: 7 additions & 2 deletions components/ProductCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
<div @click="emit('clicked')" class="item__new" v-if="isNew && useImage">
new
</div>
<div style="position: relative; display: flex; gap: 10px">
<div
v-if="useRemoveBtn || useCounter"
style="position: relative; display: flex; gap: 10px"
>
<Counter
v-if="useCounter"
class="item__counter"
Expand All @@ -31,7 +34,7 @@
<Confirmation
v-if="useRemoveBtn"
color="red"
text="Delete?"
:text="t('cart.delete-item')"
class="item__remove"
@click.stop
@confirm="emit('remove')"
Expand Down Expand Up @@ -75,6 +78,8 @@ const emit = defineEmits<{
(e: 'dec-count'): void;
(e: 'remove'): void;
}>();
const { t } = useLocator();
</script>

<style lang="scss" scoped>
Expand Down
3 changes: 2 additions & 1 deletion components/TopNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:key="navItem"
@click="selectCategory(navItem)"
>
{{ navItem }}
{{ t(`filters.genders.${navItem}`) }}
</span>
</nav>
</template>
Expand All @@ -14,6 +14,7 @@
import { scrollToGoods } from '~/helpers/scrollToGoods';
import { useGlassesStore } from '~/store/glasses';
const { t } = useLocator();
const glassesStore = useGlassesStore();
const navItems = ['man', 'woman', 'kids'];
Expand Down
72 changes: 72 additions & 0 deletions composables/useLocator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import enLocale from '~/locales/en-US.json';

type lazyLocale =
| Localization.Locale
| Promise<Localization.Locale>
| (() => Promise<Localization.Locale>);

const locales: {
'en-US': lazyLocale;
'uk-UA': lazyLocale;
} = {
'en-US': enLocale,
'uk-UA': () => import('~/locales/uk-UA.json').then((m) => m.default || m),
};

const state = reactive<{
locale: Localization.Lang['value'];
messages: Localization.Locale;
}>({
locale: 'en-US',
messages: enLocale,
});

// TODO: use i18n
export default function useLocalization() {
return {
locale: computed(() => state.locale),

t(localeKey: string, fallback?: string): string {
if (!state.messages) {
console.log('missing messages');

return fallback || localeKey;
}

const text = extractByDottedKey<string>(localeKey, state.messages);

if (!text)
console.error(
`Missing translation for ${localeKey} in ${state.locale}`
);

return text || fallback || localeKey;
},

async setLocale(locale: Localization.Lang['value']) {
const assetLoadingResult = await loadLocale(locale);

if (assetLoadingResult) {
state.locale = locale;
state.messages = locales[locale] as Localization.Locale;
}

return true;
},
};
}

function extractByDottedKey<T>(key: string, obj: object): T | undefined {
return key.split('.').reduce((o, k) => o?.[k], obj) as T;
}

async function loadLocale(locale: Localization.Lang['value']) {
let asset = locales[locale];

if (!asset) return false;
if (typeof asset === 'function') asset = asset();
if (asset instanceof Promise) asset = await asset;
if (locales[locale] !== asset) locales[locale] = asset;

return true;
}
16 changes: 0 additions & 16 deletions helpers/langs/langs.ts

This file was deleted.

26 changes: 23 additions & 3 deletions locales/en-US.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
{
"filters": {
"man": "Man",
"woman": "Woman",
"kids": "Kids"
"genders": {
"man": "Man",
"woman": "Woman",
"kids": "Kids"
},
"categories": {
"new": "new",
"sale": "sale",
"most-popular": "most popular",
"price-low-to-hight": "price low to hight",
"price-hight-to-low": "price hight to low"
}
},
"cta": {
"action-button": "browse",
"buy": "buy",
"add-to-cart": "to cart",
"slogan": ["sunglasses", "for all", "life", "seasons"]
},

"cart": {
"cart-is-empty": "Cart is currently empty",
"delete-item": "Delete?"
}
}
26 changes: 23 additions & 3 deletions locales/uk-UA.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
{
"filters": {
"man": "Чоловічі",
"woman": "Жіночі",
"kids": "Дитячі"
"genders": {
"man": "Чоловікам",
"woman": "Жінкам",
"kids": "Діти"
},
"categories": {
"new": "новинки",
"sale": "зі знижкою",
"most-popular": "популярні",
"price-low-to-hight": "починаючи з дешевих",
"price-hight-to-low": "починаючи з дорогих"
}
},
"cta": {
"action-button": "дивитися",
"buy": "купити",
"add-to-cart": "до кошика",
"slogan": ["сонцезахисні окуляри", "на всі", "випадки", "життя"]
},

"cart": {
"cart-is-empty": "Кошик наразі порожній",
"delete-item": "Видалити?"
}
}
Loading

0 comments on commit abe302c

Please sign in to comment.