feat: added language select

This commit is contained in:
Sergey Yarkov 2022-08-16 13:50:06 +03:00
parent 6a68833cf7
commit c9a45efccd
25 changed files with 139 additions and 60 deletions

View File

@ -0,0 +1,7 @@
---
import UIString from "../UIString";
---
<footer>
<h2><UIString key={"articlePost.comments"} /></h2>
</footer>

View File

@ -0,0 +1 @@
export { default } from "./ArticleComments.astro";

View File

@ -74,6 +74,7 @@
border-radius: var(--border-radius-lg); border-radius: var(--border-radius-lg);
font-size: 1rem; font-size: 1rem;
font-weight: 500; font-weight: 500;
font-family: "IBM Plex Mono", monospace;
padding: 0.2rem 0.3rem; padding: 0.2rem 0.3rem;
} }

View File

@ -1,17 +1,16 @@
--- ---
import { createDateFormatter, getLanguageFromURL } from "@root/utils"; import { createDateFormatter } from "@root/utils";
import { Image } from "@astrojs/image/components"; import { Image } from "@astrojs/image/components";
import ArticleComments from "@components/ArticleComments";
import "./ArticlePost.scss"; import "./ArticlePost.scss";
export type Props = { export type Props = {
data: ArticleType; data: ArticleType;
lang: string;
}; };
const { data } = Astro.props as Props; const { data, lang } = Astro.props as Props;
const { title, tags, author, published_at } = data; const { title, tags, author, published_at } = data;
// const lang = getLanguageFromURL(Astro.url.pathname);
// console.log(lang);
const DateFormatter = createDateFormatter("ru"); const DateFormatter = createDateFormatter("ru");
--- ---
@ -46,7 +45,5 @@ const DateFormatter = createDateFormatter("ru");
} }
</header> </header>
<slot /> <slot />
<footer> <ArticleComments />
<h2>Comments</h2>
</footer>
</article> </article>

View File

@ -6,6 +6,7 @@ import UIString from "@components/UIString";
import NavToggle from "./NavToggle"; import NavToggle from "./NavToggle";
import "./Header.scss"; import "./Header.scss";
import TranslatedLink from "@components/TranslatedLink"; import TranslatedLink from "@components/TranslatedLink";
import LanguageSelect from "@components/LanguageSelect";
const { name, nav, contacts } = SITE_METADATA; const { name, nav, contacts } = SITE_METADATA;
const { pathname } = Astro.url; const { pathname } = Astro.url;
@ -45,7 +46,7 @@ const { pathname } = Astro.url;
</a> </a>
</div> </div>
<div class="flex header__nav-settings"> <div class="flex header__nav-settings">
<Icon name="translate" width="21" height="21" /> <LanguageSelect />
<ThemeSwitcher client:visible /> <ThemeSwitcher client:visible />
</div> </div>
</nav> </nav>

View File

@ -77,6 +77,8 @@
} }
body[data-is-mobile-open="true"] { body[data-is-mobile-open="true"] {
overflow-y: hidden;
touch-action: none;
& .header { & .header {
padding: 15px 0; padding: 15px 0;
background-color: var(--background-header-color); background-color: var(--background-header-color);
@ -121,12 +123,22 @@
display: block; display: block;
position: absolute; position: absolute;
bottom: 45px; bottom: 45px;
right: 2.5rem; right: 2.2rem;
& svg { & svg {
width: 36px; width: 36px;
height: 36px; height: 36px;
} }
} }
& .language-select {
position: absolute;
bottom: 115px;
right: 2.2rem;
& svg,
& select {
height: 36px;
width: 36px;
}
}
} }
} }
} }

View File

@ -0,0 +1,59 @@
---
import { Icon } from "astro-icon";
import { DEFAULT_LANGUAGE } from "@root/config";
import languages from "@i18n/languages";
import { getLanguageFromURL } from "@root/utils";
const lang = getLanguageFromURL(Astro.url.pathname);
---
<div class="flex language-select">
<Icon name="translate" width="21" height="21" />
<select id="languageSelect" value={lang} aria-label="Select language">
{
Object.entries(languages).map(([code]) => (
<option value={code} selected={lang === code}>
<span>{code.toLocaleUpperCase()}</span>
</option>
))
}
</select>
</div>
<style lang="scss">
div {
position: relative;
&:hover {
opacity: var(--opacity-hover);
}
}
svg {
position: absolute;
right: 0;
}
select {
appearance: none;
width: 21px;
opacity: 0;
margin: 0;
border: none;
font-size: var(--font-size-md);
font-family: "IBM Plex Mono";
border-radius: var(--border-radius-md);
cursor: pointer;
}
</style>
<script define:vars={{ lang, DEFAULT_LANGUAGE }}>
const $select = document.getElementById("languageSelect");
const changeLanguageHandler = (e) => {
const newLang = e.target.value;
const pathname = window.location.pathname;
const url = newLang === DEFAULT_LANGUAGE ? pathname.slice(3) : `${newLang}${pathname}`;
window.location.pathname = url;
};
$select.addEventListener("change", changeLanguageHandler);
</script>

View File

@ -0,0 +1 @@
export { default } from "./LanguageSelect.astro";

View File

@ -1,5 +1,5 @@
--- ---
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import { getLanguageFromURL } from "@root/utils"; import { getLanguageFromURL } from "@root/utils";
import { Icon } from "astro-icon"; import { Icon } from "astro-icon";
import UIString from "../UIString"; import UIString from "../UIString";
@ -23,7 +23,7 @@ const { icon, name, year, description, articleUrl, sourceUrl, demoUrl } = data;
<span>{year}</span> <span>{year}</span>
</div> </div>
<div class="project-item__description"> <div class="project-item__description">
<p>{description[lang] || description[FALLBACK_LANGUAGE]}</p> <p>{description[lang] || description[DEFAULT_LANGUAGE]}</p>
</div> </div>
<div class="project-item__links"> <div class="project-item__links">
{ {

View File

@ -1,5 +1,5 @@
--- ---
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import { getLanguageFromURL } from "@root/utils"; import { getLanguageFromURL } from "@root/utils";
import { Icon } from "astro-icon"; import { Icon } from "astro-icon";
import "./SkillsBlock.scss"; import "./SkillsBlock.scss";
@ -16,7 +16,7 @@ const {
<div class="skills-block"> <div class="skills-block">
<div> <div>
<h3>{title[lang] || title[FALLBACK_LANGUAGE]}</h3> <h3>{title[lang] || title[DEFAULT_LANGUAGE]}</h3>
</div> </div>
<div> <div>
<ul> <ul>

View File

@ -1,5 +1,5 @@
--- ---
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import { getLanguageFromURL } from "@root/utils"; import { getLanguageFromURL } from "@root/utils";
export interface Props extends astroHTML.JSX.AnchorHTMLAttributes { export interface Props extends astroHTML.JSX.AnchorHTMLAttributes {
@ -8,7 +8,7 @@ export interface Props extends astroHTML.JSX.AnchorHTMLAttributes {
const lang = getLanguageFromURL(Astro.url.pathname); const lang = getLanguageFromURL(Astro.url.pathname);
const { to, ...attrs } = Astro.props as Props; const { to, ...attrs } = Astro.props as Props;
const href = lang === FALLBACK_LANGUAGE ? to : `/${lang}${to}`; const href = lang === DEFAULT_LANGUAGE ? to : `/${lang}${to}`;
--- ---
<a href={href} {...attrs}><slot /></a> <a href={href} {...attrs}><slot /></a>

View File

@ -1,4 +1,4 @@
export const FALLBACK_LANGUAGE = "ru"; export const DEFAULT_LANGUAGE: LanguageKeys = "ru";
export const SITE_METADATA: SiteMetadataType = { export const SITE_METADATA: SiteMetadataType = {
title: "Sergey Yarkov", title: "Sergey Yarkov",

View File

@ -3,6 +3,7 @@ export default {
"site.description": "site.description":
"Here I publish articles and tutorials on various IT topics, as well as talk about my projects that I have done over the past few years.", "Here I publish articles and tutorials on various IT topics, as well as talk about my projects that I have done over the past few years.",
"articles.empty": "No articles found...", "articles.empty": "No articles found...",
"articlePost.comments": "Comments",
"recentArticles.empty": "There is nothing here yet...", "recentArticles.empty": "There is nothing here yet...",
/* Page titles */ /* Page titles */
"pageTitle.about": "About me", "pageTitle.about": "About me",

View File

@ -3,6 +3,7 @@ export default {
"site.description": "site.description":
"Здесь я публикую статьи и туториалы на различные IT темы, а также рассказываю о своих проектах которые я сделал за последние несколько лет.", "Здесь я публикую статьи и туториалы на различные IT темы, а также рассказываю о своих проектах которые я сделал за последние несколько лет.",
"articles.empty": "Не найдено ни одной статьи...", "articles.empty": "Не найдено ни одной статьи...",
"articlePost.comments": "Комментарии",
"recentArticles.empty": "Пока что здесь ничего нет...", "recentArticles.empty": "Пока что здесь ничего нет...",
/* Page titles */ /* Page titles */
"pageTitle.about": "Обо мне", "pageTitle.about": "Обо мне",

View File

@ -1,6 +1,6 @@
import type { AstroGlobal } from "astro"; import type { AstroGlobal } from "astro";
import * as utils from "@utils/url"; import * as utils from "@utils/url";
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import languages from "./languages"; import languages from "./languages";
export function getLanguageKeys(): Array<string> { export function getLanguageKeys(): Array<string> {
@ -21,8 +21,8 @@ const translations = transformExports<UIDictionaryKeys>(import.meta.glob("./*/ui
export function useTranslation(Astro: Readonly<AstroGlobal>): (key: UIDictionaryKeys) => string | undefined { export function useTranslation(Astro: Readonly<AstroGlobal>): (key: UIDictionaryKeys) => string | undefined {
const lang = utils.getLanguageFromURL(Astro.url.pathname); const lang = utils.getLanguageFromURL(Astro.url.pathname);
return function translate(key: UIDictionaryKeys): string | undefined { return function translate(key: UIDictionaryKeys): string | undefined {
const string: string | undefined = translations[lang]?.[key] || translations[FALLBACK_LANGUAGE][key]; const string: string | undefined = translations[lang]?.[key] || translations[DEFAULT_LANGUAGE][key];
if (string === undefined) console.error(`Cannot find any string for translation key "${key}" on "${Astro.url.pathname}" page.`); if (string === undefined) console.log(`Cannot find any string for translation key "${key}" on "${Astro.url.pathname}" page.`);
return string; return string;
}; };
} }

View File

@ -1,32 +1,32 @@
--- ---
import type { Props as BaseHeadProps } from '@root/components/BaseHead.astro'; import type { Props as BaseHeadProps } from "@root/components/BaseHead.astro";
import BaseHead from '@components/BaseHead.astro'; import BaseHead from "@components/BaseHead.astro";
import BaseLayout from '@components/BaseLayout'; import BaseLayout from "@components/BaseLayout";
import ArticleContent from '@components/ArticleContent'; import ArticleContent from "@components/ArticleContent";
import ArticlePost from '@components/ArticlePost'; import ArticlePost from "@components/ArticlePost";
import '@styles/global.scss'; import "@styles/global.scss";
export interface Props extends BaseHeadProps { export interface Props extends BaseHeadProps {
lang?: string | undefined; lang?: string | undefined;
content: ArticleType; content: ArticleType;
} }
const { lang = 'ru', content } = Astro.props as Props; const { lang = "ru", content } = Astro.props as Props;
--- ---
<html lang={lang}> <html lang={lang} translate="no">
<head> <head>
<BaseHead title={content.title} description={content.description}> <BaseHead title={content.title} description={content.description}>
<script is:inline defer src='/assets/js/load-code-highlight.js' /> <script is:inline defer src="/assets/js/load-code-highlight.js"></script>
</BaseHead> </BaseHead>
</head> </head>
<BaseLayout> <BaseLayout>
<div class='container'> <div class="container">
<ArticlePost data={content}> <ArticlePost lang={lang} data={content}>
<ArticleContent> <ArticleContent>
<slot></slot> <slot />
</ArticleContent> </ArticleContent>
</ArticlePost> </ArticlePost>
</div> </div>

View File

@ -1,29 +1,29 @@
--- ---
import type { Props as BaseHeadProps } from '@components/BaseHead.astro'; import type { Props as BaseHeadProps } from "@components/BaseHead.astro";
import { SITE_METADATA } from '@root/config'; import { SITE_METADATA } from "@root/config";
import BaseHead from '@components/BaseHead.astro'; import BaseHead from "@components/BaseHead.astro";
import BaseLayout from '@components/BaseLayout'; import BaseLayout from "@components/BaseLayout";
import PageHeading from '@components/PageHeading'; import PageHeading from "@components/PageHeading";
import '@styles/global.scss'; import "@styles/global.scss";
export interface Props extends BaseHeadProps { export interface Props extends BaseHeadProps {
lang?: string | undefined; lang?: string | undefined;
showPageHeading?: boolean | undefined; showPageHeading?: boolean | undefined;
} }
const { title, description, lang = 'ru', showPageHeading = true } = Astro.props as Props; const { title, description, lang = "ru", showPageHeading = true } = Astro.props as Props;
--- ---
<html lang={lang}> <html lang={lang} translate="no">
<head> <head>
<BaseHead title={title ? `${title} | ${SITE_METADATA.title}` : SITE_METADATA.title} description={description} /> <BaseHead title={title ? `${title} | ${SITE_METADATA.title}` : SITE_METADATA.title} description={description} />
</head> </head>
<BaseLayout> <BaseLayout>
<div class='container'> <div class="container">
{showPageHeading && <PageHeading heading={title} description={description} />} {showPageHeading && <PageHeading heading={title} description={description} />}
<slot></slot> <slot />
</div> </div>
</BaseLayout> </BaseLayout>
</html> </html>

View File

@ -1,11 +1,11 @@
--- ---
import { getLanguageKeys } from "@i18n/utils"; import { getLanguageKeys } from "@i18n/utils";
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import About from "@templates/About.astro"; import About from "@templates/About.astro";
export function getStaticPaths() { export function getStaticPaths() {
return getLanguageKeys() return getLanguageKeys()
.filter((lang) => lang !== FALLBACK_LANGUAGE) .filter((lang) => lang !== DEFAULT_LANGUAGE)
.map((lang) => ({ params: { lang } })); .map((lang) => ({ params: { lang } }));
} }
--- ---

View File

@ -1,11 +1,11 @@
--- ---
import { getLanguageKeys } from "@i18n/utils"; import { getLanguageKeys } from "@i18n/utils";
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import Articles from "@templates/Articles.astro"; import Articles from "@templates/Articles.astro";
export function getStaticPaths() { export function getStaticPaths() {
return getLanguageKeys() return getLanguageKeys()
.filter((lang) => lang !== FALLBACK_LANGUAGE) .filter((lang) => lang !== DEFAULT_LANGUAGE)
.map((lang) => ({ params: { lang } })); .map((lang) => ({ params: { lang } }));
} }
--- ---

View File

@ -1,11 +1,11 @@
--- ---
import { getLanguageKeys } from "@i18n/utils"; import { getLanguageKeys } from "@i18n/utils";
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import Contacts from "@templates/Contacts.astro"; import Contacts from "@templates/Contacts.astro";
export function getStaticPaths() { export function getStaticPaths() {
return getLanguageKeys() return getLanguageKeys()
.filter((lang) => lang !== FALLBACK_LANGUAGE) .filter((lang) => lang !== DEFAULT_LANGUAGE)
.map((lang) => ({ params: { lang } })); .map((lang) => ({ params: { lang } }));
} }
--- ---

View File

@ -1,11 +1,11 @@
--- ---
import { getLanguageKeys } from "@i18n/utils"; import { getLanguageKeys } from "@i18n/utils";
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import Home from "@templates/Home.astro"; import Home from "@templates/Home.astro";
export function getStaticPaths() { export function getStaticPaths() {
return getLanguageKeys() return getLanguageKeys()
.filter((lang) => lang !== FALLBACK_LANGUAGE) .filter((lang) => lang !== DEFAULT_LANGUAGE)
.map((lang) => ({ params: { lang } })); .map((lang) => ({ params: { lang } }));
} }
--- ---

View File

@ -1,11 +1,11 @@
--- ---
import { getLanguageKeys } from "@i18n/utils"; import { getLanguageKeys } from "@i18n/utils";
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import Projects from "@templates/Projects.astro"; import Projects from "@templates/Projects.astro";
export function getStaticPaths() { export function getStaticPaths() {
return getLanguageKeys() return getLanguageKeys()
.filter((lang) => lang !== FALLBACK_LANGUAGE) .filter((lang) => lang !== DEFAULT_LANGUAGE)
.map((lang) => ({ params: { lang } })); .map((lang) => ({ params: { lang } }));
} }
--- ---

View File

@ -2,8 +2,10 @@
import type { MDXInstance } from "astro"; import type { MDXInstance } from "astro";
import ArticleLayout from "@layouts/article.astro"; import ArticleLayout from "@layouts/article.astro";
import { getLanguageFromURL } from "@root/utils";
const lang = getLanguageFromURL(Astro.url.pathname);
// TODO: generate pages based on user language
export async function getStaticPaths() { export async function getStaticPaths() {
const data = await Astro.glob<ArticleType>("../../../content/articles/**/*.mdx"); const data = await Astro.glob<ArticleType>("../../../content/articles/**/*.mdx");
return data.map((article) => { return data.map((article) => {
@ -16,6 +18,6 @@ export async function getStaticPaths() {
const { Content, frontmatter } = Astro.props as MDXInstance<ArticleType>; const { Content, frontmatter } = Astro.props as MDXInstance<ArticleType>;
--- ---
<ArticleLayout content={frontmatter}> <ArticleLayout lang={lang} content={frontmatter}>
<Content /> <Content />
</ArticleLayout> </ArticleLayout>

View File

@ -13,10 +13,6 @@ body {
min-height: 100vh; min-height: 100vh;
} }
body[data-is-mobile-open="true"] {
overflow-y: hidden;
}
*, *,
*:before, *:before,
*:after { *:after {

View File

@ -1,4 +1,4 @@
import { FALLBACK_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
export const encode = (data: object) => { export const encode = (data: object) => {
return Object.keys(data) return Object.keys(data)
@ -8,5 +8,5 @@ export const encode = (data: object) => {
export const getLanguageFromURL = (pathname: string) => { export const getLanguageFromURL = (pathname: string) => {
const langCodeMatch = pathname.match(/\/([a-z]{1}-?[a-z]{1})\//); const langCodeMatch = pathname.match(/\/([a-z]{1}-?[a-z]{1})\//);
return langCodeMatch ? langCodeMatch[1] : FALLBACK_LANGUAGE; return langCodeMatch ? langCodeMatch[1] : DEFAULT_LANGUAGE;
}; };