This commit is contained in:
Sergey Yarkov 2023-02-18 13:01:19 +03:00
parent 998a229d7a
commit cb0ae84515
No known key found for this signature in database
GPG Key ID: E91CE1FABC619B6F
12 changed files with 155 additions and 104 deletions

View File

@ -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>&#128679;&nbsp; This web site is under development &nbsp;&#128679;</UnderConstruction> <UnderConstruction>
&#128679;&nbsp; This web site is under development &nbsp;&#128679;
</UnderConstruction>
) )
} }
<Header /> <Header />

View File

@ -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">

View File

@ -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;
}
} }
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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">

View 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>

View File

@ -0,0 +1,9 @@
---
import { useTranslation } from "@i18n/utils";
export interface Props {
key: UIDictionaryKeys;
}
---
{useTranslation(Astro)(Astro.props.key)}

View File

@ -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/" },
],
}; };

View File

@ -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);
}

View File

@ -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
View File

@ -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 }>;
}; };
} }