Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLeitwolf112026-09-09 20:28:06 +0300
committerLeitwolf112026-09-09 20:28:06 +0300
commit3f84eee09b08e354f3c61da11ac1a9e00ca5a8ea (patch)
treef2663510eeec82475e6ad97ceec02a139c924f7e
parentfa3b1f249f390ecd2248e1c17e70ede1364ff034 (diff)
downloadprogmaweb-3f84eee09b08e354f3c61da11ac1a9e00ca5a8ea.tar.gz
Move the ViGet package and DSL-plugin catalogs into Progmaweb as host-aware Next.js routes, add complete en-US and German content, persistent light and dark themes, catalog-specific metadata, robots and sitemap responses, and an Nginx ownership contract for viget.progmasoft.com.
-rw-r--r--README.md6
-rw-r--r--apps/web/app/layout.tsx1
-rw-r--r--apps/web/app/robots.ts13
-rw-r--r--apps/web/app/sitemap.ts25
-rw-r--r--apps/web/app/viget.css337
-rw-r--r--apps/web/app/viget/dslplugins/page.tsx79
-rw-r--r--apps/web/app/viget/page.tsx135
-rw-r--r--apps/web/components/ViGetFooter.tsx37
-rw-r--r--apps/web/components/ViGetHeader.tsx35
-rw-r--r--apps/web/lib/localization.ts180
-rw-r--r--apps/web/next.config.ts3
-rw-r--r--apps/web/proxy.ts44
-rw-r--r--ops/README.md10
-rw-r--r--ops/nginx/progmaweb.conf73
14 files changed, 969 insertions, 9 deletions
diff --git a/README.md b/README.md
index 5466c96..d8075ab 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ the ASP.NET Core API.
- `account.progmasoft.com/login` signs an existing account in.
- `account.progmasoft.com/register` creates a new account after server-side validation.
- `account.progmasoft.com/<Account>/dashboard` serves the authenticated account dashboard.
+- `viget.progmasoft.com` serves the public Visual X# package catalog.
+- `viget.progmasoft.com/dslplugins/` serves the separate Kotlin DSL plugin catalog.
Account names preserve their original display case but reserve names case-insensitively, preventing visually confusing
duplicates. Email uniqueness is also evaluated case-insensitively by the API.
@@ -23,8 +25,8 @@ canonical Progmasoft `<Account>` name, with the same spelling and case.
## Architecture
-- `apps/web` is a Next.js App Router application. Host-aware routing keeps the public organization and account surfaces in one
- build without coupling their page hierarchy.
+- `apps/web` is a Next.js App Router application. Host-aware routing keeps the public organization, account, and ViGet
+ surfaces in one build without coupling their page hierarchies.
- `apps/api` is an ASP.NET Core minimal API. It owns account-name policy, password hashing, session issuance, secure
cookies, rate limiting, and authorization.
- The browser never receives password hashes, session digests, deployment secrets, or database credentials.
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 82159e8..4eae3fd 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -5,6 +5,7 @@ import type { Metadata } from "next";
import type { ReactNode } from "react";
import { getLocale } from "@/lib/locale.server";
import "./globals.css";
+import "./viget.css";
// Apply the stored theme before the body is painted. PreferenceControls keeps
// this value synchronized after hydration, while this small bootstrap prevents
diff --git a/apps/web/app/robots.ts b/apps/web/app/robots.ts
index 8916367..1a05aae 100644
--- a/apps/web/app/robots.ts
+++ b/apps/web/app/robots.ts
@@ -2,8 +2,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
import type { MetadataRoute } from "next";
+import { headers } from "next/headers";
-export default function robots(): MetadataRoute.Robots {
+export default async function robots(): Promise<MetadataRoute.Robots> {
+ const requestHeaders = await headers();
+ const host =
+ requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host") ?? "";
+ const canonicalHost = host.split(":", 1)[0]?.toLowerCase();
+ const sitemap =
+ canonicalHost === "viget.progmasoft.com"
+ ? "https://viget.progmasoft.com/sitemap.xml"
+ : "https://progmasoft.com/sitemap.xml";
return {
rules: [
{ userAgent: "Googlebot", allow: "/" },
@@ -18,6 +27,6 @@ export default function robots(): MetadataRoute.Robots {
{ userAgent: "Bytespider", disallow: "/" },
{ userAgent: "*", allow: "/" },
],
- sitemap: "https://progmasoft.com/sitemap.xml",
+ sitemap,
};
}
diff --git a/apps/web/app/sitemap.ts b/apps/web/app/sitemap.ts
index abecd60..855323a 100644
--- a/apps/web/app/sitemap.ts
+++ b/apps/web/app/sitemap.ts
@@ -2,8 +2,31 @@
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
import type { MetadataRoute } from "next";
+import { headers } from "next/headers";
-export default function sitemap(): MetadataRoute.Sitemap {
+export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
+ const requestHeaders = await headers();
+ const host = (
+ requestHeaders.get("x-forwarded-host") ??
+ requestHeaders.get("host") ??
+ ""
+ )
+ .split(":", 1)[0]
+ ?.toLowerCase();
+ if (host === "viget.progmasoft.com") {
+ return [
+ {
+ url: "https://viget.progmasoft.com/",
+ changeFrequency: "weekly",
+ priority: 1,
+ },
+ {
+ url: "https://viget.progmasoft.com/dslplugins/",
+ changeFrequency: "weekly",
+ priority: 0.8,
+ },
+ ];
+ }
return [
{
// Only public canonical content belongs in this sitemap. Authentication,
diff --git a/apps/web/app/viget.css b/apps/web/app/viget.css
new file mode 100644
index 0000000..5578115
--- /dev/null
+++ b/apps/web/app/viget.css
@@ -0,0 +1,337 @@
+/* SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> */
+/* SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 */
+
+/* ViGet shares the organization theme while keeping a denser registry-oriented hierarchy. */
+.viget-surface {
+ min-height: 100vh;
+ background: var(--surface);
+}
+
+.viget-brand-lockup {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+}
+
+.viget-brand-lockup > span {
+ border-left: 2px solid var(--ink-800);
+ padding-left: 15px;
+ color: var(--ink-800);
+ font-size: 16px;
+ font-weight: 700;
+}
+
+.viget-hero,
+.viget-plugin-hero {
+ border-bottom: 1px solid var(--line);
+ background: var(--hero-background);
+}
+
+.viget-hero-grid,
+.viget-plugin-grid {
+ min-height: 620px;
+ display: grid;
+ grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.75fr);
+ align-items: center;
+ gap: 80px;
+ padding-block: 76px;
+}
+
+.viget-plugin-grid {
+ min-height: 520px;
+}
+
+.viget-hero-copy h1 {
+ max-width: 720px;
+ margin: 0;
+ font-size: clamp(44px, 6vw, 72px);
+ letter-spacing: -0.05em;
+ line-height: 1.04;
+}
+
+.viget-hero-copy > p:not(.eyebrow) {
+ max-width: 680px;
+ margin: 24px 0 0;
+ color: var(--ink-650);
+ font-size: 18px;
+}
+
+.viget-availability {
+ border: 1px solid var(--line-strong);
+ border-radius: var(--radius-lg);
+ padding: 30px;
+ background: var(--surface);
+ box-shadow: var(--shadow-md);
+}
+
+.viget-status {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ color: var(--green-600);
+ font-size: 13px;
+ font-weight: 750;
+}
+
+.viget-status > span {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: var(--green-600);
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--green-600) 14%, transparent);
+}
+
+.viget-availability h2 {
+ margin: 17px 0 10px;
+ font-size: 27px;
+ letter-spacing: -0.025em;
+}
+
+.viget-availability > p {
+ margin: 0;
+ color: var(--ink-650);
+}
+
+.viget-availability dl {
+ margin: 25px 0 0;
+}
+
+.viget-availability dl > div {
+ display: flex;
+ justify-content: space-between;
+ gap: 24px;
+ border-top: 1px solid var(--line);
+ padding-block: 12px;
+}
+
+.viget-availability dt {
+ color: var(--ink-500);
+}
+
+.viget-availability dd {
+ margin: 0;
+ color: var(--ink-800);
+ font-weight: 700;
+}
+
+.viget-catalog-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 24px;
+}
+
+.viget-catalog-card {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-md);
+ padding: 30px;
+ background: var(--surface);
+ color: var(--ink-950);
+ box-shadow: var(--shadow-sm);
+ transition:
+ border-color 140ms ease,
+ transform 140ms ease;
+}
+
+a.viget-catalog-card:hover {
+ border-color: var(--blue-600);
+ text-decoration: none;
+ transform: translateY(-2px);
+}
+
+.viget-catalog-card > span {
+ color: var(--blue-600);
+ font-family: "Cascadia Code", Consolas, monospace;
+ font-size: 12px;
+ font-weight: 750;
+}
+
+.viget-catalog-card > p {
+ margin: 24px 0 5px;
+ color: var(--ink-500);
+ font-size: 13px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.viget-catalog-card h3 {
+ margin: 0;
+ font-size: 26px;
+}
+
+.viget-catalog-card code,
+.viget-availability code {
+ overflow-wrap: anywhere;
+ margin-top: 24px;
+ border-radius: var(--radius-sm);
+ padding: 12px;
+ background: var(--surface-blue);
+ color: var(--blue-700);
+ font-family: "Cascadia Code", Consolas, monospace;
+ font-size: 13px;
+}
+
+.viget-catalog-card small {
+ margin-top: 19px;
+ color: var(--ink-650);
+ font-size: 14px;
+}
+
+.viget-catalog-card > strong:last-child {
+ margin-top: auto;
+ padding-top: 25px;
+ color: var(--blue-700);
+}
+
+.viget-catalog-primary {
+ border-color: var(--blue-600);
+}
+
+.viget-contract-grid {
+ display: grid;
+ grid-template-columns: minmax(280px, 0.7fr) 1.3fr;
+ gap: 80px;
+}
+
+.viget-contract h2 {
+ margin: 0;
+ font-size: clamp(32px, 4vw, 47px);
+ letter-spacing: -0.04em;
+ line-height: 1.08;
+}
+
+.viget-contract ol {
+ display: grid;
+ gap: 0;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.viget-contract li {
+ display: grid;
+ grid-template-columns: 42px 1fr;
+ gap: 16px;
+ border-top: 1px solid var(--line);
+ padding-block: 20px;
+}
+
+.viget-contract li > span {
+ color: var(--blue-600);
+ font-family: "Cascadia Code", Consolas, monospace;
+ font-weight: 700;
+}
+
+.viget-contract li strong {
+ color: var(--ink-800);
+ font-size: 18px;
+}
+
+.viget-contract li p {
+ margin: 5px 0 0;
+ color: var(--ink-650);
+}
+
+.viget-text-link {
+ display: inline-flex;
+ align-items: center;
+ color: var(--blue-700);
+ font-weight: 700;
+}
+
+.viget-guidance-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24px;
+}
+
+.viget-guidance-grid article {
+ display: flex;
+ flex-direction: column;
+ border-top: 3px solid var(--blue-500);
+ padding: 24px;
+ background: var(--surface);
+ box-shadow: var(--shadow-sm);
+}
+
+.viget-guidance-grid article > span {
+ color: var(--blue-700);
+ font-size: 12px;
+ font-weight: 750;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.viget-guidance-grid article > strong {
+ margin-top: 10px;
+ color: var(--ink-800);
+ font-size: 20px;
+}
+
+.viget-guidance-grid article > p {
+ margin: 8px 0 0;
+ color: var(--ink-650);
+}
+
+.viget-footer {
+ margin-top: 0;
+}
+
+@media (max-width: 980px) {
+ .viget-header .header-inner {
+ align-items: flex-start;
+ flex-direction: column;
+ padding-block: 16px;
+ }
+
+ .viget-header .primary-nav {
+ width: 100%;
+ flex-wrap: wrap;
+ }
+
+ .viget-hero-grid,
+ .viget-plugin-grid,
+ .viget-contract-grid {
+ grid-template-columns: 1fr;
+ gap: 46px;
+ }
+
+ .viget-hero-grid,
+ .viget-plugin-grid {
+ min-height: auto;
+ }
+}
+
+@media (max-width: 680px) {
+ .viget-header .primary-nav {
+ gap: 14px;
+ }
+
+ .viget-header .primary-nav > a:not(.button) {
+ display: inline-flex;
+ }
+
+ .viget-hero-grid,
+ .viget-plugin-grid {
+ padding-block: 56px;
+ }
+
+ .viget-hero-copy h1 {
+ font-size: 43px;
+ }
+
+ .viget-catalog-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .viget-guidance-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .viget-availability,
+ .viget-catalog-card {
+ padding: 24px;
+ }
+}
diff --git a/apps/web/app/viget/dslplugins/page.tsx b/apps/web/app/viget/dslplugins/page.tsx
new file mode 100644
index 0000000..8a4be8c
--- /dev/null
+++ b/apps/web/app/viget/dslplugins/page.tsx
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+import type { Metadata } from "next";
+import { ViGetFooter } from "@/components/ViGetFooter";
+import { ViGetHeader } from "@/components/ViGetHeader";
+import { getLocale } from "@/lib/locale.server";
+import { getMessages } from "@/lib/localization";
+
+export async function generateMetadata(): Promise<Metadata> {
+ const { metadata } = getMessages(await getLocale());
+ return {
+ title: metadata.dslPluginsTitle,
+ description: metadata.dslPluginsDescription,
+ alternates: {
+ canonical: "https://viget.progmasoft.com/dslplugins/",
+ },
+ robots: { index: true, follow: true },
+ };
+}
+
+export default async function DslPluginsPage() {
+ const locale = await getLocale();
+ const { viget } = getMessages(locale);
+ return (
+ <div className="viget-surface">
+ <ViGetHeader locale={locale} />
+ <main>
+ <section className="viget-plugin-hero">
+ <div className="shell viget-plugin-grid">
+ <div className="viget-hero-copy">
+ <p className="eyebrow">{viget.pluginEyebrow}</p>
+ <h1>{viget.pluginTitle}</h1>
+ <p>{viget.pluginDescription}</p>
+ <div className="hero-actions">
+ <a
+ className="button button-secondary"
+ href="https://viget.progmasoft.com/"
+ >
+ {viget.backToPackages}
+ </a>
+ <a
+ className="viget-text-link"
+ href="https://github.com/Progmasoft/visual-xsharp"
+ >
+ {viget.followDevelopment} →
+ </a>
+ </div>
+ </div>
+ <aside
+ className="viget-availability"
+ aria-labelledby="plugin-availability-title"
+ >
+ <div className="viget-status">
+ <span aria-hidden="true" />
+ {viget.pluginAvailable}
+ </div>
+ <h2 id="plugin-availability-title">{viget.pluginEmptyTitle}</h2>
+ <p>{viget.pluginEmptyDescription}</p>
+ <code>/dslplugins/&lt;Publisher&gt;/&lt;Name&gt;/</code>
+ </aside>
+ </div>
+ </section>
+ <section className="section section-muted">
+ <div className="shell viget-guidance-grid">
+ {viget.guidance.map(([label, title, description]) => (
+ <article key={label}>
+ <span>{label}</span>
+ <strong>{title}</strong>
+ <p>{description}</p>
+ </article>
+ ))}
+ </div>
+ </section>
+ </main>
+ <ViGetFooter locale={locale} />
+ </div>
+ );
+}
diff --git a/apps/web/app/viget/page.tsx b/apps/web/app/viget/page.tsx
new file mode 100644
index 0000000..2bf9161
--- /dev/null
+++ b/apps/web/app/viget/page.tsx
@@ -0,0 +1,135 @@
+// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+import type { Metadata } from "next";
+import { ViGetFooter } from "@/components/ViGetFooter";
+import { ViGetHeader } from "@/components/ViGetHeader";
+import { getLocale } from "@/lib/locale.server";
+import { getMessages } from "@/lib/localization";
+
+export async function generateMetadata(): Promise<Metadata> {
+ const { metadata } = getMessages(await getLocale());
+ return {
+ title: metadata.vigetTitle,
+ description: metadata.vigetDescription,
+ alternates: { canonical: "https://viget.progmasoft.com/" },
+ robots: { index: true, follow: true },
+ };
+}
+
+export default async function ViGetPage() {
+ const locale = await getLocale();
+ const { viget } = getMessages(locale);
+ return (
+ <div className="viget-surface">
+ <ViGetHeader locale={locale} />
+ <main>
+ <section className="viget-hero">
+ <div className="shell viget-hero-grid">
+ <div className="viget-hero-copy">
+ <p className="eyebrow">{viget.homeEyebrow}</p>
+ <h1>{viget.homeTitle}</h1>
+ <p>{viget.homeDescription}</p>
+ <div className="hero-actions">
+ <a
+ className="button"
+ href="https://account.progmasoft.com/register"
+ >
+ {viget.createAccount}
+ </a>
+ <a
+ className="button button-secondary"
+ href="https://xsharp-lang.xyz/"
+ >
+ {viget.exploreVisualXSharp}
+ </a>
+ </div>
+ </div>
+ <aside
+ className="viget-availability"
+ aria-labelledby="viget-availability-title"
+ >
+ <div className="viget-status">
+ <span aria-hidden="true" />
+ {viget.available}
+ </div>
+ <h2 id="viget-availability-title">{viget.emptyTitle}</h2>
+ <p>{viget.emptyDescription}</p>
+ <dl>
+ <div>
+ <dt>{viget.packageFormat}</dt>
+ <dd>.vipkg</dd>
+ </div>
+ <div>
+ <dt>{viget.publisherIdentity}</dt>
+ <dd>Progmasoft Account</dd>
+ </div>
+ <div>
+ <dt>{viget.publishing}</dt>
+ <dd>{viget.publishingClosed}</dd>
+ </div>
+ </dl>
+ </aside>
+ </div>
+ </section>
+
+ <section className="section section-muted">
+ <div className="shell">
+ <div className="section-heading">
+ <div>
+ <p className="eyebrow">{viget.catalogsEyebrow}</p>
+ <h2>{viget.catalogsTitle}</h2>
+ </div>
+ <p>{viget.catalogsDescription}</p>
+ </div>
+ <div className="viget-catalog-grid">
+ <article className="viget-catalog-card viget-catalog-primary">
+ <span>01</span>
+ <p>{viget.visualPackages}</p>
+ <h3>{viget.vipkgCatalog}</h3>
+ <code>
+ viget.progmasoft.com/&lt;Publisher&gt;/&lt;Name&gt;/
+ </code>
+ <small>{viget.vipkgDescription}</small>
+ </article>
+ <a
+ className="viget-catalog-card"
+ href="https://viget.progmasoft.com/dslplugins/"
+ >
+ <span>02</span>
+ <p>{viget.projectExtensions}</p>
+ <h3>{viget.kotlinPlugins}</h3>
+ <code>
+ viget.progmasoft.com/dslplugins/&lt;Publisher&gt;/&lt;Name&gt;/
+ </code>
+ <small>{viget.kotlinDescription}</small>
+ <strong>{viget.openCatalog} →</strong>
+ </a>
+ </div>
+ </div>
+ </section>
+
+ <section className="section viget-contract">
+ <div className="shell viget-contract-grid">
+ <div>
+ <p className="eyebrow">{viget.contractEyebrow}</p>
+ <h2>{viget.contractTitle}</h2>
+ </div>
+ <ol>
+ {viget.principles.map(([title, description], index) => (
+ <li key={title}>
+ <span>{String(index + 1).padStart(2, "0")}</span>
+ <div>
+ <strong>{title}</strong>
+ <p>{description}</p>
+ </div>
+ </li>
+ ))}
+ </ol>
+ </div>
+ </section>
+ </main>
+ <ViGetFooter locale={locale} />
+ </div>
+ );
+}
diff --git a/apps/web/components/ViGetFooter.tsx b/apps/web/components/ViGetFooter.tsx
new file mode 100644
index 0000000..c87449b
--- /dev/null
+++ b/apps/web/components/ViGetFooter.tsx
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+import { Brand } from "./Brand";
+import { getMessages, type Locale } from "@/lib/localization";
+
+export function ViGetFooter({ locale }: { locale: Locale }) {
+ const { viget } = getMessages(locale);
+ return (
+ <footer className="site-footer viget-footer">
+ <div className="shell footer-grid">
+ <div>
+ <Brand />
+ <p>{viget.footerDescription}</p>
+ </div>
+ <div>
+ <strong>{viget.registry}</strong>
+ <a href="https://viget.progmasoft.com/">{viget.packages}</a>
+ <a href="https://viget.progmasoft.com/dslplugins/">
+ {viget.dslPlugins}
+ </a>
+ <a href="https://xsharp-lang.xyz/">Visual X#</a>
+ </div>
+ <div>
+ <strong>Progmasoft</strong>
+ <a href="https://account.progmasoft.com/">{viget.account}</a>
+ <a href="mailto:support@progmasoft.com">{viget.support}</a>
+ <a href="https://github.com/Progmasoft/progmaweb">{viget.source}</a>
+ </div>
+ </div>
+ <div className="shell footer-bottom">
+ <span>© 2026 Progmasoft</span>
+ <span>{viget.footerClosing}</span>
+ </div>
+ </footer>
+ );
+}
diff --git a/apps/web/components/ViGetHeader.tsx b/apps/web/components/ViGetHeader.tsx
new file mode 100644
index 0000000..fd888fe
--- /dev/null
+++ b/apps/web/components/ViGetHeader.tsx
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+import Link from "next/link";
+import { getMessages, type Locale } from "@/lib/localization";
+import { Brand } from "./Brand";
+import { PreferenceControls } from "./PreferenceControls";
+
+export function ViGetHeader({ locale }: { locale: Locale }) {
+ const { navigation, viget } = getMessages(locale);
+ return (
+ <header className="site-header viget-header">
+ <div className="shell header-inner">
+ <div className="viget-brand-lockup">
+ <Brand />
+ <span>ViGet</span>
+ </div>
+ <nav className="primary-nav" aria-label={viget.navigationLabel}>
+ <Link href="https://viget.progmasoft.com/">{viget.packages}</Link>
+ <Link href="https://viget.progmasoft.com/dslplugins/">
+ {viget.dslPlugins}
+ </Link>
+ <Link href="https://account.progmasoft.com/login">{viget.login}</Link>
+ <Link
+ className="button button-small"
+ href="https://account.progmasoft.com/register"
+ >
+ {viget.register}
+ </Link>
+ <PreferenceControls locale={locale} labels={navigation} />
+ </nav>
+ </div>
+ </header>
+ );
+}
diff --git a/apps/web/lib/localization.ts b/apps/web/lib/localization.ts
index ae4702b..1e57e53 100644
--- a/apps/web/lib/localization.ts
+++ b/apps/web/lib/localization.ts
@@ -21,6 +21,12 @@ export const messages = {
recoveryTitle: "Recover your account",
recoveryDescription: "Get help regaining access to a Progmasoft account.",
dashboardTitle: "Dashboard",
+ vigetTitle: "ViGet Package Registry by Progmasoft",
+ vigetDescription:
+ "The Visual X# package registry is online, but no packages have been published yet.",
+ dslPluginsTitle: "DSL Plugins · ViGet Package Registry by Progmasoft",
+ dslPluginsDescription:
+ "The ViGet Kotlin DSL plugin catalog is online, but no plugins have been published yet.",
},
navigation: {
primary: "Primary navigation",
@@ -247,6 +253,90 @@ export const messages = {
profileDescription: "Developer ecosystem profile",
planned: "Planned",
},
+ viget: {
+ navigationLabel: "Registry navigation",
+ packages: "Packages",
+ dslPlugins: "DSL plugins",
+ account: "Account",
+ login: "Log in",
+ register: "Register",
+ homeEyebrow: "ViGet package registry",
+ homeTitle: "Packages for the Visual X# ecosystem.",
+ homeDescription:
+ "ViGet is the canonical source for Visual X# packages and project DSL plugins, operated directly by Progmasoft.",
+ createAccount: "Create account",
+ exploreVisualXSharp: "Explore Visual X#",
+ available: "Registry available",
+ emptyTitle: "The public catalog is empty.",
+ emptyDescription:
+ "No package releases have been published yet. ViGet will show real packages here as they become available.",
+ packageFormat: "Package format",
+ publisherIdentity: "Publisher identity",
+ publishing: "Publishing",
+ publishingClosed: "Not open yet",
+ catalogsEyebrow: "Catalogs",
+ catalogsTitle: "Two artifact types, one registry.",
+ catalogsDescription:
+ "Packages and project DSL plugins have separate, predictable coordinate spaces.",
+ visualPackages: "Visual X# packages",
+ vipkgCatalog: "ViPkg catalog",
+ vipkgDescription:
+ "Libraries and applications authored in Visual X#, distributed as .vipkg artifacts.",
+ projectExtensions: "Project extensions",
+ kotlinPlugins: "Kotlin DSL plugins",
+ kotlinDescription:
+ "Kotlin JAR plugins that extend Visual.XSharp.kts project configuration.",
+ openCatalog: "Open catalog",
+ contractEyebrow: "Registry contract",
+ contractTitle: "Clear ownership from identity to artifact.",
+ principles: [
+ [
+ "One account name",
+ "Your Progmasoft Account name is also your ViGet publisher name.",
+ ],
+ [
+ "Case-sensitive coordinates",
+ "Publisher and package names retain their exact public spelling.",
+ ],
+ [
+ "No placeholder releases",
+ "The catalog stays honestly empty until a real signed artifact is published.",
+ ],
+ ],
+ pluginEyebrow: "ViGet · Kotlin DSL plugins",
+ pluginTitle: "Extend the project model.",
+ pluginDescription:
+ "Kotlin DSL plugins are JAR artifacts for Visual.XSharp.kts. They are separate from Visual X# .vipkg packages.",
+ backToPackages: "Back to packages",
+ followDevelopment: "Follow development",
+ pluginAvailable: "Catalog available",
+ pluginEmptyTitle: "No public DSL plugins yet.",
+ pluginEmptyDescription:
+ "The catalog is ready and intentionally contains no placeholder artifacts.",
+ guidance: [
+ [
+ "Format",
+ "Kotlin JAR",
+ "DSL plugins run as Kotlin/JVM project extensions.",
+ ],
+ [
+ "Location",
+ "Dedicated catalog",
+ "Plugin coordinates always begin with /dslplugins.",
+ ],
+ [
+ "Availability",
+ "Publishing closed",
+ "Publishing opens only after the signed plugin contract is complete.",
+ ],
+ ],
+ footerDescription:
+ "ViGet is the package and Kotlin DSL plugin registry for the Visual X# ecosystem.",
+ registry: "Registry",
+ support: "Support",
+ source: "Source",
+ footerClosing: "ViGet Package Registry by Progmasoft",
+ },
},
"de-DE": {
metadata: {
@@ -261,6 +351,12 @@ export const messages = {
recoveryDescription:
"Erhalten Sie Hilfe beim Wiederherstellen des Zugriffs auf ein Progmasoft-Konto.",
dashboardTitle: "Kontoübersicht",
+ vigetTitle: "ViGet-Paketregistrierung von Progmasoft",
+ vigetDescription:
+ "Die Visual-X#-Paketregistrierung ist online, enthält aber noch keine veröffentlichten Pakete.",
+ dslPluginsTitle: "DSL-Plugins · ViGet-Paketregistrierung von Progmasoft",
+ dslPluginsDescription:
+ "Der ViGet-Katalog für Kotlin-DSL-Plugins ist online, enthält aber noch keine veröffentlichten Plugins.",
},
navigation: {
primary: "Hauptnavigation",
@@ -491,6 +587,90 @@ export const messages = {
profileDescription: "Profil im Entwicklerökosystem",
planned: "Geplant",
},
+ viget: {
+ navigationLabel: "Navigation der Paketregistrierung",
+ packages: "Pakete",
+ dslPlugins: "DSL-Plugins",
+ account: "Konto",
+ login: "Anmelden",
+ register: "Registrieren",
+ homeEyebrow: "ViGet-Paketregistrierung",
+ homeTitle: "Pakete für das Visual-X#-Ökosystem.",
+ homeDescription:
+ "ViGet ist die kanonische Quelle für Visual-X#-Pakete und Projekt-DSL-Plugins und wird direkt von Progmasoft betrieben.",
+ createAccount: "Konto erstellen",
+ exploreVisualXSharp: "Visual X# entdecken",
+ available: "Paketregistrierung verfügbar",
+ emptyTitle: "Der öffentliche Katalog ist leer.",
+ emptyDescription:
+ "Es wurden noch keine Pakete veröffentlicht. ViGet zeigt hier echte Pakete, sobald sie verfügbar sind.",
+ packageFormat: "Paketformat",
+ publisherIdentity: "Publisher-Identität",
+ publishing: "Veröffentlichung",
+ publishingClosed: "Noch nicht geöffnet",
+ catalogsEyebrow: "Kataloge",
+ catalogsTitle: "Zwei Artefakttypen, eine Paketregistrierung.",
+ catalogsDescription:
+ "Pakete und Projekt-DSL-Plugins besitzen getrennte, vorhersehbare Koordinatenräume.",
+ visual