diff --git a/apps/docs/content/docs/plugins/admin-page.mdx b/apps/docs/content/docs/dev/admin-page.mdx
similarity index 100%
rename from apps/docs/content/docs/plugins/admin-page.mdx
rename to apps/docs/content/docs/dev/admin-page.mdx
diff --git a/apps/docs/content/docs/dev/auth.mdx b/apps/docs/content/docs/dev/auth.mdx
index 9ac1d7af7..686c371cb 100644
--- a/apps/docs/content/docs/dev/auth.mdx
+++ b/apps/docs/content/docs/dev/auth.mdx
@@ -3,6 +3,10 @@ title: Authorization
description: xddd
---
+
+ We're working hard to bring you the best documentation experience.
+
+
## Configuration
You can configure the authentication settings in the `VitNodeAPI` function and `authorization` param.
diff --git a/apps/docs/content/docs/dev/database/index.mdx b/apps/docs/content/docs/dev/database/index.mdx
new file mode 100644
index 000000000..167663301
--- /dev/null
+++ b/apps/docs/content/docs/dev/database/index.mdx
@@ -0,0 +1,96 @@
+---
+title: Database
+description: Learn how to work with databases in VitNode plugins using Drizzle ORM and PostgreSQL.
+---
+
+VitNode plugins seamlessly integrate with databases using [Drizzle ORM](https://orm.drizzle.team/) and [PostgreSQL](https://www.postgresql.org/). This guide shows you how to define schemas, perform database operations, and manage migrations.
+
+## Defining Schema
+
+Create your database schema in the `database` directory of your plugin. Each table should be defined in its own file for better organization.
+
+```ts title="plugins/{plugin_name}/src/database/categories.ts"
+import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
+
+export const blog_categories = pgTable('blog_categories', {
+ id: serial().primaryKey(),
+ createdAt: timestamp().notNull().defaultNow(),
+ updatedAt: timestamp()
+ .notNull()
+ .$onUpdate(() => new Date()),
+});
+```
+
+## Accessing Database
+
+Access the database in your plugin handlers using `c.get('database')` from the Hono context. This provides a Drizzle ORM instance for all your database operations.
+
+```ts title="plugins/{plugin_name}/src/routes/posts.ts"
+export const postsRoute = buildRoute({
+ ...CONFIG_PLUGIN,
+ route: {},
+ handler: async c => {
+ // [!code ++:7]
+ const data = await c
+ .get('database')
+ .select({
+ id: blog_posts.id,
+ title: blog_posts.title,
+ })
+ .from(blog_posts);
+
+ return c.json(data);
+ },
+});
+```
+
+## Database Operations
+
+VitNode provides convenient commands for managing your database schema and migrations.
+
+### Creating Migrations
+
+Generate migration files when you modify your database schema:
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
+
+
+
+```bash tab="pnpm"
+pnpm db:migrate
+```
+
+```bash tab="bun"
+bun db:migrate
+```
+
+```bash tab="npm"
+npm run db:migrate
+```
+
+
+
+### Pushing Schema Changes
+
+Apply your schema changes directly to the database:
+
+
+
+```bash tab="pnpm"
+pnpm db:push
+```
+
+```bash tab="bun"
+bun db:push
+```
+
+```bash tab="npm"
+npm run db:push
+```
+
+
+
+
+ VitNode automatically runs migrations and schema updates when you start your
+ application in development mode, keeping your database in sync with your code.
+
diff --git a/apps/docs/content/docs/dev/pagination.mdx b/apps/docs/content/docs/dev/database/pagination.mdx
similarity index 100%
rename from apps/docs/content/docs/dev/pagination.mdx
rename to apps/docs/content/docs/dev/database/pagination.mdx
diff --git a/apps/docs/content/docs/dev/i18n/index.mdx b/apps/docs/content/docs/dev/i18n/index.mdx
new file mode 100644
index 000000000..9369ef8fc
--- /dev/null
+++ b/apps/docs/content/docs/dev/i18n/index.mdx
@@ -0,0 +1,10 @@
+---
+title: Expand Languages
+description: Expand the languages to your application.
+---
+
+
+ We're working hard to bring you the best documentation experience.
+
+
+As default VitNode uses the `en` _(English USA)_ language for the application.
diff --git a/apps/docs/content/docs/dev/i18n/meta.json b/apps/docs/content/docs/dev/i18n/meta.json
new file mode 100644
index 000000000..1efaf1161
--- /dev/null
+++ b/apps/docs/content/docs/dev/i18n/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Internationalization (I18n)",
+ "pages": ["expand-langs", "messages", "namespaces", "..."]
+}
diff --git a/apps/docs/content/docs/dev/i18n/namespaces.mdx b/apps/docs/content/docs/dev/i18n/namespaces.mdx
index 3d65491c1..55e5efa41 100644
--- a/apps/docs/content/docs/dev/i18n/namespaces.mdx
+++ b/apps/docs/content/docs/dev/i18n/namespaces.mdx
@@ -16,43 +16,24 @@ For example, you have a component that needs to get access to the translation st
To translate your content, select the plugin in `frontend` folder, go to `langs` folder and pick the language you want to translate.
-```json title="apps/frontend/src/plugins/{your_plugin}/langs/en.json"
-{
- "{your_plugin}": {
- "home": {
- "hello": "Hello World",
- "world": "World"
- }
- }
-}
-```
-
-
- Name your plugin should be a primary key in the JSON file or with the prefix `admin_` for admin plugins.
-
-```json title="apps/frontend/src/plugins/{your_plugin}/langs/en.json"
+```json title="plugins/{your_plugin}/src/locales/en.json"
{
"{your_plugin}": {
"hello": "Hello World" // ✅ This will work
},
- "admin_{your_plugin}" {
- "world": "World" // ✅ This will work
- },
"world": "World" // ❌ This will not work
}
```
-
-
## Usage
To get access to the translation strings in other namespaces you need to use the `TranslationsProvider` component.
-```tsx title="apps/frontend/src/app/[locale]/(main)/{your_plugin}/layout.tsx"
+```tsx title="plugins/{your_plugin}/src/app/blog/layout.tsx"
import { I18nProvider } from '@vitnode/core/components/i18n-provider';
export default function Layout({ children }: { children: React.ReactNode }) {
- return {children};
+ return {children};
}
```
diff --git a/apps/docs/content/docs/dev/index.mdx b/apps/docs/content/docs/dev/index.mdx
index 487673076..fd066bb90 100644
--- a/apps/docs/content/docs/dev/index.mdx
+++ b/apps/docs/content/docs/dev/index.mdx
@@ -4,6 +4,10 @@ description: Welcome to the VitNode documentation!
icon: Power
---
+
+ We're working hard to bring you the best documentation experience.
+
+
## Get started
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
diff --git a/apps/docs/content/docs/plugins/layouts-and-pages.mdx b/apps/docs/content/docs/dev/layouts-and-pages.mdx
similarity index 100%
rename from apps/docs/content/docs/plugins/layouts-and-pages.mdx
rename to apps/docs/content/docs/dev/layouts-and-pages.mdx
diff --git a/apps/docs/content/docs/dev/meta.json b/apps/docs/content/docs/dev/meta.json
index c78606f74..990412e35 100644
--- a/apps/docs/content/docs/dev/meta.json
+++ b/apps/docs/content/docs/dev/meta.json
@@ -11,9 +11,16 @@
"swagger",
"---Framework---",
"auth",
- "---API---",
+ "---Plugins---",
+ "plugins",
+ "rest-api",
"fetcher",
- "---Rest---",
+ "database",
+ "---UI---",
+ "layouts-and-pages",
+ "admin-page",
+ "i18n",
+ "---Advanced---",
"..."
]
}
diff --git a/apps/docs/content/docs/dev/plugins.mdx b/apps/docs/content/docs/dev/plugins.mdx
new file mode 100644
index 000000000..8fa5c705d
--- /dev/null
+++ b/apps/docs/content/docs/dev/plugins.mdx
@@ -0,0 +1,49 @@
+---
+title: Create Plugins
+description: Learn how to create plugins for VitNode to extend its functionality.
+---
+
+
+ We're working hard to bring you the best documentation experience.
+
+
+In this section we'll guide you through the process of creating your first VitNode plugin. Whether you're building a simple feature or a complex application, VitNode's plugin system has got you covered.
+
+By creating a plugin, you can extend the functionality like:
+
+- **🚀 APIs**: Build your own RESTful,
+- **🧩 UI Components**: Create reusable components for your application,
+- **📄 Pages and Layouts**: Define custom pages and layouts for your application,
+- **⚡ Middleware**: Add custom middleware to handle requests and responses,
+- **🗄️ Database Models**: Define your own database models and schemas,
+- **🔗 Integrations**: Connect with third-party services and APIs.
+- **⚙️ Custom Logic**: Implement any custom logic you need for your application.
+- **🌟 And much more!**
+
+## Create a Plugin
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
+
+
+
+```bash tab="pnpm"
+pnpm create vitnode-app@canary --plugin
+```
+
+```bash tab="bun"
+bun create vitnode-app@canary --plugin
+```
+
+```bash tab="npm"
+npx create-vitnode-app@canary --plugin
+```
+
+
+
+## Prerequisites
+
+### Ignore `(plugins)` Folders
+
+Before diving into plugin development, please ignore all `(plugins)` folders form search results in your IDE. These folders are generated by VitNode and contain the plugin code, which is not meant to be modified directly.
+
+If you're using VS Code, this configuration is provided out-of-the-box via the `.vscode/settings.json` file.
diff --git a/apps/docs/content/docs/plugins/routing.mdx b/apps/docs/content/docs/dev/rest-api.mdx
similarity index 99%
rename from apps/docs/content/docs/plugins/routing.mdx
rename to apps/docs/content/docs/dev/rest-api.mdx
index e0eaea045..cdf7e904c 100644
--- a/apps/docs/content/docs/plugins/routing.mdx
+++ b/apps/docs/content/docs/dev/rest-api.mdx
@@ -1,5 +1,5 @@
---
-title: Routing
+title: Restful API
description: Learn how to create and organize API routes in your VitNode plugins with modules, handlers, and parameter validation.
---
diff --git a/apps/docs/content/docs/plugins/sso.mdx b/apps/docs/content/docs/dev/sso.mdx
similarity index 99%
rename from apps/docs/content/docs/plugins/sso.mdx
rename to apps/docs/content/docs/dev/sso.mdx
index fb9bfafc8..aed47d6d4 100644
--- a/apps/docs/content/docs/plugins/sso.mdx
+++ b/apps/docs/content/docs/dev/sso.mdx
@@ -333,7 +333,7 @@ VitNodeAPI({
plugins: [],
authorization: {
// [!code ++]
- ssoPlugins: [
+ ssoProviders: [
// [!code ++]
DiscordSSOApiPlugin({
// [!code ++]
diff --git a/apps/docs/content/docs/dev/swagger.mdx b/apps/docs/content/docs/dev/swagger.mdx
index 8832d7257..375459a50 100644
--- a/apps/docs/content/docs/dev/swagger.mdx
+++ b/apps/docs/content/docs/dev/swagger.mdx
@@ -4,6 +4,10 @@ description: Generate API documentation with OpenAPI.
icon: ListCollapse
---
+
+ We're working hard to bring you the best documentation experience.
+
+
Swagger is a tool that helps you generate API documentation with OpenAPI. It's a powerful tool that can help you write documentation automatically and validate the request.
## Usage
diff --git a/apps/docs/content/docs/plugins/blog.mdx b/apps/docs/content/docs/guides/blog.mdx
similarity index 100%
rename from apps/docs/content/docs/plugins/blog.mdx
rename to apps/docs/content/docs/guides/blog.mdx
diff --git a/apps/docs/content/docs/guides/meta.json b/apps/docs/content/docs/guides/meta.json
index 4688a8787..3e8466f9a 100644
--- a/apps/docs/content/docs/guides/meta.json
+++ b/apps/docs/content/docs/guides/meta.json
@@ -6,6 +6,8 @@
"pages": [
"index",
"...",
+ "---Plugins by VitNode---",
+ "blog",
"--- Authorization ---",
"sso",
"--- Security ---",
diff --git a/apps/docs/content/docs/guides/sso/discord.mdx b/apps/docs/content/docs/guides/sso/discord.mdx
index 69fa65f35..edc1e382d 100644
--- a/apps/docs/content/docs/guides/sso/discord.mdx
+++ b/apps/docs/content/docs/guides/sso/discord.mdx
@@ -68,7 +68,7 @@ VitNodeAPI({
// [!code ++]
authorization: {
// [!code ++]
- ssoPlugins: [
+ ssoProviders: [
// [!code ++]
new DiscordSSOApiPlugin({
// [!code ++]
diff --git a/apps/docs/content/docs/guides/sso/facebook.mdx b/apps/docs/content/docs/guides/sso/facebook.mdx
index f1d1486f3..8674b2f01 100644
--- a/apps/docs/content/docs/guides/sso/facebook.mdx
+++ b/apps/docs/content/docs/guides/sso/facebook.mdx
@@ -62,7 +62,7 @@ VitNodeAPI({
// [!code ++]
authorization
// [!code ++]
- ssoPlugins: [
+ ssoProviders: [
// [!code ++]
new FacebookSSOApiPlugin({
// [!code ++]
diff --git a/apps/docs/content/docs/guides/sso/google.mdx b/apps/docs/content/docs/guides/sso/google.mdx
index d9a9edcf2..c5ce45613 100644
--- a/apps/docs/content/docs/guides/sso/google.mdx
+++ b/apps/docs/content/docs/guides/sso/google.mdx
@@ -123,7 +123,7 @@ VitNodeAPI({
// [!code ++]
authorization: {
// [!code ++]
- ssoPlugins: [
+ ssoProviders: [
// [!code ++]
new GoogleSSOApiPlugin({
// [!code ++]
diff --git a/apps/docs/content/docs/plugins/database/index.mdx b/apps/docs/content/docs/plugins/database/index.mdx
deleted file mode 100644
index 6c5d93bc9..000000000
--- a/apps/docs/content/docs/plugins/database/index.mdx
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: Working with Database
-description: elo123
----
-
-VitNode plugins can interact with databases using [Drizzle ORM](https://orm.drizzle.team/) and [PostgreSQL](https://www.postgresql.org/).
-
-## Schema
-
-You can define your database schema in `database` directory of your plugin.
-
-```ts title="plugins/{plugin_name}/src/database/categories.ts"
-import { pgTable } from 'drizzle-orm/pg-core';
-
-export const blog_categories = pgTable('blog_categories', t => ({
- id: t.serial().primaryKey(),
- createdAt: t.timestamp().notNull().defaultNow(),
- updatedAt: t
- .timestamp()
- .notNull()
- .$onUpdate(() => new Date()),
-}));
-```
diff --git a/apps/docs/content/docs/plugins/index.mdx b/apps/docs/content/docs/plugins/index.mdx
deleted file mode 100644
index 530652cd4..000000000
--- a/apps/docs/content/docs/plugins/index.mdx
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: Get Started
-description: Create awesome plugins for VitNode.
-icon: House
----
-
-
- We're working on the plugin system, so stay tuned for updates! 🚀
-
-
-import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
-
-
-
-```bash tab="pnpm"
-pnpm create vitnode-app@canary --plugin
-```
-
-```bash tab="bun"
-bun create vitnode-app@canary --plugin
-```
-
-```bash tab="npm"
-npx create-vitnode-app@canary --plugin
-```
-
-
diff --git a/apps/docs/content/docs/plugins/meta.json b/apps/docs/content/docs/plugins/meta.json
deleted file mode 100644
index f0b4d4416..000000000
--- a/apps/docs/content/docs/plugins/meta.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "title": "Plugins",
- "description": "Create and share own plugins",
- "icon": "Plug",
- "root": true,
- "pages": [
- "index",
- "---Plugins by VitNode---",
- "blog",
- "---API---",
- "routing",
- "database",
- "---UI---",
- "layouts-and-pages",
- "admin-page",
- "---Advanced---",
- "..."
- ]
-}
diff --git a/apps/docs/content/docs/ui/dropdown-menu.mdx b/apps/docs/content/docs/ui/dropdown-menu.mdx
new file mode 100644
index 000000000..807fbe96d
--- /dev/null
+++ b/apps/docs/content/docs/ui/dropdown-menu.mdx
@@ -0,0 +1,31 @@
+---
+title: Dropdown Menu
+description: A dropdown menu component for building interactive menus in your application.
+---
+
+## Usage
+
+```ts
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '@vitnode/core/components/ui/dropdown-menu';
+```
+
+```tsx
+
+ Open
+
+ My Account
+
+ Profile
+ Billing
+ Team
+ Subscription
+
+
+```
diff --git a/apps/docs/content/docs/ui/index.mdx b/apps/docs/content/docs/ui/index.mdx
index f0e7cf891..b36d95986 100644
--- a/apps/docs/content/docs/ui/index.mdx
+++ b/apps/docs/content/docs/ui/index.mdx
@@ -4,6 +4,10 @@ description: Welcome to the VitNode documentation!
icon: Power
---
+
+ We're working hard to bring you the best documentation experience.
+
+
Welcome to the docs! You can start writing documents in `/content/docs`.
## What is Next?
diff --git a/apps/docs/src/app/global.css b/apps/docs/src/app/global.css
index cf465d9a2..a3f1e1bf4 100644
--- a/apps/docs/src/app/global.css
+++ b/apps/docs/src/app/global.css
@@ -40,9 +40,8 @@
--sidebar-ring: oklch(0.708 0 0);
--dev-color: oklch(0.6 0.18 50);
- --plugins-color: oklch(0.65 0.18 170);
+ --ui-color: oklch(0.65 0.18 170);
--guides-color: oklch(0.45 0.16 262.61);
- --ui-color: oklch(0.5 0.18 280);
}
.dark {
@@ -78,9 +77,8 @@
--sidebar-ring: oklch(0.51 0.16 262.61);
--dev-color: oklch(0.75 0.18 50);
- --plugins-color: oklch(0.7 0.18 170);
+ --ui-color: oklch(0.7 0.18 170);
--guides-color: oklch(0.65 0.16 262.61);
- --ui-color: oklch(0.78 0.18 280);
}
:root {
diff --git a/apps/docs/src/content/docs/guides/sso/discord.mdx b/apps/docs/src/content/docs/guides/sso/discord.mdx
index 69fa65f35..edc1e382d 100644
--- a/apps/docs/src/content/docs/guides/sso/discord.mdx
+++ b/apps/docs/src/content/docs/guides/sso/discord.mdx
@@ -68,7 +68,7 @@ VitNodeAPI({
// [!code ++]
authorization: {
// [!code ++]
- ssoPlugins: [
+ ssoProviders: [
// [!code ++]
new DiscordSSOApiPlugin({
// [!code ++]
diff --git a/apps/docs/src/content/docs/guides/sso/facebook.mdx b/apps/docs/src/content/docs/guides/sso/facebook.mdx
index f1d1486f3..8674b2f01 100644
--- a/apps/docs/src/content/docs/guides/sso/facebook.mdx
+++ b/apps/docs/src/content/docs/guides/sso/facebook.mdx
@@ -62,7 +62,7 @@ VitNodeAPI({
// [!code ++]
authorization
// [!code ++]
- ssoPlugins: [
+ ssoProviders: [
// [!code ++]
new FacebookSSOApiPlugin({
// [!code ++]
diff --git a/apps/docs/src/content/docs/guides/sso/google.mdx b/apps/docs/src/content/docs/guides/sso/google.mdx
index d9a9edcf2..c5ce45613 100644
--- a/apps/docs/src/content/docs/guides/sso/google.mdx
+++ b/apps/docs/src/content/docs/guides/sso/google.mdx
@@ -123,7 +123,7 @@ VitNodeAPI({
// [!code ++]
authorization: {
// [!code ++]
- ssoPlugins: [
+ ssoProviders: [
// [!code ++]
new GoogleSSOApiPlugin({
// [!code ++]
diff --git a/apps/web/src/app/[locale]/(main)/layout.tsx b/apps/web/src/app/[locale]/(main)/layout.tsx
index b55a566bb..086bc3368 100644
--- a/apps/web/src/app/[locale]/(main)/layout.tsx
+++ b/apps/web/src/app/[locale]/(main)/layout.tsx
@@ -1,9 +1,14 @@
import { LogoVitNode } from '@vitnode/core/components/logo-vitnode';
import { ThemeLayout } from '@vitnode/core/views/layouts/theme/layout';
+import { vitNodeConfig } from '../../../vitnode.config';
+
export default function Layout({ children }: { children: React.ReactNode }) {
return (
- }>
+ }
+ vitNodeConfig={vitNodeConfig}
+ >
{children}
);
diff --git a/apps/web/src/app/[locale]/layout.tsx b/apps/web/src/app/[locale]/layout.tsx
index 7a1615563..f830831ba 100644
--- a/apps/web/src/app/[locale]/layout.tsx
+++ b/apps/web/src/app/[locale]/layout.tsx
@@ -23,7 +23,7 @@ export const generateMetadata = (): Metadata =>
generateMetadataRootLayout(vitNodeConfig);
export const generateStaticParams = () =>
- vitNodeConfig.i18n.locales.map(locale => ({ locale }));
+ vitNodeConfig.i18n.locales.map(locale => ({ locale: locale.code }));
export default function LocaleLayout(props: RootLayoutProps) {
return (
diff --git a/apps/web/src/locales/@vitnode/blog/pl.json b/apps/web/src/locales/@vitnode/blog/pl.json
new file mode 100644
index 000000000..65f92dfff
--- /dev/null
+++ b/apps/web/src/locales/@vitnode/blog/pl.json
@@ -0,0 +1,70 @@
+{
+ "@vitnode/blog": {
+ "title": "Blog",
+ "admin": {
+ "nav": {
+ "posts": "Posts",
+ "categories": "Categories"
+ },
+ "categories": {
+ "desc": "Manage categories for blog posts.",
+ "table": {
+ "title": "Title",
+ "updated_at": "Updated At"
+ },
+ "delete": {
+ "title": "Delete Category",
+ "desc": "Are you sure you want to delete
category? This action cannot be undone.",
+ "confirm": "Yes, delete this category",
+ "success": "Category has been deleted successfully."
+ },
+ "create": {
+ "title": "Create Category",
+ "desc": "A new category for your blog posts.",
+ "form": {
+ "title": {
+ "label": "Title",
+ "already_exists": "This category title already exists."
+ }
+ },
+ "submit": "Create"
+ },
+ "edit": {
+ "title": "Edit Category",
+ "submit": "Save Changes"
+ }
+ },
+ "posts": {
+ "desc": "Write and manage your blog posts.",
+ "table": {
+ "title": "Title",
+ "category": "Category",
+ "updated_at": "Updated At"
+ },
+ "create": {
+ "title": "Create Post",
+ "desc": "Write a new article for your blog.",
+ "form": {
+ "title": {
+ "label": "Title",
+ "already_exists": "This post title already exists."
+ },
+ "content": "Content",
+ "category": "Category"
+ },
+ "submit": "Create Post"
+ },
+ "edit": {
+ "title": "Edit Post",
+ "submit": "Save Changes"
+ },
+ "delete": {
+ "title": "Delete Post",
+ "desc": "Are you sure you want to delete post? This action cannot be undone.",
+ "confirm": "Yes, delete this post",
+ "success": "Post has been deleted successfully."
+ }
+ }
+ }
+ }
+}
diff --git a/apps/web/src/locales/@vitnode/core/pl.json b/apps/web/src/locales/@vitnode/core/pl.json
new file mode 100644
index 000000000..b89001b73
--- /dev/null
+++ b/apps/web/src/locales/@vitnode/core/pl.json
@@ -0,0 +1,155 @@
+{
+ "core": {
+ "global": {
+ "no_results": {
+ "title": "No results found",
+ "desc": "Try adjusting your search or filter criteria."
+ },
+ "are_you_sure_want_to_leave_form": {
+ "title": "Are you sure you want to leave this form?",
+ "desc": "Your changes will not be saved.",
+ "cancel": "Cancel",
+ "confirm": "Yes, leave"
+ },
+ "search_placeholder": "Search...",
+ "results_not_found": "No results found",
+ "date": "{date, date}",
+ "date_medium": "{date, date, medium}",
+ "date_short": "{date, date, short}",
+ "register": "Register",
+ "login": "Login",
+ "save": "Save",
+ "submit": "Submit",
+ "cancel": "Cancel",
+ "optional": "Optional",
+ "loading": "Loading...",
+ "or": "or",
+ "back_home": "Back to home",
+ "go_back": "Go back",
+ "select_option": "Select an option",
+ "select_options": "Select options",
+ "go_to_prev_page": "Go to previous page",
+ "go_to_next_page": "Go to next page",
+ "errors": {
+ "title": "Oops! Something went wrong.",
+ "internal_server_error": "Internal server error.",
+ "field_required": "This field is required.",
+ "field_min_length": "This field must be at least {min} characters.",
+ "404": {
+ "title": "Page Not Found",
+ "desc": "Oops! The page you're looking for doesn't exist."
+ },
+ "500": {
+ "title": "Internal Server Error",
+ "desc": "Sorry, we're experiencing technical difficulties on our server."
+ },
+ "400": {
+ "title": "Bad Request",
+ "desc": "The request couldn't be processed due to invalid parameters."
+ },
+ "403": {
+ "title": "Forbidden",
+ "desc": "You don't have permission to access this resource."
+ }
+ },
+ "user_bar": {
+ "log_out": "Log out",
+ "admin_cp": "Admin CP"
+ }
+ },
+ "auth": {
+ "sso": {
+ "or": "Or continue With",
+ "access_denied": "You have denied access to the application or the request has expired. Please try again."
+ },
+ "sign_up": {
+ "desc": "Hello there! Create your account to get started.",
+ "already_have_account": "You already have an account? Sign in.",
+ "submit": "Register",
+ "username": {
+ "label": "Username",
+ "min_length": "Username must be at least 3 characters long.",
+ "max_length": "Username must be at most 32 characters long.",
+ "exists": "Username already exists.",
+ "your_user_code": "Your user code: "
+ },
+ "email": {
+ "label": "Email",
+ "invalid": "Invalid email address.",
+ "exists": "Email already exists."
+ },
+ "password": {
+ "label": "Password",
+ "invalid": "Password is too weak.",
+ "requirements": {
+ "label": "Password should contain:",
+ "min_length": "At least 8 characters",
+ "uppercase": "At least one uppercase letter",
+ "number": "At least one number",
+ "special_char": "At least one special character"
+ }
+ },
+ "terms": {
+ "label": "Accept terms and conditions",
+ "required": "You must accept the terms and conditions.",
+ "desc": "You agree to our Legal documents & Policies."
+ },
+ "newsletter": {
+ "label": "Newsletter",
+ "desc": "Receive the latest news and updates."
+ },
+ "email_confirmation": {
+ "title": "Check your email",
+ "desc": "We've sent a confirmation link to your email address",
+ "check_spam": "If you don't see the email in your inbox, please check your spam folder."
+ }
+ },
+ "sign_in": {
+ "desc": "Welcome back! Sign in to your account.",
+ "do_not_have_account": "Don't have an account? Sign up.",
+ "email": {
+ "label": "Email",
+ "invalid": "Invalid email address."
+ },
+ "password": {
+ "label": "Password",
+ "required": "Password is required."
+ },
+ "errors": {
+ "access_denied": {
+ "title": "Invalid credentials",
+ "desc": "The email address or password was incorrect. Please try again (make sure your caps lock is off)."
+ }
+ },
+ "submit": "Login"
+ }
+ }
+ },
+ "admin": {
+ "dashboard": {
+ "dev_mode": "Development Mode",
+ "version": "Version: {version}"
+ },
+ "global": {
+ "nav": {
+ "core": "Core",
+ "dashboard": "Dashboard",
+ "users": {
+ "title": "Users",
+ "list": "User List"
+ },
+ "user_bar": {
+ "home_page": "Home Page"
+ }
+ }
+ },
+ "user": {
+ "list": {
+ "desc": "Manage users of your application.",
+ "user": "User",
+ "createdAt": "Created At",
+ "emailNotVerified": "Email Not Verified"
+ }
+ }
+ }
+}
diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts
index ac037e605..d3692d532 100644
--- a/apps/web/src/middleware.ts
+++ b/apps/web/src/middleware.ts
@@ -3,7 +3,7 @@ import createMiddleware from 'next-intl/middleware';
import { vitNodeConfig } from './vitnode.config';
export default createMiddleware({
- locales: vitNodeConfig.i18n.locales,
+ locales: vitNodeConfig.i18n.locales.map(locale => locale.code),
defaultLocale: vitNodeConfig.i18n.defaultLocale,
localePrefix: vitNodeConfig.i18n.localePrefix,
});
diff --git a/apps/web/src/vitnode.api.config.ts b/apps/web/src/vitnode.api.config.ts
index 4fd7f2934..bcd8b48e6 100644
--- a/apps/web/src/vitnode.api.config.ts
+++ b/apps/web/src/vitnode.api.config.ts
@@ -4,15 +4,13 @@ import { DiscordSSOApiPlugin } from '@vitnode/core/api/plugins/sso/discord';
import { FacebookSSOApiPlugin } from '@vitnode/core/api/plugins/sso/facebook';
import { GoogleSSOApiPlugin } from '@vitnode/core/api/plugins/sso/google';
import { buildApiConfig } from '@vitnode/core/vitnode.config';
+import * as dotenv from 'dotenv';
import { drizzle } from 'drizzle-orm/postgres-js';
+import { join } from 'path';
-// import * as dotenv from 'dotenv';
-// import { drizzle } from 'drizzle-orm/postgres-js';
-// import { join } from 'path';
-
-// dotenv.config({
-// path: join(process.cwd(), '..', '..', '.env'),
-// });
+dotenv.config({
+ path: join(process.cwd(), '..', '..', '.env'),
+});
export const POSTGRES_URL =
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
@@ -31,7 +29,7 @@ export const vitNodeApiConfig = buildApiConfig({
user: process.env.NOD_EMAILER_USER,
}),
authorization: {
- ssoPlugins: [
+ ssoProviders: [
DiscordSSOApiPlugin({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
diff --git a/apps/web/src/vitnode.config.ts b/apps/web/src/vitnode.config.ts
index a1fa8f302..2df1ce21d 100644
--- a/apps/web/src/vitnode.config.ts
+++ b/apps/web/src/vitnode.config.ts
@@ -9,7 +9,16 @@ export const vitNodeConfig = buildConfig({
},
plugins: [blogPlugin()],
i18n: {
- locales: ['en', 'pl'] as const,
+ locales: [
+ {
+ code: 'en',
+ name: 'English (USA)',
+ },
+ {
+ code: 'pl',
+ name: 'Polski (PL)',
+ },
+ ],
defaultLocale: 'en',
},
theme: {
diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/middleware.ts b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/middleware.ts
index 02d5ae94f..99e9d7051 100644
--- a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/middleware.ts
+++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/middleware.ts
@@ -3,7 +3,7 @@ import createMiddleware from 'next-intl/middleware';
import { vitNodeConfig } from './vitnode.config';
export default createMiddleware({
- locales: vitNodeConfig.i18n.locales,
+ locales: vitNodeConfig.i18n.locales.map(locale => locale.code),
defaultLocale: vitNodeConfig.i18n.defaultLocale,
localePrefix: vitNodeConfig.i18n.localePrefix,
});
diff --git a/packages/vitnode/scripts/plugin.ts b/packages/vitnode/scripts/plugin.ts
index aa4618d60..d9bfc5f8e 100644
--- a/packages/vitnode/scripts/plugin.ts
+++ b/packages/vitnode/scripts/plugin.ts
@@ -108,6 +108,12 @@ export const processPlugin = ({ initMessage }: { initMessage: string }) => {
const cleanupDeletedFiles = (sourceDir: string, destinationDir: string) => {
if (!existsSync(destinationDir)) return;
+ // Check if this is a locale directory - if so, skip cleanup to preserve other language files
+ const isLocaleDir = destinationDir.includes(join('src', 'locales'));
+ if (isLocaleDir) {
+ return; // Skip cleanup for locale directories to preserve files from other plugins/languages
+ }
+
const destFiles = getAllFiles(destinationDir);
for (const destFile of destFiles) {
const relativePath = relative(destinationDir, destFile);
diff --git a/packages/vitnode/src/api/middlewares/global/global.ts b/packages/vitnode/src/api/middlewares/global/global.ts
index e58556d2e..545206fc9 100644
--- a/packages/vitnode/src/api/middlewares/global/global.ts
+++ b/packages/vitnode/src/api/middlewares/global/global.ts
@@ -39,7 +39,7 @@ interface EnvVariablesVitNode {
cookieSecure: boolean;
deviceCookieExpires: number;
deviceCookieName: string;
- ssoPlugins: SSOApiPlugin[];
+ ssoProviders: SSOApiPlugin[];
};
emailProvider?: EmailApiPlugin;
metadata: {
@@ -88,7 +88,7 @@ export const globalMiddleware = ({
cookieName: authorization?.cookieName ?? 'vitnode_auth',
cookie_expires:
authorization?.cookieExpires ?? 1000 * 60 * 60 * 24 * 90, // 90 days
- ssoPlugins: authorization?.ssoPlugins ?? [],
+ ssoProviders: authorization?.ssoProviders ?? [],
deviceCookieName: authorization?.deviceCookieName ?? 'vitnode_device',
deviceCookieExpires:
authorization?.deviceCookieExpires ?? 1000 * 60 * 60 * 24 * 365, // 1 year,
diff --git a/packages/vitnode/src/api/models/session-admin.ts b/packages/vitnode/src/api/models/session-admin.ts
index d3d66e197..4573147f4 100644
--- a/packages/vitnode/src/api/models/session-admin.ts
+++ b/packages/vitnode/src/api/models/session-admin.ts
@@ -61,8 +61,7 @@ export class SessionAdminModel {
setCookie(this.c, this.c.get('core').authorization.adminCookieName, token, {
httpOnly: true,
secure: this.c.get('core').authorization.cookieSecure,
-
- path: '/admin',
+ path: '/',
expires: new Date(
Date.now() + this.c.get('core').authorization.adminCookieExpires,
),
diff --git a/packages/vitnode/src/api/models/sso.ts b/packages/vitnode/src/api/models/sso.ts
index 10e13d82f..7cb952ed4 100644
--- a/packages/vitnode/src/api/models/sso.ts
+++ b/packages/vitnode/src/api/models/sso.ts
@@ -30,7 +30,7 @@ export const getRedirectUri = (code: string) =>
export class SSOModel {
constructor(c: Context) {
this.c = c;
- this.plugins = c.get('core').authorization.ssoPlugins;
+ this.plugins = c.get('core').authorization.ssoProviders;
}
private readonly c: Context;
diff --git a/packages/vitnode/src/api/modules/middleware/route.ts b/packages/vitnode/src/api/modules/middleware/route.ts
index 7c880d5ed..e8f33690a 100644
--- a/packages/vitnode/src/api/modules/middleware/route.ts
+++ b/packages/vitnode/src/api/modules/middleware/route.ts
@@ -25,7 +25,7 @@ export const routeMiddleware = buildRoute({
},
},
handler: c => {
- const sso = c.get('core').authorization.ssoPlugins;
+ const sso = c.get('core').authorization.ssoProviders;
const email = new EmailModel(c);
return c.json({
diff --git a/packages/vitnode/src/components/switchers/langs/language-swietcher.tsx b/packages/vitnode/src/components/switchers/langs/language-swietcher.tsx
new file mode 100644
index 000000000..b75c62654
--- /dev/null
+++ b/packages/vitnode/src/components/switchers/langs/language-swietcher.tsx
@@ -0,0 +1,58 @@
+'use client';
+
+import { CheckIcon, LanguagesIcon } from 'lucide-react';
+import { useLocale } from 'next-intl';
+import React from 'react';
+
+import type { LocaleConfig } from '@/vitnode.config';
+
+import { usePathname, useRouter } from '@/lib/navigation';
+
+import { Button } from '../../ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '../../ui/dropdown-menu';
+
+export const LanguageSwitcher = ({ locales }: { locales: LocaleConfig[] }) => {
+ const currentLocale = useLocale();
+ const [isPending, startTransition] = React.useTransition();
+ const { replace } = useRouter();
+
+ const pathname = usePathname();
+
+ return (
+
+
+
+
+
+
+ {locales.map(locale => (
+ {
+ startTransition(() => {
+ replace(pathname, {
+ locale: locale.code,
+ });
+ });
+ }}
+ >
+ {locale.name}
+ {locale.code === currentLocale && }
+
+ ))}
+
+
+ );
+};
diff --git a/packages/vitnode/src/components/switchers/theme-switcher.tsx b/packages/vitnode/src/components/switchers/themes/theme-switcher.tsx
similarity index 93%
rename from packages/vitnode/src/components/switchers/theme-switcher.tsx
rename to packages/vitnode/src/components/switchers/themes/theme-switcher.tsx
index 91cc127b2..eeeb9e05b 100644
--- a/packages/vitnode/src/components/switchers/theme-switcher.tsx
+++ b/packages/vitnode/src/components/switchers/themes/theme-switcher.tsx
@@ -3,7 +3,7 @@
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
-import { Button } from '../ui/button';
+import { Button } from '../../ui/button';
export const ThemeSwitcher = () => {
const { setTheme, resolvedTheme } = useTheme();
diff --git a/packages/vitnode/src/views/admin/layouts/admin-layout.tsx b/packages/vitnode/src/views/admin/layouts/admin-layout.tsx
index 936cb6b03..b8742a6b5 100644
--- a/packages/vitnode/src/views/admin/layouts/admin-layout.tsx
+++ b/packages/vitnode/src/views/admin/layouts/admin-layout.tsx
@@ -1,7 +1,7 @@
import { getTranslations } from 'next-intl/server';
import { cookies } from 'next/headers';
-import { ThemeSwitcher } from '@/components/switchers/theme-switcher';
+import { ThemeSwitcher } from '@/components/switchers/themes/theme-switcher';
import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
import { SidebarInset } from '@/components/ui/sidebar';
import { getSessionAdminApi } from '@/lib/api/get-session-admin-api';
@@ -10,6 +10,7 @@ import type { VitNodeConfig } from '../../../vitnode.config';
import type { NavAdminParent } from './sidebar/nav/nav';
import { I18nProvider } from '../../../components/i18n-provider';
+import { LanguageSwitcher } from '../../../components/switchers/langs/language-swietcher';
import { SidebarAdmin } from './sidebar/sidebar';
import { UserBarAdmin } from './user-bar/user-bar';
@@ -62,6 +63,7 @@ export const AdminLayout = async ({
+
diff --git a/packages/vitnode/src/views/auth/sso/buttons/sso-buttons.tsx b/packages/vitnode/src/views/auth/sso/buttons/sso-buttons.tsx
index 1d22e8757..d5471e8ea 100644
--- a/packages/vitnode/src/views/auth/sso/buttons/sso-buttons.tsx
+++ b/packages/vitnode/src/views/auth/sso/buttons/sso-buttons.tsx
@@ -20,6 +20,10 @@ export const SSOButtons = async () => {
getMiddlewareApi(),
]);
+ if (!sso.length) {
+ return null;
+ }
+
return (
<>
@@ -27,24 +31,18 @@ export const SSOButtons = async () => {
- {sso.length > 0 && (
-
-
- {t('or')}
-
-
- )}
+
+ {t('or')}
+
- {sso.length > 0 && (
-
- {sso.map(provider => (
-
- {provider.name}
-
- ))}
-
- )}
+
+ {sso.map(provider => (
+
+ {provider.name}
+
+ ))}
+
>
);
};
diff --git a/packages/vitnode/src/views/layouts/theme/header/header.tsx b/packages/vitnode/src/views/layouts/theme/header/header.tsx
index bad287497..2b4d51a9e 100644
--- a/packages/vitnode/src/views/layouts/theme/header/header.tsx
+++ b/packages/vitnode/src/views/layouts/theme/header/header.tsx
@@ -1,6 +1,9 @@
-import { Suspense } from 'react';
+import React from 'react';
-import { ThemeSwitcher } from '@/components/switchers/theme-switcher';
+import type { VitNodeConfig } from '@/vitnode.config';
+
+import { LanguageSwitcher } from '@/components/switchers/langs/language-swietcher';
+import { ThemeSwitcher } from '@/components/switchers/themes/theme-switcher';
import { Skeleton } from '@/components/ui/skeleton';
import { Link } from '@/lib/navigation';
import { cn } from '@/lib/utils';
@@ -10,8 +13,12 @@ import { UserHeader } from './user/user';
export const HeaderLayout = ({
logo,
className,
+ vitNodeConfig,
...props
-}: React.ComponentProps<'header'> & { logo: React.ReactNode }) => {
+}: React.ComponentProps<'header'> & {
+ logo: React.ReactNode;
+ vitNodeConfig: VitNodeConfig;
+}) => {
return (
diff --git a/packages/vitnode/src/views/layouts/theme/layout.tsx b/packages/vitnode/src/views/layouts/theme/layout.tsx
index 7f6c4b6c6..111b7a8e9 100644
--- a/packages/vitnode/src/views/layouts/theme/layout.tsx
+++ b/packages/vitnode/src/views/layouts/theme/layout.tsx
@@ -1,14 +1,19 @@
+import type { VitNodeConfig } from '../../../vitnode.config';
+
import { HeaderLayout } from './header/header';
export const ThemeLayout = ({
children,
logo,
+ vitNodeConfig,
}: React.ComponentProps & {
children: React.ReactNode;
+ vitNodeConfig: VitNodeConfig;
}) => {
return (
<>
- {children}
+ {' '}
+ {children}
>
);
};
diff --git a/packages/vitnode/src/vitnode.config.ts b/packages/vitnode/src/vitnode.config.ts
index 206ef63be..18ece55a0 100644
--- a/packages/vitnode/src/vitnode.config.ts
+++ b/packages/vitnode/src/vitnode.config.ts
@@ -6,13 +6,20 @@ import type { EmailApiPlugin } from './api/models/email';
import type { SSOApiPlugin } from './api/models/sso';
import type { BuildPluginReturn } from './lib/plugin';
-export interface VitNodeConfig {
+export interface LocaleConfig {
+ code: string;
+ name: string;
+}
+
+export interface VitNodeConfig<
+ AppLocales extends LocaleConfig[] = LocaleConfig[],
+> {
admin?: {
sidebarCookieName?: string;
};
debug?: boolean;
i18n: {
- defaultLocale: AppLocales[number];
+ defaultLocale: AppLocales[number]['code'];
localePrefix?: 'always' | 'as-needed' | 'never';
locales: AppLocales;
timeZone?: string;
@@ -37,14 +44,14 @@ export interface VitNodeApiConfig {
cookieSecure?: boolean;
deviceCookieExpires?: number;
deviceCookieName?: string;
- ssoPlugins?: SSOApiPlugin[];
+ ssoProviders?: SSOApiPlugin[];
};
dbProvider: PostgresJsDatabase;
emailProvider?: EmailApiPlugin;
plugins: BuildPluginApiReturn[];
}
-export function buildConfig(
+export function buildConfig(
args: VitNodeConfig,
): VitNodeConfig {
return {
@@ -70,8 +77,9 @@ export const handleRequestConfig = async ({
vitNodeConfig: VitNodeConfig;
}) => {
const reqLocale = await requestLocale;
+ const localeCodes = vitNodeConfig.i18n.locales.map(locale => locale.code);
const locale =
- reqLocale && `${vitNodeConfig.i18n.locales}`.includes(reqLocale)
+ reqLocale && localeCodes.includes(reqLocale)
? reqLocale
: vitNodeConfig.i18n.defaultLocale;