this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add localized page titles

+191 -39
+15 -1
app/(creator)/dashboard/page.tsx
··· 1 + import type { Metadata } from "next"; 1 2 import { Plus } from "lucide-react"; 2 3 3 4 import { createFormAction } from "@/app/(creator)/actions"; ··· 8 9 import { listFormsForWorkspace } from "@/lib/forms"; 9 10 import { getRequestI18n } from "@/lib/i18n-server"; 10 11 import { getActiveWorkspaceForUser } from "@/lib/workspaces"; 12 + import { withTitle } from "@/lib/metadata"; 11 13 12 - export default async function DashboardPage() { 14 + async function getDashboardData() { 13 15 const session = await getServerAuthSession(); 14 16 const { t } = await getRequestI18n(); 15 17 const workspaceState = await getActiveWorkspaceForUser(session!.user.id); ··· 21 23 workspaceState.activeOption.kind === "personal" 22 24 ? t("workspace.personal") 23 25 : workspaceState.activeOption.label; 26 + 27 + return { forms, workspaceLabel }; 28 + } 29 + 30 + export async function generateMetadata(): Promise<Metadata> { 31 + const { workspaceLabel } = await getDashboardData(); 32 + return withTitle(workspaceLabel); 33 + } 34 + 35 + export default async function DashboardPage() { 36 + const { t } = await getRequestI18n(); 37 + const { forms, workspaceLabel } = await getDashboardData(); 24 38 25 39 return forms.length === 0 ? ( 26 40 <div className="space-y-8">
+26 -9
app/(creator)/forms/[id]/edit/page.tsx
··· 1 + import type { Metadata } from "next"; 1 2 import { notFound } from "next/navigation"; 2 3 3 4 import { FormBuilder } from "@/components/form-builder"; 4 5 import { getServerAuthSession } from "@/lib/auth"; 5 6 import { AppError } from "@/lib/errors"; 6 7 import { getOwnedFormForBuilder } from "@/lib/forms"; 8 + import { resolveTitle, withTitle } from "@/lib/metadata"; 9 + import { getRequestI18n } from "@/lib/i18n-server"; 7 10 8 - export default async function EditFormPage({ 9 - params, 10 - }: { 11 - params: Promise<{ id: string }>; 12 - }) { 11 + async function getEditableForm(id: string) { 13 12 const session = await getServerAuthSession(); 14 - const { id } = await params; 15 - 16 - let form; 17 13 18 14 try { 19 - form = await getOwnedFormForBuilder(session!.user.id, id); 15 + return await getOwnedFormForBuilder(session!.user.id, id); 20 16 } catch (error) { 21 17 if (error instanceof AppError && error.status === 404) { 22 18 notFound(); ··· 24 20 25 21 throw error; 26 22 } 23 + } 24 + 25 + export async function generateMetadata({ 26 + params, 27 + }: { 28 + params: Promise<{ id: string }>; 29 + }): Promise<Metadata> { 30 + const { t } = await getRequestI18n(); 31 + const { id } = await params; 32 + const form = await getEditableForm(id); 33 + 34 + return withTitle(resolveTitle(form.title, t("meta.untitledForm"))); 35 + } 36 + 37 + export default async function EditFormPage({ 38 + params, 39 + }: { 40 + params: Promise<{ id: string }>; 41 + }) { 42 + const { id } = await params; 43 + const form = await getEditableForm(id); 27 44 28 45 return <FormBuilder initialForm={form} />; 29 46 }
+28 -10
app/(creator)/forms/[id]/responses/[responseId]/page.tsx
··· 1 + import type { Metadata } from "next"; 1 2 import Link from "next/link"; 2 3 import { notFound } from "next/navigation"; 3 4 ··· 9 10 import { AppError } from "@/lib/errors"; 10 11 import { getOwnedResponseDetail } from "@/lib/forms"; 11 12 import { getRequestI18n } from "@/lib/i18n-server"; 13 + import { resolveTitle, withTitle } from "@/lib/metadata"; 12 14 import { formatCalendarDate, formatDate } from "@/lib/utils"; 13 15 14 - export default async function ResponseDetailPage({ 15 - params, 16 - }: { 17 - params: Promise<{ id: string; responseId: string }>; 18 - }) { 16 + async function getResponseData(id: string, responseId: string) { 19 17 const session = await getServerAuthSession(); 20 - const { locale, t } = await getRequestI18n(); 21 - const { id, responseId } = await params; 22 - 23 - let response; 24 18 25 19 try { 26 - response = await getOwnedResponseDetail(session!.user.id, id, responseId); 20 + return await getOwnedResponseDetail(session!.user.id, id, responseId); 27 21 } catch (error) { 28 22 if (error instanceof AppError && error.status === 404) { 29 23 notFound(); ··· 31 25 32 26 throw error; 33 27 } 28 + } 29 + 30 + export async function generateMetadata({ 31 + params, 32 + }: { 33 + params: Promise<{ id: string; responseId: string }>; 34 + }): Promise<Metadata> { 35 + const { t } = await getRequestI18n(); 36 + const { id, responseId } = await params; 37 + const response = await getResponseData(id, responseId); 38 + 39 + return withTitle( 40 + `${resolveTitle(response.formTitle, t("meta.untitledForm"))} — ${t("meta.response", { number: response.submissionNumber })}`, 41 + ); 42 + } 43 + 44 + export default async function ResponseDetailPage({ 45 + params, 46 + }: { 47 + params: Promise<{ id: string; responseId: string }>; 48 + }) { 49 + const { locale, t } = await getRequestI18n(); 50 + const { id, responseId } = await params; 51 + const response = await getResponseData(id, responseId); 34 52 35 53 const workspaceLabel = 36 54 response.workspace.kind === "personal"
+28 -13
app/(creator)/forms/[id]/responses/page.tsx
··· 1 + import type { Metadata } from "next"; 1 2 import Link from "next/link"; 2 3 import { ArrowRight } from "lucide-react"; 3 4 import { notFound } from "next/navigation"; ··· 11 12 import { AppError } from "@/lib/errors"; 12 13 import { listResponsesForOwnedForm } from "@/lib/forms"; 13 14 import { getRequestI18n } from "@/lib/i18n-server"; 15 + import { resolveTitle, withTitle } from "@/lib/metadata"; 14 16 import { formatDate } from "@/lib/utils"; 15 17 16 - export default async function ResponsesPage({ 17 - params, 18 - }: { 19 - params: Promise<{ id: string }>; 20 - }) { 18 + async function getResponsesData(id: string) { 21 19 const session = await getServerAuthSession(); 22 - const { locale, t } = await getRequestI18n(); 23 - const { id } = await params; 24 - 25 - let form; 26 - let responses; 27 20 28 21 try { 29 - const payload = await listResponsesForOwnedForm(session!.user.id, id); 30 - form = payload.form; 31 - responses = payload.responses; 22 + return await listResponsesForOwnedForm(session!.user.id, id); 32 23 } catch (error) { 33 24 if (error instanceof AppError && error.status === 404) { 34 25 notFound(); ··· 36 27 37 28 throw error; 38 29 } 30 + } 31 + 32 + export async function generateMetadata({ 33 + params, 34 + }: { 35 + params: Promise<{ id: string }>; 36 + }): Promise<Metadata> { 37 + const { t } = await getRequestI18n(); 38 + const { id } = await params; 39 + const { form } = await getResponsesData(id); 40 + 41 + return withTitle( 42 + `${resolveTitle(form.title, t("meta.untitledForm"))} — ${t("meta.responses")}`, 43 + ); 44 + } 45 + 46 + export default async function ResponsesPage({ 47 + params, 48 + }: { 49 + params: Promise<{ id: string }>; 50 + }) { 51 + const { locale, t } = await getRequestI18n(); 52 + const { id } = await params; 53 + const { form, responses } = await getResponsesData(id); 39 54 40 55 const workspaceLabel = 41 56 form.workspace.kind === "personal"
+9
app/(creator)/settings/page.tsx
··· 1 + import type { Metadata } from "next"; 2 + 1 3 import { SettingsShell } from "@/components/settings-shell"; 2 4 import { getServerAuthSession } from "@/lib/auth"; 3 5 import { listOrganizationsForUser } from "@/lib/organizations"; 6 + import { getRequestI18n } from "@/lib/i18n-server"; 7 + import { withTitle } from "@/lib/metadata"; 4 8 import { getProfileSettingsUser } from "@/lib/users"; 9 + 10 + export async function generateMetadata(): Promise<Metadata> { 11 + const { t } = await getRequestI18n(); 12 + return withTitle(t("settings.shell.title")); 13 + } 5 14 6 15 export default async function SettingsPage({ 7 16 searchParams,
+26 -5
app/f/[slug]/page.tsx
··· 1 + import type { Metadata } from "next"; 1 2 import { notFound } from "next/navigation"; 2 3 3 4 import { PublicFormRunner } from "@/components/public-form-runner"; 4 5 import { getPublicFormBySlug } from "@/lib/forms"; 6 + import { getRequestI18n } from "@/lib/i18n-server"; 7 + import { resolveTitle, withTitle } from "@/lib/metadata"; 8 + 9 + async function getPublicForm(slug: string) { 10 + const form = await getPublicFormBySlug(slug); 11 + 12 + if (!form) { 13 + notFound(); 14 + } 15 + 16 + return form; 17 + } 18 + 19 + export async function generateMetadata({ 20 + params, 21 + }: { 22 + params: Promise<{ slug: string }>; 23 + }): Promise<Metadata> { 24 + const { t } = await getRequestI18n(); 25 + const { slug } = await params; 26 + const form = await getPublicForm(slug); 27 + 28 + return withTitle(resolveTitle(form.title, t("meta.untitledForm"))); 29 + } 5 30 6 31 export default async function PublicFormPage({ 7 32 params, ··· 9 34 params: Promise<{ slug: string }>; 10 35 }) { 11 36 const { slug } = await params; 12 - const form = await getPublicFormBySlug(slug); 13 - 14 - if (!form) { 15 - notFound(); 16 - } 37 + const form = await getPublicForm(slug); 17 38 18 39 return ( 19 40 <main className="mx-auto flex w-full max-w-6xl flex-1 items-center justify-center px-6 py-8 lg:px-10 lg:py-10">
+23
app/join/[token]/page.tsx
··· 1 + import type { Metadata } from "next"; 1 2 import Link from "next/link"; 2 3 import { redirect } from "next/navigation"; 3 4 ··· 7 8 import { getServerAuthSession } from "@/lib/auth"; 8 9 import { getRequestI18n } from "@/lib/i18n-server"; 9 10 import { getOrganizationInviteState } from "@/lib/organizations"; 11 + import { withTitle } from "@/lib/metadata"; 12 + 13 + export async function generateMetadata({ 14 + params, 15 + }: { 16 + params: Promise<{ token: string }>; 17 + }): Promise<Metadata> { 18 + const session = await getServerAuthSession(); 19 + const { t } = await getRequestI18n(); 20 + const { token } = await params; 21 + 22 + if (!session?.user?.id) { 23 + return withTitle(t("meta.joinOrganization")); 24 + } 25 + 26 + const invite = await getOrganizationInviteState(session.user.id, token); 27 + return withTitle( 28 + invite 29 + ? t("join.joinTitle", { organization: invite.organizationName }) 30 + : t("meta.joinOrganization"), 31 + ); 32 + } 10 33 11 34 export default async function JoinOrganizationPage({ 12 35 params,
+5 -1
app/layout.tsx
··· 21 21 22 22 export async function generateMetadata(): Promise<Metadata> { 23 23 const { t } = await getRequestI18n(); 24 + const appName = t("app.name"); 24 25 25 26 return { 26 - title: t("app.name"), 27 + title: { 28 + default: appName, 29 + template: `%s | ${appName}`, 30 + }, 27 31 description: t("app.description"), 28 32 }; 29 33 }
+7
app/not-found.tsx
··· 1 + import type { Metadata } from "next"; 1 2 import Link from "next/link"; 2 3 3 4 import { Button } from "@/components/ui/button"; 4 5 import { Card } from "@/components/ui/card"; 5 6 import { getRequestI18n } from "@/lib/i18n-server"; 7 + import { withTitle } from "@/lib/metadata"; 8 + 9 + export async function generateMetadata(): Promise<Metadata> { 10 + const { t } = await getRequestI18n(); 11 + return withTitle(t("meta.notFound")); 12 + } 6 13 7 14 export default async function NotFound() { 8 15 const { t } = await getRequestI18n();
+10
lib/metadata.ts
··· 1 + import type { Metadata } from "next"; 2 + 3 + export function resolveTitle(title: string | null | undefined, fallback: string) { 4 + const normalized = title?.trim(); 5 + return normalized && normalized.length > 0 ? normalized : fallback; 6 + } 7 + 8 + export function withTitle(title: string): Metadata { 9 + return { title }; 10 + }
+7
locales/en.yml
··· 10 10 licensedUnder: Licensed under 11 11 sourceCode: Source code 12 12 reportBug: Report a bug 13 + meta: 14 + untitledForm: Untitled form 15 + dashboard: Dashboard 16 + responses: Responses 17 + response: "Response #{number}" 18 + joinOrganization: Join organization 19 + notFound: Not found 13 20 auth: 14 21 continueWithGoogle: Continue with Google 15 22 workspace:
+7
locales/ru.yml
··· 10 10 licensedUnder: Лицензия 11 11 sourceCode: Исходный код 12 12 reportBug: Сообщить об ошибке 13 + meta: 14 + untitledForm: Форма без названия 15 + dashboard: Дашборд 16 + responses: Ответы 17 + response: "Ответ №{number}" 18 + joinOrganization: Вступить в организацию 19 + notFound: Не найдено 13 20 auth: 14 21 continueWithGoogle: Продолжить с Google 15 22 workspace: