mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 18:39:14 +03:00
i18n
This commit is contained in:
parent
998a229d7a
commit
cb0ae84515
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { IS_SITE_UNDER_CONSTRUCTION } from "@root/constants";
|
import { IS_SITE_UNDER_CONSTRUCTION } from "@root/constants";
|
||||||
import Header from "@components/Header/Header.astro";
|
import Header from "@components/Header.astro";
|
||||||
import Footer from "@components/Footer.astro";
|
import Footer from "@components/Footer.astro";
|
||||||
import SiteContent from "@components/SiteContent.astro";
|
import SiteContent from "@components/SiteContent.astro";
|
||||||
import UnderConstruction from "./UnderConstruction.astro";
|
import UnderConstruction from "./UnderConstruction.astro";
|
||||||
@ -9,7 +9,9 @@ import UnderConstruction from "./UnderConstruction.astro";
|
|||||||
<body>
|
<body>
|
||||||
{
|
{
|
||||||
IS_SITE_UNDER_CONSTRUCTION && (
|
IS_SITE_UNDER_CONSTRUCTION && (
|
||||||
<UnderConstruction>🚧 This web site is under development 🚧</UnderConstruction>
|
<UnderConstruction>
|
||||||
|
🚧 This web site is under development 🚧
|
||||||
|
</UnderConstruction>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Header />
|
<Header />
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
---
|
---
|
||||||
|
import UIString from "@components/UIString.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>Не найдено ни одной записи.</p>
|
<p><UIString key={"recentArticles.empty"} /></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@ -4,55 +4,51 @@ import { IS_SITE_UNDER_CONSTRUCTION, SITE_METADATA } from "@root/constants";
|
|||||||
import ThemeSwitcher from "@components/ThemeSwticher";
|
import ThemeSwitcher from "@components/ThemeSwticher";
|
||||||
import LanguageSelect from "@components/LanguageSelect.astro";
|
import LanguageSelect from "@components/LanguageSelect.astro";
|
||||||
import NavToggle from "@components/NavToggle";
|
import NavToggle from "@components/NavToggle";
|
||||||
import HeaderLink from "./HeaderLink.astro";
|
import UIString from "@components/UIString.astro";
|
||||||
|
import TranslatedLink from "@components/TranslatedLink.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<header {...(IS_SITE_UNDER_CONSTRUCTION ? { style: 'top: 45px;' } : {})}>
|
<header {...(IS_SITE_UNDER_CONSTRUCTION ? { style: 'top: 45px;' } : {})}>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<a href={"/"}>{SITE_METADATA.name}</a>
|
<TranslatedLink to="/">
|
||||||
|
{SITE_METADATA.name}
|
||||||
|
</TranslatedLink>
|
||||||
</div>
|
</div>
|
||||||
<nav itemscope itemtype="http://schema.org/SiteNavigationElement">
|
<nav itemscope itemtype="http://schema.org/SiteNavigationElement">
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
(
|
SITE_METADATA.nav.map((link) => (
|
||||||
<li>
|
<li>
|
||||||
<HeaderLink itemprop={"url"} href={"/articles"}>
|
<TranslatedLink
|
||||||
статьи
|
itemprop={"url"}
|
||||||
</HeaderLink>
|
to={link.url}
|
||||||
|
class:list={[{ active: link.url === Astro.url.pathname }]}
|
||||||
|
>
|
||||||
|
<UIString key={link.key} />
|
||||||
|
</TranslatedLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
))
|
||||||
<HeaderLink itemprop={"url"} href={"/projects"}>
|
|
||||||
проекты
|
|
||||||
</HeaderLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<HeaderLink itemprop={"url"} href={"/about"}>
|
|
||||||
обо мне
|
|
||||||
</HeaderLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<HeaderLink itemprop={"url"} href={"/contacts"}>
|
|
||||||
контакты
|
|
||||||
</HeaderLink>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
{SITE_METADATA.contacts && <div class="contacts">
|
{
|
||||||
<a target="_blank" href={"mailto:" + SITE_METADATA.contacts.email}>
|
SITE_METADATA.contacts && (
|
||||||
Email
|
<div class="contacts">
|
||||||
<Icon name="email" width="18" height="18" />
|
<a target="_blank" href={"mailto:" + SITE_METADATA.contacts.email}>
|
||||||
</a>
|
Email
|
||||||
<a target="_blank" href={SITE_METADATA.contacts.github}>
|
<Icon name="email" width="18" height="18" />
|
||||||
Github
|
</a>
|
||||||
<Icon name="github_filled" width="18" height="18" />
|
<a target="_blank" href={SITE_METADATA.contacts.github}>
|
||||||
</a>
|
Github
|
||||||
<a target="_blank" href={SITE_METADATA.contacts.telegram}>
|
<Icon name="github_filled" width="18" height="18" />
|
||||||
Telegram
|
</a>
|
||||||
<Icon name="telegram" width="18" height="18" />
|
<a target="_blank" href={SITE_METADATA.contacts.telegram}>
|
||||||
</a>
|
Telegram
|
||||||
</div>}
|
<Icon name="telegram" width="18" height="18" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
<div class="flex settings">
|
<div class="flex settings">
|
||||||
<LanguageSelect />
|
<LanguageSelect />
|
||||||
<ThemeSwitcher client:visible />
|
<ThemeSwitcher client:visible />
|
||||||
@ -74,7 +70,7 @@ import HeaderLink from "./HeaderLink.astro";
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
& .container {
|
& .container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
& .logo a {
|
& .logo a {
|
||||||
@ -157,7 +153,7 @@ import HeaderLink from "./HeaderLink.astro";
|
|||||||
display: block;
|
display: block;
|
||||||
& ul {
|
& ul {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
& li {
|
& li {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
@ -168,8 +164,14 @@ import HeaderLink from "./HeaderLink.astro";
|
|||||||
}
|
}
|
||||||
& .contacts {
|
& .contacts {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-top: 2rem;
|
margin-top: 2.5rem;
|
||||||
justify-content: center;
|
& a {
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
}
|
||||||
|
& svg {
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
export interface Props extends astroHTML.JSX.AnchorHTMLAttributes {}
|
|
||||||
|
|
||||||
const { href, class: className, ...props } = Astro.props;
|
|
||||||
|
|
||||||
const { pathname } = Astro.url;
|
|
||||||
const isActive = href === pathname || href === pathname.replace(/\/$/, "");
|
|
||||||
---
|
|
||||||
|
|
||||||
<a href={href} class:list={[className, { active: isActive }]} {...props}>
|
|
||||||
<slot />
|
|
||||||
</a>
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from "astro-icon";
|
import { Icon } from "astro-icon";
|
||||||
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants";
|
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants";
|
||||||
|
import { URL_BLOG_PREFIX } from "@root/pages/[...blog].astro";
|
||||||
import { getLanguageFromURL } from "@i18n/utils";
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
|
||||||
const lang = getLanguageFromURL(Astro.url.pathname);
|
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
@ -19,6 +20,35 @@ const lang = getLanguageFromURL(Astro.url.pathname);
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script define:vars={{ lang, DEFAULT_LANGUAGE, URL_BLOG_PREFIX }}>
|
||||||
|
const $select = document.getElementById("languageSelect");
|
||||||
|
|
||||||
|
const changeLanguageHandler = (e) => {
|
||||||
|
const newLang = e.target.value;
|
||||||
|
const pathname = window.location.pathname;
|
||||||
|
let url;
|
||||||
|
|
||||||
|
if (lang !== DEFAULT_LANGUAGE) {
|
||||||
|
url = pathname.split("/").slice(2).join("/");
|
||||||
|
} else {
|
||||||
|
url = pathname.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLang !== DEFAULT_LANGUAGE) url = `${newLang}/${url}`;
|
||||||
|
|
||||||
|
/* Move user to articles page with new language */
|
||||||
|
if (window.location.pathname.includes(`/${URL_BLOG_PREFIX}/`)) {
|
||||||
|
window.location.pathname =
|
||||||
|
`${newLang === DEFAULT_LANGUAGE ? "" : newLang + "/"}` + "articles/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.pathname = url;
|
||||||
|
};
|
||||||
|
|
||||||
|
$select.addEventListener("change", changeLanguageHandler);
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div {
|
div {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -63,31 +93,3 @@ const lang = getLanguageFromURL(Astro.url.pathname);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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;
|
|
||||||
let url;
|
|
||||||
|
|
||||||
if (lang !== DEFAULT_LANGUAGE) {
|
|
||||||
url = pathname.split("/").slice(2).join("/");
|
|
||||||
} else {
|
|
||||||
url = pathname.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newLang !== DEFAULT_LANGUAGE) url = `${newLang}/${url}`;
|
|
||||||
|
|
||||||
/* Move user to articles page with new language */
|
|
||||||
if (window.location.pathname.includes("/blog/")) {
|
|
||||||
window.location.pathname = `${newLang === DEFAULT_LANGUAGE ? "" : newLang + "/"}` + "articles/";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.pathname = url;
|
|
||||||
};
|
|
||||||
|
|
||||||
$select.addEventListener("change", changeLanguageHandler);
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,20 +1,25 @@
|
|||||||
---
|
---
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
import siteconfig from "siteconfig.json";
|
||||||
import ArticleItem from "@components/ArticleItem.astro";
|
import ArticleItem from "@components/ArticleItem.astro";
|
||||||
import EmptyList from "@components/EmptyList.astro";
|
import EmptyList from "@components/EmptyList.astro";
|
||||||
import siteconfig from "siteconfig.json";
|
import UIString from "@components/UIString.astro";
|
||||||
|
import TranslatedLink from "@components/TranslatedLink.astro";
|
||||||
export interface Props {
|
export interface Props {
|
||||||
articles: CollectionEntry<"blog">[];
|
articles: CollectionEntry<"blog">[];
|
||||||
length?: number | undefined;
|
length?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { articles, length = siteconfig.articles.recentLimit } = Astro.props as Props;
|
const { articles, length = siteconfig.articles.recentLimit } = Astro.props as Props;
|
||||||
const isEmpty = articles.length === 0;
|
const isEmpty = articles.length === 0;
|
||||||
---
|
---
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div>
|
<div>
|
||||||
<h2>недавние публикации</h2>
|
<h2><UIString key={"recentArticles.heading"} /></h2>
|
||||||
<a href="/articles/">смотреть все</a>
|
<TranslatedLink to="/articles">
|
||||||
|
<UIString key={"recentArticles.all"} />
|
||||||
|
</TranslatedLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div itemscope itemtype="http://schema.org/Blog">
|
<div itemscope itemtype="http://schema.org/Blog">
|
||||||
|
|||||||
16
src/components/TranslatedLink.astro
Normal file
16
src/components/TranslatedLink.astro
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants";
|
||||||
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
|
||||||
|
export interface Props extends astroHTML.JSX.AnchorHTMLAttributes {
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageLang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
const { to, ...attrs } = Astro.props as Props;
|
||||||
|
|
||||||
|
if (!SUPPORTED_LANGUAGES.includes(pageLang)) pageLang = DEFAULT_LANGUAGE;
|
||||||
|
const href = pageLang === DEFAULT_LANGUAGE ? to : `/${pageLang}${to}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a href={href} {...attrs}><slot /></a>
|
||||||
9
src/components/UIString.astro
Normal file
9
src/components/UIString.astro
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
import { useTranslation } from "@i18n/utils";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
key: UIDictionaryKeys;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
{useTranslation(Astro)(Astro.props.key)}
|
||||||
@ -9,4 +9,10 @@ export const SITE_METADATA: SiteMetadataType = {
|
|||||||
author: "Sergey Yarkov",
|
author: "Sergey Yarkov",
|
||||||
themeColor: "#1e2327",
|
themeColor: "#1e2327",
|
||||||
contacts: siteconfig.contacts,
|
contacts: siteconfig.contacts,
|
||||||
|
nav: [
|
||||||
|
{ key: "navigation.articles", url: "/articles/" },
|
||||||
|
{ key: "navigation.projects", url: "/projects/" },
|
||||||
|
{ key: "navigation.about", url: "/about/" },
|
||||||
|
{ key: "navigation.contacts", url: "/contacts/" },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import languages from "./languages";
|
import type { AstroGlobal } from "astro";
|
||||||
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants";
|
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants";
|
||||||
|
import languages from "./languages";
|
||||||
|
|
||||||
function transformExports<T>(modules: Record<string, { default: T }>) {
|
function transformExports<T>(modules: Record<string, { default: T }>): Record<string, string> {
|
||||||
const translations: Record<string, T> = {};
|
const translations: Record<string, T> = {};
|
||||||
for (const [path, module] of Object.entries(modules)) {
|
for (const [path, module] of Object.entries(modules)) {
|
||||||
const [, lang] = path.split("/");
|
const [, lang] = path.split("/");
|
||||||
@ -16,12 +17,29 @@ export function getLanguageKeys(): Array<string> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLanguageFromURL = (pathname: string): LanguageKeys => {
|
export function getLanguageFromURL(pathname: string): LanguageKeys {
|
||||||
const lang = pathname.split("/")[1] as LanguageKeys;
|
const lang = pathname.split("/")[1] as LanguageKeys;
|
||||||
if (Object.hasOwn(languages, lang)) return lang;
|
if (Object.hasOwn(languages, lang)) return lang;
|
||||||
return DEFAULT_LANGUAGE;
|
return DEFAULT_LANGUAGE;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const removeLangFromURL = (pathname: string): string => {
|
export function removeLangFromURL(pathname: string): string {
|
||||||
return pathname.split("/").slice(2).join("/");
|
return pathname.split("/").slice(2).join("/");
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const translations = transformExports(import.meta.glob("./*/ui.ts", { eager: true }));
|
||||||
|
|
||||||
|
const s = translations["ru"];
|
||||||
|
|
||||||
|
function translate(key: UIDictionaryKeys, lang: LanguageKeys): string | undefined {
|
||||||
|
const value = translations[lang]?.[key] || translations[DEFAULT_LANGUAGE][key];
|
||||||
|
if (value === undefined) console.log(`Cannot find any string for translation key "${key}".`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTranslation(
|
||||||
|
Astro: Readonly<AstroGlobal>
|
||||||
|
): (key: UIDictionaryKeys) => string | undefined {
|
||||||
|
const pageLang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
return (key: UIDictionaryKeys) => translate(key, pageLang);
|
||||||
|
}
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import PageLayout from "@layouts/default.astro";
|
import PageLayout from "@layouts/default.astro";
|
||||||
import Splash from "@components/Splash/Splash.astro";
|
import Splash from "@components/Splash/Splash.astro";
|
||||||
import SplashHeading from "@root/components/Splash/SplashHeading.astro";
|
import SplashHeading from "@components/Splash/SplashHeading.astro";
|
||||||
import SplashLinks from "@root/components/Splash/SplashLinks.astro";
|
import SplashLinks from "@components/Splash/SplashLinks.astro";
|
||||||
import SplashLink from "@root/components/Splash/SplashLink.astro";
|
import SplashLink from "@components/Splash/SplashLink.astro";
|
||||||
import RecentArticles from "@root/components/RecentArticles.astro";
|
import RecentArticles from "@components/RecentArticles.astro";
|
||||||
|
import UIString from "@components/UIString.astro";
|
||||||
|
|
||||||
const articles = (await getCollection("blog", (e) => !e.data.draft)).sort(
|
const articles = (await getCollection("blog", (e) => !e.data.draft)).sort(
|
||||||
(a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
|
(a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
|
||||||
@ -16,17 +17,16 @@ const articles = (await getCollection("blog", (e) => !e.data.draft)).sort(
|
|||||||
<Splash>
|
<Splash>
|
||||||
<SplashHeading>
|
<SplashHeading>
|
||||||
<hgroup>
|
<hgroup>
|
||||||
<h1>Hello, I'am Sergey</h1>
|
<h1><UIString key={"splash.headline"} /></h1>
|
||||||
<h2>Fullstack <b>web-developer</b> & <b>freelancer</b></h2>
|
<h2>Fullstack <b>web-developer</b> & <b>freelancer</b></h2>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
<p>
|
<p><UIString key={"splash.description"} /></p>
|
||||||
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую
|
|
||||||
статьи и туториалы на разные IT темы.
|
|
||||||
</p>
|
|
||||||
</SplashHeading>
|
</SplashHeading>
|
||||||
<SplashLinks>
|
<SplashLinks>
|
||||||
<SplashLink icon="information" href="/about/">обо мне</SplashLink>
|
<SplashLink icon="information" href="/about/"><UIString key={"splash.about"} /></SplashLink>
|
||||||
<SplashLink icon="email_filled" href="/contacts/">написать мне</SplashLink>
|
<SplashLink icon="email_filled" href="/contacts/">
|
||||||
|
<UIString key={"splash.contact"} />
|
||||||
|
</SplashLink>
|
||||||
</SplashLinks>
|
</SplashLinks>
|
||||||
</Splash>
|
</Splash>
|
||||||
<RecentArticles articles={articles} />
|
<RecentArticles articles={articles} />
|
||||||
|
|||||||
2
src/types/main.d.ts
vendored
2
src/types/main.d.ts
vendored
@ -1,4 +1,5 @@
|
|||||||
import languages from "@i18n/languages";
|
import languages from "@i18n/languages";
|
||||||
|
import uiKeys from "@i18n/ru/ui";
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
@ -31,5 +32,6 @@ declare global {
|
|||||||
telegram?: string;
|
telegram?: string;
|
||||||
github?: string;
|
github?: string;
|
||||||
};
|
};
|
||||||
|
nav: Array<{ key: UIDictionaryKeys; url: string }>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user