Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Сборка сайта на каждый PR в main; сборка и выкладка на GitHub Pages — на каждый пуш в main.
# Выкладывать в окружение github-pages разрешено только из main (настройки окружения).
name: Build and deploy

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

# PR: новая сборка отменяет устаревшую. main: деплои идут по очереди, не прерываясь.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm

- run: npm ci

- run: npm run build
env:
NEXT_TELEMETRY_DISABLED: 1

# Эти адреса прописаны в App Store / Google Play и в самих приложениях.
# 22.09.2026 политика Hytaý dili 1 уже пропадала вместе с перестройкой сайта.
- name: Check pages the stores depend on
run: |
for f in index.html ru/index.html en/index.html projects/index.html 404.html \
hytay-dili-1/privacy-policy.html sitemap.xml robots.txt; do
test -s "out/$f" || { echo "::error::out/$f is missing"; exit 1; }
done

- uses: actions/upload-pages-artifact@v5
if: github.event_name != 'pull_request'
with:
path: out

deploy:
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
.next/
out/
next-env.d.ts
*.tsbuildinfo
33 changes: 0 additions & 33 deletions 404.html

This file was deleted.

12 changes: 12 additions & 0 deletions app/(tk)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Metadata, Viewport } from "next";
import type { ReactNode } from "react";
import { Document, baseMetadata, baseViewport } from "@/lib/document";
import "../globals.css";

// Туркменский — язык по умолчанию, живёт в корне сайта без префикса.
export const metadata: Metadata = baseMetadata;
export const viewport: Viewport = baseViewport;

export default function TurkmenLayout({ children }: { children: ReactNode }) {
return <Document lang="tk">{children}</Document>;
}
7 changes: 7 additions & 0 deletions app/(tk)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { HomePage, homeMetadata } from "@/components/HomePage";

export const metadata = homeMetadata("tk");

export default function Page() {
return <HomePage lang="tk" />;
}
7 changes: 7 additions & 0 deletions app/(tk)/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ProjectsPage, projectsMetadata } from "@/components/ProjectsPage";

export const metadata = projectsMetadata("tk");

export default function Page() {
return <ProjectsPage lang="tk" />;
}
21 changes: 21 additions & 0 deletions app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Metadata, Viewport } from "next";
import type { ReactNode } from "react";
import { notFound } from "next/navigation";
import { Document, baseMetadata, baseViewport } from "@/lib/document";
import { PREFIXED_LANGS, isLang } from "@/lib/i18n";
import "../globals.css";

// Статический экспорт: собираются только /ru/ и /en/, других языков нет.
export const dynamicParams = false;
export function generateStaticParams() {
return PREFIXED_LANGS.map((lang) => ({ lang }));
}

export const metadata: Metadata = baseMetadata;
export const viewport: Viewport = baseViewport;

export default async function LangLayout({ children, params }: { children: ReactNode; params: Promise<{ lang: string }> }) {
const { lang } = await params;
if (!isLang(lang)) notFound();
return <Document lang={lang}>{children}</Document>;
}
16 changes: 16 additions & 0 deletions app/[lang]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { notFound } from "next/navigation";
import { HomePage, homeMetadata } from "@/components/HomePage";
import { isLang } from "@/lib/i18n";

type Props = { params: Promise<{ lang: string }> };

export async function generateMetadata({ params }: Props) {
const { lang } = await params;
return isLang(lang) ? homeMetadata(lang) : {};
}

export default async function Page({ params }: Props) {
const { lang } = await params;
if (!isLang(lang)) notFound();
return <HomePage lang={lang} />;
}
16 changes: 16 additions & 0 deletions app/[lang]/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { notFound } from "next/navigation";
import { ProjectsPage, projectsMetadata } from "@/components/ProjectsPage";
import { isLang } from "@/lib/i18n";

type Props = { params: Promise<{ lang: string }> };

export async function generateMetadata({ params }: Props) {
const { lang } = await params;
return isLang(lang) ? projectsMetadata(lang) : {};
}

export default async function Page({ params }: Props) {
const { lang } = await params;
if (!isLang(lang)) notFound();
return <ProjectsPage lang={lang} />;
}
37 changes: 37 additions & 0 deletions app/global-not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Metadata, Viewport } from "next";
import { baseViewport } from "@/lib/document";
import "./globals.css";
import "@/components/not-found.css";

// Одна 404 на весь сайт: у сайта две корневые раскладки, поэтому обычный
// app/not-found.tsx собрать не из чего. Язык запроса неизвестен — текст на трёх.
export const metadata: Metadata = {
title: "404 — Şapak Apps",
robots: { index: false },
icons: { icon: "/icon.png" },
};
export const viewport: Viewport = baseViewport;

export default function GlobalNotFound() {
return (
<html lang="tk">
<body>
<div className="wrap nf">
<a className="brand" href="/">
<img src="/logo.svg" alt="şapak" width={788} height={204} />
</a>
<h1>
Sahypa tapylmady <span>· Страница не найдена · Page not found</span>
</h1>
<p>Bu salgy boýunça sahypa ýok. Baş sahypa geçiň ýa-da GitHub-daky repozitoriýalara serediň.</p>
<p lang="ru">Страницы по этому адресу нет. Перейдите на главную или посмотрите репозитории на GitHub.</p>
<p lang="en">There is no page at this address. Go to the home page or look at the repositories on GitHub.</p>
<div className="ctas">
<a className="cta" href="/">Baş sahypa · Главная · Home →</a>
<a className="cta quiet" href="https://github.com/Shapak-Apps">github.com/Shapak-Apps</a>
</div>
</div>
</body>
</html>
);
}
17 changes: 6 additions & 11 deletions assets/site.css → app/globals.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Şapak Apps — общие стили всех страниц. Стили конкретной страницы — в её <style>. */
/* Şapak Apps — общие стили всех страниц. Стили конкретной страницы — в components/<page>.css. */
:root{
--bg:#FBFAF8; --fg:#17150F; --muted:#6E675B; --rule:#E3DFD6;
--brand:#FF851B;
Expand All @@ -24,11 +24,6 @@ img{max-width:100%}
:focus-visible{outline:2px solid var(--accent); outline-offset:3px; border-radius:2px}
.wrap{max-width:1120px; margin:0 auto; padding:0 48px 80px}

/* ---------- языки: виден только выбранный ---------- */
[data-lang="tk"] [lang="en"],[data-lang="tk"] [lang="ru"],
[data-lang="en"] [lang="tk"],[data-lang="en"] [lang="ru"],
[data-lang="ru"] [lang="tk"],[data-lang="ru"] [lang="en"]{display:none}

/* ---------- шапка ---------- */
.masthead{
display:flex; align-items:center; gap:24px 36px; padding:34px 0 0; flex-wrap:wrap;
Expand All @@ -40,12 +35,12 @@ img{max-width:100%}
.nav a:hover,.nav a[aria-current="page"]{color:var(--fg)}

.langsel{display:flex; gap:2px; font-family:var(--mono); font-size:11px}
.langsel button{
appearance:none; border:0; background:none; cursor:pointer; color:var(--muted);
font:inherit; letter-spacing:.09em; padding:5px 7px; border-radius:3px;
.langsel a{
color:var(--muted); text-decoration:none;
letter-spacing:.09em; padding:5px 7px; border-radius:3px;
}
.langsel button:hover{color:var(--fg)}
.langsel button[aria-pressed="true"]{color:var(--fg); background:var(--accent-soft)}
.langsel a:hover{color:var(--fg)}
.langsel a[aria-current="true"]{color:var(--fg); background:var(--accent-soft)}

/* ---------- общие блоки ---------- */
.eyebrow{
Expand Down
24 changes: 24 additions & 0 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { MetadataRoute } from "next";
import { LANGS, SITE_URL, languageAlternates, localePath } from "@/lib/i18n";
import { PRIVACY_URLS } from "@/content/site";

// Статический экспорт: sitemap.xml собирается один раз при сборке.
export const dynamic = "force-static";

/** Страницы, которые есть на всех трёх языках. Новая страница — добавить сюда. */
const PAGES = ["/", "/projects/"];

const abs = (path: string) => `${SITE_URL}${path}`;

export default function sitemap(): MetadataRoute.Sitemap {
const pages = PAGES.flatMap((path) =>
LANGS.map((lang) => ({
url: abs(localePath(lang, path)),
alternates: {
languages: Object.fromEntries(Object.entries(languageAlternates(path)).map(([l, p]) => [l, abs(p)])),
},
})),
);
// Политика Hytaý dili 1 — единственная, что лежит в этом репозитории.
return [...pages, { url: abs(PRIVACY_URLS.hytay) }];
}
69 changes: 0 additions & 69 deletions assets/site.js

This file was deleted.

16 changes: 16 additions & 0 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Lang } from "@/lib/i18n";
import { EMAIL, ORG_URL, PRIVACY_URLS, footer } from "@/content/site";

export function Footer({ lang }: { lang: Lang }) {
return (
<footer>
<div className="meta">
<a href={ORG_URL}>github.com/Shapak-Apps</a>
<a href={`mailto:${EMAIL}`}>{EMAIL}</a>
<a href={PRIVACY_URLS.ykjam}>{footer.privacyYkjam[lang]}</a>
<a href={PRIVACY_URLS.hytay}>{footer.privacyHytay[lang]}</a>
<span>Türkmenistan</span>
</div>
</footer>
);
}
Loading
Loading