add translates

This commit is contained in:
Dmitriy Pleshevskiy 2022-05-28 23:28:07 +03:00
parent a6353699df
commit b918557791
13 changed files with 127 additions and 27 deletions

View File

@ -1,7 +1,8 @@
import { AnyNode, E } from "ren/node.ts";
import { Context } from "../context.ts";
export function Layout(page: AnyNode): AnyNode {
return E("html", { lang: "ru" }, [
export function Layout(ctx: Context, page: AnyNode): AnyNode {
return E("html", { lang: ctx.lang }, [
E("head", [], [
E("meta", { charset: "utf-8" }),
E("meta", {

View File

@ -1,12 +1,12 @@
import { AnyNode, Attrs, E, Elem } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../context.ts";
import { Context, Lang } from "../context.ts";
export function PageLayout(ctx: Context, children: AnyNode[]): Elem {
return E("div", { id: "main" }, [
Header(ctx),
E("div", classNames("content"), children),
Footer(),
Footer(ctx),
]);
}
@ -18,25 +18,29 @@ export function Header(ctx: Context): AnyNode {
export function HeaderNav(ctx: Context): AnyNode {
return E("nav", classNames("main-menu"), [
Link("Главная", navLink("/", ctx)),
Link("Рецепты", navLink("/recipes", ctx)),
Link("Ингредиенты", navLink("/ingredients", ctx)),
Link(ctx.tr.Home, navLink("/", ctx)),
Link(ctx.tr.Recipes, navLink("/recipes", ctx)),
Link(ctx.tr.Ingredients, navLink("/ingredients", ctx)),
]);
}
function navLink(href: string, ctx?: Context): Attrs {
const attrs: Attrs = { href };
if (ctx?.locPath === href) attrs["aria-current"] = "true";
function navLink(lhref: string, ctx?: Context): Attrs {
const attrs: Attrs = { lhref };
if (ctx?.locPath === lhref) attrs["aria-current"] = "true";
return attrs;
}
export function Footer(): AnyNode {
export function Footer(ctx: Context): AnyNode {
return E("footer", classNames("footer gap-v-1x5"), [
E("div", classNames("content-width"), [
Link("Исходный код", {
Link(ctx.tr.Source_code, {
href: "https://notabug.org/pleshevskiy/recipes",
rel: "external nofollow noopener noreferrer",
}),
E("div", [], [
ChangeLangBtn(ctx, Lang.Rus),
ChangeLangBtn(ctx, Lang.Eng),
]),
]),
]);
}
@ -44,3 +48,8 @@ export function Footer(): AnyNode {
export function Link(text: string, attrs: Attrs | Attrs[]): AnyNode {
return E("a", attrs, text);
}
export function ChangeLangBtn(ctx: Context, lang: Lang): AnyNode {
const prefix = lang === Lang.Rus ? "" : `/${lang}`;
return E("a", { "href": prefix + ctx.locPath }, lang);
}

View File

@ -1,3 +1,12 @@
import { Translations } from "./translates/rus.ts";
export interface Context {
locPath: string;
lang: Lang;
tr: Translations;
}
export enum Lang {
Rus = "rus",
Eng = "eng",
}

View File

@ -1,5 +1,6 @@
{
"imports": {
"ren/": "https://notabug.org/pleshevskiy/ren/raw/v2/ren/"
"ren/": "https://notabug.org/pleshevskiy/ren/raw/v2/ren/",
"ren/": "../../ren/ren/"
}
}

View File

@ -1,11 +1,15 @@
import { Lang } from "../../context.ts";
import { Ingredient } from "../../domain/ingredient/types.ts";
import { RestLang } from "../misc_types.ts";
import { IngredientRepo } from "./types.ts";
export class RestIngredientRepo implements IngredientRepo {
async fetchIngredients(): Promise<Ingredient[]> {
async fetchIngredients(lang: Lang): Promise<Ingredient[]> {
const url = new URL("http://localhost:33333/api/ingredients");
url.searchParams.set("lang", lang);
const res = await fetch(
"http://localhost:33333/api/ingredients",
url.toString(),
{ headers: { "content-type": "application/json" } },
);

View File

@ -1,5 +1,6 @@
import { Lang } from "../../context.ts";
import { Ingredient } from "../../domain/ingredient/types.ts";
export interface IngredientRepo {
fetchIngredients(): Promise<Ingredient[]>;
fetchIngredients(lang: Lang): Promise<Ingredient[]>;
}

View File

@ -1,12 +1,14 @@
import { StrRenderer } from "ren/str.ts";
import { Layout } from "./comp/layout.ts";
import { Context } from "./context.ts";
import { Context, Lang } from "./context.ts";
import { E404Page } from "./views/e404.ts";
import * as log from "./log.ts";
import { HomePage } from "./views/home.ts";
import { RecipesPage } from "./views/recipes.ts";
import { IngredientsPage } from "./views/ingredients.ts";
import { RestIngredientRepo } from "./repo/ingredient/rest.ts";
import rusTranslates from "./translates/rus.ts";
import type { Translations } from "./translates/rus.ts";
if (import.meta.main) {
await main();
@ -39,14 +41,40 @@ async function serveHttp(conn: Deno.Conn) {
}
async function handleRequest(req: Request): Promise<Response> {
log.debug({ url: req.url });
log.info({ method: req.method, url: req.url });
if (req.method === "GET") {
return await handleGet(req);
} else {
return new Response("Method Not Allowed", { status: 405 });
}
}
async function handleGet(req: Request) {
const ctx = createContextFromRequest(req);
try {
const res = await tryCreateFileResponse(ctx.locPath);
return res;
} catch (_) {
const ren = new StrRenderer({ wrapNode: Layout });
if (ctx.lang !== Lang.Rus) {
await loadAndUpdateTranslations(ctx);
}
log.debug({ context: ctx });
const ren = new StrRenderer({
wrapNode: Layout.bind(null, ctx),
onVisitAttr: ([key, value]) => {
if (key === "lhref") {
const prefix = ctx.lang === Lang.Rus || !value.startsWith("/")
? ""
: `/${ctx.lang}`;
return ["href", prefix + value];
} else {
return [key, value];
}
},
});
if (ctx.locPath === "/") {
return createHtmlResponse(ren.render(HomePage(ctx)));
@ -54,7 +82,7 @@ async function handleRequest(req: Request): Promise<Response> {
return createHtmlResponse(ren.render(RecipesPage(ctx)));
} else if (ctx.locPath === "/ingredients") {
const repo = new RestIngredientRepo();
const ingredients = await repo.fetchIngredients();
const ingredients = await repo.fetchIngredients(ctx.lang);
return createHtmlResponse(
ren.render(IngredientsPage(ctx, { ingredients })),
@ -65,12 +93,39 @@ async function handleRequest(req: Request): Promise<Response> {
}
}
async function loadAndUpdateTranslations(ctx: Context) {
try {
const translates = await import(`./translates/${ctx.lang}.ts`);
ctx.tr = Object.entries(translates.default as Partial<Translations>)
.reduce(
(acc, [key, val]) => ({
...acc,
[key as keyof Translations]: val,
}),
{ ...ctx.tr } as Translations,
);
} catch (_e) { /* ignore */ }
}
function createContextFromRequest(req: Request): Context {
const locUrl = new URL(req.url);
const lang = langFromUrl(locUrl);
return {
locPath: new URL(req.url).pathname,
lang,
locPath: stripPrefix(`/${lang}`, locUrl.pathname),
tr: rusTranslates,
};
}
function langFromUrl(url: URL): Lang {
return url.pathname.startsWith("/eng/") ? Lang.Eng : Lang.Rus;
}
function stripPrefix(prefix: string, val: string): string {
return val.startsWith(prefix) ? val.slice(prefix.length) : val;
}
function createHtmlResponse(body: string, status = 200): Response {
return new Response(body, {
status,

9
web/translates/eng.ts Normal file
View File

@ -0,0 +1,9 @@
import { Translations } from "./rus.ts";
export default {
Home: "Home",
Recipes: "Recipes",
Ingredients: "Ingredients",
Source_code: "Source code",
Page_not_found: "Page not found",
} as Translations;

11
web/translates/rus.ts Normal file
View File

@ -0,0 +1,11 @@
export const rus = {
Home: "Главная",
Recipes: "Рецепты",
Ingredients: "Ингредиенты",
Source_code: "Исходный код",
Page_not_found: "Страница не найдена",
};
export default rus;
export type Translations = typeof rus;

View File

@ -5,11 +5,11 @@ import { Context } from "../context.ts";
import { H3 } from "../uikit/typo.ts";
export function E404Page(ctx: Context): AnyNode {
return PageLayout(ctx, [E404()]);
return PageLayout(ctx, [E404(ctx)]);
}
export function E404(): AnyNode {
export function E404(ctx: Context): AnyNode {
return E("div", classNames("content-width"), [
H3("Страница не найдена"),
H3(ctx.tr.Page_not_found),
]);
}

View File

@ -7,7 +7,7 @@ import { H3 } from "../uikit/typo.ts";
export function HomePage(ctx: Context): AnyNode {
return PageLayout(ctx, [
E("div", classNames("content-width"), [
H3("Главная"),
H3(ctx.tr.Home),
]),
]);
}

View File

@ -15,7 +15,7 @@ export function IngredientsPage(
): AnyNode {
return PageLayout(ctx, [
E("div", classNames("content-width"), [
H3("Ингредиенты"),
H3(ctx.tr.Ingredients),
IngredientList(data.ingredients),
]),
]);

View File

@ -7,7 +7,7 @@ import { H3 } from "../uikit/typo.ts";
export function RecipesPage(ctx: Context): AnyNode {
return PageLayout(ctx, [
E("div", classNames("content-width"), [
H3("Рецепты"),
H3(ctx.tr.Recipes),
]),
]);
}