mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 02:19:08 +03:00
feat: added language select
This commit is contained in:
parent
6a68833cf7
commit
c9a45efccd
7
src/components/ArticleComments/ArticleComments.astro
Normal file
7
src/components/ArticleComments/ArticleComments.astro
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
import UIString from "../UIString";
|
||||
---
|
||||
|
||||
<footer>
|
||||
<h2><UIString key={"articlePost.comments"} /></h2>
|
||||
</footer>
|
||||
1
src/components/ArticleComments/index.ts
Normal file
1
src/components/ArticleComments/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./ArticleComments.astro";
|
||||
@ -74,6 +74,7 @@
|
||||
border-radius: var(--border-radius-lg);
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
padding: 0.2rem 0.3rem;
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
---
|
||||
import { createDateFormatter, getLanguageFromURL } from "@root/utils";
|
||||
import { createDateFormatter } from "@root/utils";
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import ArticleComments from "@components/ArticleComments";
|
||||
import "./ArticlePost.scss";
|
||||
|
||||
export type Props = {
|
||||
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 lang = getLanguageFromURL(Astro.url.pathname);
|
||||
// console.log(lang);
|
||||
|
||||
const DateFormatter = createDateFormatter("ru");
|
||||
---
|
||||
|
||||
@ -46,7 +45,5 @@ const DateFormatter = createDateFormatter("ru");
|
||||
}
|
||||
</header>
|
||||
<slot />
|
||||
<footer>
|
||||
<h2>Comments</h2>
|
||||
</footer>
|
||||
<ArticleComments />
|
||||
</article>
|
||||
|
||||
@ -6,6 +6,7 @@ import UIString from "@components/UIString";
|
||||
import NavToggle from "./NavToggle";
|
||||
import "./Header.scss";
|
||||
import TranslatedLink from "@components/TranslatedLink";
|
||||
import LanguageSelect from "@components/LanguageSelect";
|
||||
|
||||
const { name, nav, contacts } = SITE_METADATA;
|
||||
const { pathname } = Astro.url;
|
||||
@ -45,7 +46,7 @@ const { pathname } = Astro.url;
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex header__nav-settings">
|
||||
<Icon name="translate" width="21" height="21" />
|
||||
<LanguageSelect />
|
||||
<ThemeSwitcher client:visible />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -77,6 +77,8 @@
|
||||
}
|
||||
|
||||
body[data-is-mobile-open="true"] {
|
||||
overflow-y: hidden;
|
||||
touch-action: none;
|
||||
& .header {
|
||||
padding: 15px 0;
|
||||
background-color: var(--background-header-color);
|
||||
@ -121,12 +123,22 @@
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
right: 2.5rem;
|
||||
right: 2.2rem;
|
||||
& svg {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
& .language-select {
|
||||
position: absolute;
|
||||
bottom: 115px;
|
||||
right: 2.2rem;
|
||||
& svg,
|
||||
& select {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
src/components/LanguageSelect/LanguageSelect.astro
Normal file
59
src/components/LanguageSelect/LanguageSelect.astro
Normal 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>
|
||||
1
src/components/LanguageSelect/index.ts
Normal file
1
src/components/LanguageSelect/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./LanguageSelect.astro";
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import { getLanguageFromURL } from "@root/utils";
|
||||
import { Icon } from "astro-icon";
|
||||
import UIString from "../UIString";
|
||||
@ -23,7 +23,7 @@ const { icon, name, year, description, articleUrl, sourceUrl, demoUrl } = data;
|
||||
<span>{year}</span>
|
||||
</div>
|
||||
<div class="project-item__description">
|
||||
<p>{description[lang] || description[FALLBACK_LANGUAGE]}</p>
|
||||
<p>{description[lang] || description[DEFAULT_LANGUAGE]}</p>
|
||||
</div>
|
||||
<div class="project-item__links">
|
||||
{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import { getLanguageFromURL } from "@root/utils";
|
||||
import { Icon } from "astro-icon";
|
||||
import "./SkillsBlock.scss";
|
||||
@ -16,7 +16,7 @@ const {
|
||||
|
||||
<div class="skills-block">
|
||||
<div>
|
||||
<h3>{title[lang] || title[FALLBACK_LANGUAGE]}</h3>
|
||||
<h3>{title[lang] || title[DEFAULT_LANGUAGE]}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import { getLanguageFromURL } from "@root/utils";
|
||||
|
||||
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 { 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>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export const FALLBACK_LANGUAGE = "ru";
|
||||
export const DEFAULT_LANGUAGE: LanguageKeys = "ru";
|
||||
|
||||
export const SITE_METADATA: SiteMetadataType = {
|
||||
title: "Sergey Yarkov",
|
||||
|
||||
@ -3,6 +3,7 @@ export default {
|
||||
"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.",
|
||||
"articles.empty": "No articles found...",
|
||||
"articlePost.comments": "Comments",
|
||||
"recentArticles.empty": "There is nothing here yet...",
|
||||
/* Page titles */
|
||||
"pageTitle.about": "About me",
|
||||
|
||||
@ -3,6 +3,7 @@ export default {
|
||||
"site.description":
|
||||
"Здесь я публикую статьи и туториалы на различные IT темы, а также рассказываю о своих проектах которые я сделал за последние несколько лет.",
|
||||
"articles.empty": "Не найдено ни одной статьи...",
|
||||
"articlePost.comments": "Комментарии",
|
||||
"recentArticles.empty": "Пока что здесь ничего нет...",
|
||||
/* Page titles */
|
||||
"pageTitle.about": "Обо мне",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { AstroGlobal } from "astro";
|
||||
import * as utils from "@utils/url";
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import languages from "./languages";
|
||||
|
||||
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 {
|
||||
const lang = utils.getLanguageFromURL(Astro.url.pathname);
|
||||
return function translate(key: UIDictionaryKeys): string | undefined {
|
||||
const string: string | undefined = translations[lang]?.[key] || translations[FALLBACK_LANGUAGE][key];
|
||||
if (string === undefined) console.error(`Cannot find any string for translation key "${key}" on "${Astro.url.pathname}" page.`);
|
||||
const string: string | undefined = translations[lang]?.[key] || translations[DEFAULT_LANGUAGE][key];
|
||||
if (string === undefined) console.log(`Cannot find any string for translation key "${key}" on "${Astro.url.pathname}" page.`);
|
||||
return string;
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 BaseLayout from '@components/BaseLayout';
|
||||
import ArticleContent from '@components/ArticleContent';
|
||||
import ArticlePost from '@components/ArticlePost';
|
||||
import '@styles/global.scss';
|
||||
import BaseHead from "@components/BaseHead.astro";
|
||||
import BaseLayout from "@components/BaseLayout";
|
||||
import ArticleContent from "@components/ArticleContent";
|
||||
import ArticlePost from "@components/ArticlePost";
|
||||
import "@styles/global.scss";
|
||||
|
||||
export interface Props extends BaseHeadProps {
|
||||
lang?: string | undefined;
|
||||
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>
|
||||
<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>
|
||||
</head>
|
||||
|
||||
<BaseLayout>
|
||||
<div class='container'>
|
||||
<ArticlePost data={content}>
|
||||
<div class="container">
|
||||
<ArticlePost lang={lang} data={content}>
|
||||
<ArticleContent>
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</ArticleContent>
|
||||
</ArticlePost>
|
||||
</div>
|
||||
|
||||
@ -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 BaseHead from '@components/BaseHead.astro';
|
||||
import BaseLayout from '@components/BaseLayout';
|
||||
import PageHeading from '@components/PageHeading';
|
||||
import '@styles/global.scss';
|
||||
import { SITE_METADATA } from "@root/config";
|
||||
import BaseHead from "@components/BaseHead.astro";
|
||||
import BaseLayout from "@components/BaseLayout";
|
||||
import PageHeading from "@components/PageHeading";
|
||||
import "@styles/global.scss";
|
||||
|
||||
export interface Props extends BaseHeadProps {
|
||||
lang?: string | 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>
|
||||
<BaseHead title={title ? `${title} | ${SITE_METADATA.title}` : SITE_METADATA.title} description={description} />
|
||||
</head>
|
||||
|
||||
<BaseLayout>
|
||||
<div class='container'>
|
||||
<div class="container">
|
||||
{showPageHeading && <PageHeading heading={title} description={description} />}
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
</html>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
---
|
||||
import { getLanguageKeys } from "@i18n/utils";
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import About from "@templates/About.astro";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguageKeys()
|
||||
.filter((lang) => lang !== FALLBACK_LANGUAGE)
|
||||
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||
.map((lang) => ({ params: { lang } }));
|
||||
}
|
||||
---
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
---
|
||||
import { getLanguageKeys } from "@i18n/utils";
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import Articles from "@templates/Articles.astro";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguageKeys()
|
||||
.filter((lang) => lang !== FALLBACK_LANGUAGE)
|
||||
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||
.map((lang) => ({ params: { lang } }));
|
||||
}
|
||||
---
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
---
|
||||
import { getLanguageKeys } from "@i18n/utils";
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import Contacts from "@templates/Contacts.astro";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguageKeys()
|
||||
.filter((lang) => lang !== FALLBACK_LANGUAGE)
|
||||
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||
.map((lang) => ({ params: { lang } }));
|
||||
}
|
||||
---
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
---
|
||||
import { getLanguageKeys } from "@i18n/utils";
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import Home from "@templates/Home.astro";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguageKeys()
|
||||
.filter((lang) => lang !== FALLBACK_LANGUAGE)
|
||||
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||
.map((lang) => ({ params: { lang } }));
|
||||
}
|
||||
---
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
---
|
||||
import { getLanguageKeys } from "@i18n/utils";
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import Projects from "@templates/Projects.astro";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguageKeys()
|
||||
.filter((lang) => lang !== FALLBACK_LANGUAGE)
|
||||
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||
.map((lang) => ({ params: { lang } }));
|
||||
}
|
||||
---
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
import type { MDXInstance } from "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() {
|
||||
const data = await Astro.glob<ArticleType>("../../../content/articles/**/*.mdx");
|
||||
return data.map((article) => {
|
||||
@ -16,6 +18,6 @@ export async function getStaticPaths() {
|
||||
const { Content, frontmatter } = Astro.props as MDXInstance<ArticleType>;
|
||||
---
|
||||
|
||||
<ArticleLayout content={frontmatter}>
|
||||
<ArticleLayout lang={lang} content={frontmatter}>
|
||||
<Content />
|
||||
</ArticleLayout>
|
||||
|
||||
@ -13,10 +13,6 @@ body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body[data-is-mobile-open="true"] {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FALLBACK_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
|
||||
export const encode = (data: object) => {
|
||||
return Object.keys(data)
|
||||
@ -8,5 +8,5 @@ export const encode = (data: object) => {
|
||||
|
||||
export const getLanguageFromURL = (pathname: string) => {
|
||||
const langCodeMatch = pathname.match(/\/([a-z]{1}-?[a-z]{1})\//);
|
||||
return langCodeMatch ? langCodeMatch[1] : FALLBACK_LANGUAGE;
|
||||
return langCodeMatch ? langCodeMatch[1] : DEFAULT_LANGUAGE;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user