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 Header from "@components/Header/Header.astro";
|
||||
import Header from "@components/Header.astro";
|
||||
import Footer from "@components/Footer.astro";
|
||||
import SiteContent from "@components/SiteContent.astro";
|
||||
import UnderConstruction from "./UnderConstruction.astro";
|
||||
@ -9,7 +9,9 @@ import UnderConstruction from "./UnderConstruction.astro";
|
||||
<body>
|
||||
{
|
||||
IS_SITE_UNDER_CONSTRUCTION && (
|
||||
<UnderConstruction>🚧 This web site is under development 🚧</UnderConstruction>
|
||||
<UnderConstruction>
|
||||
🚧 This web site is under development 🚧
|
||||
</UnderConstruction>
|
||||
)
|
||||
}
|
||||
<Header />
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
---
|
||||
import UIString from "@components/UIString.astro";
|
||||
---
|
||||
|
||||
<div>
|
||||
<p>Не найдено ни одной записи.</p>
|
||||
<p><UIString key={"recentArticles.empty"} /></p>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -4,55 +4,51 @@ import { IS_SITE_UNDER_CONSTRUCTION, SITE_METADATA } from "@root/constants";
|
||||
import ThemeSwitcher from "@components/ThemeSwticher";
|
||||
import LanguageSelect from "@components/LanguageSelect.astro";
|
||||
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;' } : {})}>
|
||||
<div class="container">
|
||||
<div class="logo">
|
||||
<a href={"/"}>{SITE_METADATA.name}</a>
|
||||
<TranslatedLink to="/">
|
||||
{SITE_METADATA.name}
|
||||
</TranslatedLink>
|
||||
</div>
|
||||
<nav itemscope itemtype="http://schema.org/SiteNavigationElement">
|
||||
<ul>
|
||||
{
|
||||
(
|
||||
SITE_METADATA.nav.map((link) => (
|
||||
<li>
|
||||
<HeaderLink itemprop={"url"} href={"/articles"}>
|
||||
статьи
|
||||
</HeaderLink>
|
||||
<TranslatedLink
|
||||
itemprop={"url"}
|
||||
to={link.url}
|
||||
class:list={[{ active: link.url === Astro.url.pathname }]}
|
||||
>
|
||||
<UIString key={link.key} />
|
||||
</TranslatedLink>
|
||||
</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>
|
||||
{SITE_METADATA.contacts && <div class="contacts">
|
||||
<a target="_blank" href={"mailto:" + SITE_METADATA.contacts.email}>
|
||||
Email
|
||||
<Icon name="email" width="18" height="18" />
|
||||
</a>
|
||||
<a target="_blank" href={SITE_METADATA.contacts.github}>
|
||||
Github
|
||||
<Icon name="github_filled" width="18" height="18" />
|
||||
</a>
|
||||
<a target="_blank" href={SITE_METADATA.contacts.telegram}>
|
||||
Telegram
|
||||
<Icon name="telegram" width="18" height="18" />
|
||||
</a>
|
||||
</div>}
|
||||
{
|
||||
SITE_METADATA.contacts && (
|
||||
<div class="contacts">
|
||||
<a target="_blank" href={"mailto:" + SITE_METADATA.contacts.email}>
|
||||
Email
|
||||
<Icon name="email" width="18" height="18" />
|
||||
</a>
|
||||
<a target="_blank" href={SITE_METADATA.contacts.github}>
|
||||
Github
|
||||
<Icon name="github_filled" width="18" height="18" />
|
||||
</a>
|
||||
<a target="_blank" href={SITE_METADATA.contacts.telegram}>
|
||||
Telegram
|
||||
<Icon name="telegram" width="18" height="18" />
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div class="flex settings">
|
||||
<LanguageSelect />
|
||||
<ThemeSwitcher client:visible />
|
||||
@ -74,7 +70,7 @@ import HeaderLink from "./HeaderLink.astro";
|
||||
z-index: 9999;
|
||||
& .container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
& .logo a {
|
||||
@ -157,7 +153,7 @@ import HeaderLink from "./HeaderLink.astro";
|
||||
display: block;
|
||||
& ul {
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
& li {
|
||||
margin-bottom: 30px;
|
||||
@ -168,8 +164,14 @@ import HeaderLink from "./HeaderLink.astro";
|
||||
}
|
||||
& .contacts {
|
||||
margin-right: 0;
|
||||
margin-top: 2rem;
|
||||
justify-content: center;
|
||||
margin-top: 2.5rem;
|
||||
& 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 { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants";
|
||||
import { URL_BLOG_PREFIX } from "@root/pages/[...blog].astro";
|
||||
import { getLanguageFromURL } from "@i18n/utils";
|
||||
|
||||
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||
@ -19,6 +20,35 @@ const lang = getLanguageFromURL(Astro.url.pathname);
|
||||
</select>
|
||||
</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">
|
||||
div {
|
||||
position: relative;
|
||||
@ -63,31 +93,3 @@ const lang = getLanguageFromURL(Astro.url.pathname);
|
||||
}
|
||||
}
|
||||
</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 siteconfig from "siteconfig.json";
|
||||
import ArticleItem from "@components/ArticleItem.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 {
|
||||
articles: CollectionEntry<"blog">[];
|
||||
length?: number | undefined;
|
||||
}
|
||||
|
||||
const { articles, length = siteconfig.articles.recentLimit } = Astro.props as Props;
|
||||
const isEmpty = articles.length === 0;
|
||||
---
|
||||
|
||||
<section>
|
||||
<div>
|
||||
<h2>недавние публикации</h2>
|
||||
<a href="/articles/">смотреть все</a>
|
||||
<h2><UIString key={"recentArticles.heading"} /></h2>
|
||||
<TranslatedLink to="/articles">
|
||||
<UIString key={"recentArticles.all"} />
|
||||
</TranslatedLink>
|
||||
</div>
|
||||
|
||||
<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",
|
||||
themeColor: "#1e2327",
|
||||
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 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> = {};
|
||||
for (const [path, module] of Object.entries(modules)) {
|
||||
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;
|
||||
if (Object.hasOwn(languages, lang)) return lang;
|
||||
return DEFAULT_LANGUAGE;
|
||||
};
|
||||
}
|
||||
|
||||
export const removeLangFromURL = (pathname: string): string => {
|
||||
export function removeLangFromURL(pathname: string): string {
|
||||
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 PageLayout from "@layouts/default.astro";
|
||||
import Splash from "@components/Splash/Splash.astro";
|
||||
import SplashHeading from "@root/components/Splash/SplashHeading.astro";
|
||||
import SplashLinks from "@root/components/Splash/SplashLinks.astro";
|
||||
import SplashLink from "@root/components/Splash/SplashLink.astro";
|
||||
import RecentArticles from "@root/components/RecentArticles.astro";
|
||||
import SplashHeading from "@components/Splash/SplashHeading.astro";
|
||||
import SplashLinks from "@components/Splash/SplashLinks.astro";
|
||||
import SplashLink from "@components/Splash/SplashLink.astro";
|
||||
import RecentArticles from "@components/RecentArticles.astro";
|
||||
import UIString from "@components/UIString.astro";
|
||||
|
||||
const articles = (await getCollection("blog", (e) => !e.data.draft)).sort(
|
||||
(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>
|
||||
<SplashHeading>
|
||||
<hgroup>
|
||||
<h1>Hello, I'am Sergey</h1>
|
||||
<h1><UIString key={"splash.headline"} /></h1>
|
||||
<h2>Fullstack <b>web-developer</b> & <b>freelancer</b></h2>
|
||||
</hgroup>
|
||||
<p>
|
||||
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую
|
||||
статьи и туториалы на разные IT темы.
|
||||
</p>
|
||||
<p><UIString key={"splash.description"} /></p>
|
||||
</SplashHeading>
|
||||
<SplashLinks>
|
||||
<SplashLink icon="information" href="/about/">обо мне</SplashLink>
|
||||
<SplashLink icon="email_filled" href="/contacts/">написать мне</SplashLink>
|
||||
<SplashLink icon="information" href="/about/"><UIString key={"splash.about"} /></SplashLink>
|
||||
<SplashLink icon="email_filled" href="/contacts/">
|
||||
<UIString key={"splash.contact"} />
|
||||
</SplashLink>
|
||||
</SplashLinks>
|
||||
</Splash>
|
||||
<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 uiKeys from "@i18n/ru/ui";
|
||||
|
||||
export {};
|
||||
|
||||
@ -31,5 +32,6 @@ declare global {
|
||||
telegram?: string;
|
||||
github?: string;
|
||||
};
|
||||
nav: Array<{ key: UIDictionaryKeys; url: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user