├── .env.example ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .storybook ├── Wrapper.svelte ├── main.ts ├── preview-head.html └── preview.ts ├── .typesafe-i18n.json ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prisma ├── migrations │ ├── 20221220114054_init │ │ └── migration.sql │ └── migration_lock.toml ├── schema.prisma └── seed.ts ├── src ├── app.d.ts ├── app.html ├── assets │ └── bigben.avif ├── helpers │ ├── scripts │ │ └── trpc.ts │ └── styles │ │ ├── a11y.scss │ │ ├── main.scss │ │ └── variables.scss ├── hooks.server.ts ├── i18n │ ├── de │ │ └── index.ts │ ├── en │ │ └── index.ts │ ├── formatters.ts │ ├── i18n-svelte.ts │ ├── i18n-types.ts │ ├── i18n-util.async.ts │ ├── i18n-util.sync.ts │ └── i18n-util.ts ├── lib │ ├── components │ │ ├── _reusables │ │ │ └── .gitkeep │ │ ├── atoms │ │ │ ├── .gitkeep │ │ │ ├── Button │ │ │ │ ├── Button.stories.svelte │ │ │ │ └── Button.svelte │ │ │ └── Logo │ │ │ │ ├── Logo.stories.svelte │ │ │ │ ├── Logo.svelte │ │ │ │ └── svelte.svg │ │ ├── molecules │ │ │ └── .gitkeep │ │ └── organisms │ │ │ └── .gitkeep │ └── server │ │ ├── prisma.ts │ │ └── trpc │ │ ├── _app.ts │ │ ├── createContext.ts │ │ ├── middleware │ │ └── isAuthenticated.ts │ │ └── server.ts └── routes │ ├── (api) │ └── trpc │ │ └── [...args] │ │ └── +server.ts │ └── (app) │ ├── +layout.server.ts │ ├── +layout.svelte │ ├── +page.svelte │ └── [locale] │ ├── +page.server.ts │ └── +page.svelte ├── static └── favicon.png ├── svelte.config.js ├── tsconfig.json ├── types ├── HTMLImageElement.d.ts ├── poppanator-sveltekit-svg.d.ts └── sveltejs-enhanced-img.d.ts └── vite.config.js /.env.example: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL="file:./dev.db" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier', 'plugin:storybook/recommended'], 5 | plugins: ['svelte', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ 8 | files: ["*.svelte"], 9 | parser: "svelte-eslint-parser", 10 | parserOptions: { 11 | parser: "@typescript-eslint/parser", 12 | }, 13 | }], 14 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | prisma/*.db 12 | prisma/*.db-journal -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore PNPM, NPM and YARN files 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /.storybook/Wrapper.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/sveltekit'; 2 | const config: StorybookConfig = { 3 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx|svelte)'], 4 | addons: [ 5 | '@storybook/addon-links', 6 | '@storybook/addon-essentials', 7 | '@storybook/addon-interactions', 8 | '@storybook/addon-svelte-csf' 9 | ], 10 | framework: { 11 | name: '@storybook/sveltekit', 12 | options: {} 13 | }, 14 | docs: { 15 | autodocs: 'tag' 16 | } 17 | }; 18 | export default config; 19 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from '@storybook/svelte'; 2 | import Wrapper from './Wrapper.svelte'; 3 | 4 | const preview: Preview = { 5 | parameters: { 6 | actions: { argTypesRegex: '^on[A-Z].*' }, 7 | controls: { 8 | matchers: { 9 | color: /(background|color)$/i, 10 | date: /Date$/ 11 | } 12 | } 13 | }, 14 | // @ts-ignore 15 | decorators: [() => Wrapper] 16 | }; 17 | 18 | export default preview; 19 | -------------------------------------------------------------------------------- /.typesafe-i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "adapter": "svelte", 3 | "esmImports": true, 4 | "$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json" 5 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | 3 | WORKDIR /app 4 | 5 | COPY /.npmrc /app/ 6 | COPY /package*.json /app/ 7 | RUN npm ci 8 | 9 | COPY /static /app/static 10 | COPY postcss.config.cjs /app/ 11 | COPY svelte.config.js /app/ 12 | COPY vite.config.js /app/ 13 | COPY /.storybook /app/.storybook 14 | COPY tsconfig.json /app/ 15 | 16 | COPY /.env.production /app/.env 17 | 18 | COPY /prisma /app/prisma 19 | RUN npx -y prisma generate 20 | 21 | COPY /src /app/src 22 | RUN npm run build 23 | 24 | CMD [ "node", "/app/build/index.js" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Luca Goslar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sveltekit-fullstack 2 | 3 | Everything you need to build a Svelte project with [Storybook](https://storybook.js.org/), [typesafe-i18n](https://github.com/ivanhofer/typesafe-i18n), [Prisma](https://prisma.io/) and [trpc](https://trpc.io/). 4 | 5 | ## Developing 6 | 7 | Make sure to create a copy of `.env.example` with the name `.env` and adapt it to your requirements before running the application. 8 | 9 | ```bash 10 | # install dependencies 11 | npm i 12 | 13 | # apply db migrations to db 14 | npx prisma migrate dev 15 | 16 | # seed the database (flags '--no-flush' and '--no-seed' available) 17 | npm run seed -- 18 | 19 | # run storybook 20 | npm run storybook 21 | 22 | # or run the development server 23 | npm run dev 24 | ``` 25 | 26 | ## Building 27 | 28 | You may build for any target wanted. However, this project is preconfigured to operate on Docker. Similar to before, create a copy of `.env.example`. However, name it `.env.production` this time. Take into consideration that your application will use port `3000` in production. Before starting the service, apply any pending migrations with `prisma migrate deploy` to your database. 29 | 30 | ```bash 31 | # build and run the image 32 | docker-compose up --build 33 | ``` 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: . 6 | ports: 7 | - '3000:3000' 8 | env_file: 9 | - .env.production 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit-fullstack", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"typesafe-i18n\" \"vite dev\"", 7 | "build": "typesafe-i18n --no-watch && vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 12 | "format": "prettier --plugin-search-dir . --write .", 13 | "typesafe-i18n": "typesafe-i18n", 14 | "storybook": "storybook dev -p 6006", 15 | "build-storybook": "storybook build", 16 | "seed": "ts-node-esm prisma/seed.ts" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.23.9", 20 | "@poppanator/sveltekit-svg": "^4.2.1", 21 | "@storybook/addon-actions": "^7.6.12", 22 | "@storybook/addon-essentials": "^7.6.12", 23 | "@storybook/addon-interactions": "^7.6.12", 24 | "@storybook/addon-links": "^7.6.12", 25 | "@storybook/addon-svelte-csf": "^4.1.0", 26 | "@storybook/blocks": "^7.6.12", 27 | "@storybook/builder-vite": "^7.6.12", 28 | "@storybook/cli": "^7.6.12", 29 | "@storybook/svelte": "^7.6.12", 30 | "@storybook/sveltekit": "^7.6.12", 31 | "@storybook/testing-library": "^0.2.2", 32 | "@sveltejs/adapter-auto": "^3.1.1", 33 | "@sveltejs/adapter-node": "^4.0.1", 34 | "@sveltejs/enhanced-img": "^0.1.8", 35 | "@sveltejs/kit": "^2.5.0", 36 | "@sveltejs/vite-plugin-svelte": "^3.0.2", 37 | "@types/accept-language-parser": "^1.5.6", 38 | "@typescript-eslint/eslint-plugin": "^6.20.0", 39 | "@typescript-eslint/parser": "^6.20.0", 40 | "autoprefixer": "^10.4.17", 41 | "babel-loader": "^9.1.3", 42 | "concurrently": "^8.2.2", 43 | "dotenv": "^16.4.1", 44 | "eslint": "^8.56.0", 45 | "eslint-config-prettier": "^9.1.0", 46 | "eslint-plugin-storybook": "^0.6.15", 47 | "eslint-plugin-svelte": "^2.35.1", 48 | "imagetools-core": "^6.0.4", 49 | "postcss-pxtorem": "^6.1.0", 50 | "prettier": "^3.2.4", 51 | "prettier-plugin-svelte": "^3.1.2", 52 | "prisma": "^5.9.0", 53 | "react": "^18.2.0", 54 | "react-dom": "^18.2.0", 55 | "sass": "^1.70.0", 56 | "storybook": "^7.6.12", 57 | "svelte": "^4.2.9", 58 | "svelte-check": "^3.6.3", 59 | "svelte-loader": "^3.1.9", 60 | "svelte-preprocess-sass-alias-import": "^1.0.0", 61 | "svgo": "^3.2.0", 62 | "ts-node": "^10.9.2", 63 | "tslib": "^2.6.2", 64 | "typescript": "^5.3.3", 65 | "vite": "^5.0.12" 66 | }, 67 | "type": "module", 68 | "dependencies": { 69 | "@prisma/client": "^5.9.0", 70 | "@trpc/client": "^10.45.0", 71 | "@trpc/server": "^10.45.0", 72 | "accept-language-parser": "^1.5.0", 73 | "normalize.css": "^8.0.1", 74 | "svelte-meta-tags": "^3.1.0", 75 | "trpc-sveltekit": "^3.5.26", 76 | "typesafe-i18n": "^5.26.2", 77 | "zod": "^3.22.4" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | const pxtorem = require('postcss-pxtorem'); 3 | 4 | const config = { 5 | plugins: [ 6 | pxtorem({ 7 | rootValue: 16, 8 | unitPrecision: 5, 9 | propList: ['*'], 10 | replace: true, 11 | mediaQuery: true, 12 | minPixelValue: 0, 13 | exclude: /node_modules/i 14 | }), 15 | autoprefixer 16 | ] 17 | }; 18 | 19 | module.exports = config; 20 | -------------------------------------------------------------------------------- /prisma/migrations/20221220114054_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "email" TEXT NOT NULL, 5 | "name" TEXT 6 | ); 7 | 8 | -- CreateIndex 9 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 10 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "sqlite" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model User { 14 | id Int @id @default(autoincrement()) 15 | email String @unique 16 | name String? 17 | } -------------------------------------------------------------------------------- /prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { PrismaClient } from '@prisma/client'; 3 | 4 | dotenv.config(); 5 | 6 | const prisma = new PrismaClient({ 7 | datasources: { 8 | db: { 9 | url: process.env.DATABASE_URL 10 | } 11 | } 12 | }); 13 | 14 | async function seed() { 15 | await prisma.$transaction([prisma.user.create({ data: { email: 'mail@example.com' } })]); 16 | console.log('Database seeded.'); 17 | } 18 | 19 | async function flush() { 20 | await prisma.$transaction([prisma.user.deleteMany()]); 21 | 22 | console.log('Database flushed.'); 23 | } 24 | 25 | if (!process.argv.includes('--no-flush')) { 26 | await flush(); 27 | } 28 | 29 | if (!process.argv.includes('--no-seed')) { 30 | await seed(); 31 | } 32 | 33 | console.log('All set.'); 34 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | // and what to do when importing types 4 | declare namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/bigben.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucagoslar/sveltekit-fullstack/ad2e43a8917414d4800158b959e32b808522b649/src/assets/bigben.avif -------------------------------------------------------------------------------- /src/helpers/scripts/trpc.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCClient, type TRPCClientInit } from 'trpc-sveltekit'; 2 | import type { AppRouter } from '$lib/server/trpc/_app'; 3 | 4 | let browserClient: ReturnType>; 5 | 6 | const slug = '/trpc'; 7 | 8 | export const client = (init?: TRPCClientInit) => { 9 | if (typeof window === 'undefined') { 10 | return createTRPCClient({ 11 | url: slug, 12 | init 13 | }); 14 | } 15 | 16 | if (!browserClient) 17 | browserClient = createTRPCClient({ 18 | url: slug 19 | }); 20 | 21 | return browserClient; 22 | }; 23 | -------------------------------------------------------------------------------- /src/helpers/styles/a11y.scss: -------------------------------------------------------------------------------- 1 | @mixin outline($color: #000, $offset: 0) { 2 | &:focus-visible { 3 | outline: rgba($color, 0.3) solid 3px; 4 | } 5 | 6 | &:focus-visible:active { 7 | outline: rgba($color, 0.5) solid 3px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/helpers/styles/main.scss: -------------------------------------------------------------------------------- 1 | @use '$styles/variables.scss'; 2 | 3 | * { 4 | font-family: 5 | system-ui, 6 | -apple-system, 7 | BlinkMacSystemFont, 8 | 'Segoe UI', 9 | Roboto, 10 | Oxygen, 11 | Ubuntu, 12 | Cantarell, 13 | 'Open Sans', 14 | 'Helvetica Neue', 15 | sans-serif; 16 | font-size: 16px; 17 | 18 | box-sizing: border-box; 19 | } 20 | -------------------------------------------------------------------------------- /src/helpers/styles/variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --placeholder: black; 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { sequence } from '@sveltejs/kit/hooks'; 2 | import type { Handle } from '@sveltejs/kit'; 3 | 4 | import { isLocale } from '$i18n/i18n-util'; 5 | 6 | const language: Handle = async ({ event, resolve }) => { 7 | let [, lang] = event.url.pathname.split('/'); 8 | 9 | if (isLocale(lang)) { 10 | // Set lang attribute 11 | return await resolve(event, { 12 | transformPageChunk: ({ html }) => html.replace('%lang%', lang) 13 | }); 14 | } 15 | 16 | return await resolve(event); 17 | }; 18 | 19 | export const handle: Handle = sequence(language); 20 | -------------------------------------------------------------------------------- /src/i18n/de/index.ts: -------------------------------------------------------------------------------- 1 | import type { Translation } from '../i18n-types.js'; 2 | 3 | const de: Translation = { 4 | // this is an example Translation, just rename or delete this folder if you want 5 | HI: 'Hallo {name}! Bitte hinterlasse einen Stern, wenn dir das Projekt gefällt: https://github.com/ivanhofer/typesafe-i18n', 6 | story: { 7 | button: '{times} mal geklickt' 8 | } 9 | }; 10 | 11 | export default de; 12 | -------------------------------------------------------------------------------- /src/i18n/en/index.ts: -------------------------------------------------------------------------------- 1 | import type { BaseTranslation } from '../i18n-types.js'; 2 | 3 | const en: BaseTranslation = { 4 | // TODO: your translations go here 5 | HI: 'Hi {name:string}! Please leave a star if you like this project: https://github.com/ivanhofer/typesafe-i18n', 6 | story: { 7 | button: 'Clicked {times:number} times' 8 | } 9 | }; 10 | 11 | export default en; 12 | -------------------------------------------------------------------------------- /src/i18n/formatters.ts: -------------------------------------------------------------------------------- 1 | import type { FormattersInitializer } from 'typesafe-i18n' 2 | import type { Locales, Formatters } from './i18n-types.js' 3 | 4 | export const initFormatters: FormattersInitializer = (locale: Locales) => { 5 | 6 | const formatters: Formatters = { 7 | // add your formatter functions here 8 | } 9 | 10 | return formatters 11 | } 12 | -------------------------------------------------------------------------------- /src/i18n/i18n-svelte.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { initI18nSvelte } from 'typesafe-i18n/svelte' 5 | import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types.js' 6 | import { loadedFormatters, loadedLocales } from './i18n-util.js' 7 | 8 | const { locale, LL, setLocale } = initI18nSvelte(loadedLocales, loadedFormatters) 9 | 10 | export { locale, LL, setLocale } 11 | 12 | export default LL 13 | -------------------------------------------------------------------------------- /src/i18n/i18n-types.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' 4 | 5 | export type BaseTranslation = BaseTranslationType 6 | export type BaseLocale = 'en' 7 | 8 | export type Locales = 9 | | 'de' 10 | | 'en' 11 | 12 | export type Translation = RootTranslation 13 | 14 | export type Translations = RootTranslation 15 | 16 | type RootTranslation = { 17 | /** 18 | * H​i​ ​{​n​a​m​e​}​!​ ​P​l​e​a​s​e​ ​l​e​a​v​e​ ​a​ ​s​t​a​r​ ​i​f​ ​y​o​u​ ​l​i​k​e​ ​t​h​i​s​ ​p​r​o​j​e​c​t​:​ ​h​t​t​p​s​:​/​/​g​i​t​h​u​b​.​c​o​m​/​i​v​a​n​h​o​f​e​r​/​t​y​p​e​s​a​f​e​-​i​1​8​n 19 | * @param {string} name 20 | */ 21 | HI: RequiredParams<'name'> 22 | story: { 23 | /** 24 | * C​l​i​c​k​e​d​ ​{​t​i​m​e​s​}​ ​t​i​m​e​s 25 | * @param {number} times 26 | */ 27 | button: RequiredParams<'times'> 28 | } 29 | } 30 | 31 | export type TranslationFunctions = { 32 | /** 33 | * Hi {name}! Please leave a star if you like this project: https://github.com/ivanhofer/typesafe-i18n 34 | */ 35 | HI: (arg: { name: string }) => LocalizedString 36 | story: { 37 | /** 38 | * Clicked {times} times 39 | */ 40 | button: (arg: { times: number }) => LocalizedString 41 | } 42 | } 43 | 44 | export type Formatters = {} 45 | -------------------------------------------------------------------------------- /src/i18n/i18n-util.async.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { initFormatters } from './formatters.js' 5 | import type { Locales, Translations } from './i18n-types.js' 6 | import { loadedFormatters, loadedLocales, locales } from './i18n-util.js' 7 | 8 | const localeTranslationLoaders = { 9 | de: () => import('./de/index.js'), 10 | en: () => import('./en/index.js'), 11 | } 12 | 13 | const updateDictionary = (locale: Locales, dictionary: Partial): Translations => 14 | loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } 15 | 16 | export const importLocaleAsync = async (locale: Locales): Promise => 17 | (await localeTranslationLoaders[locale]()).default as unknown as Translations 18 | 19 | export const loadLocaleAsync = async (locale: Locales): Promise => { 20 | updateDictionary(locale, await importLocaleAsync(locale)) 21 | loadFormatters(locale) 22 | } 23 | 24 | export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) 25 | 26 | export const loadFormatters = (locale: Locales): void => 27 | void (loadedFormatters[locale] = initFormatters(locale)) 28 | -------------------------------------------------------------------------------- /src/i18n/i18n-util.sync.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { initFormatters } from './formatters.js' 5 | import type { Locales, Translations } from './i18n-types.js' 6 | import { loadedFormatters, loadedLocales, locales } from './i18n-util.js' 7 | 8 | import de from './de/index.js' 9 | import en from './en/index.js' 10 | 11 | const localeTranslations = { 12 | de, 13 | en, 14 | } 15 | 16 | export const loadLocale = (locale: Locales): void => { 17 | if (loadedLocales[locale]) return 18 | 19 | loadedLocales[locale] = localeTranslations[locale] as unknown as Translations 20 | loadFormatters(locale) 21 | } 22 | 23 | export const loadAllLocales = (): void => locales.forEach(loadLocale) 24 | 25 | export const loadFormatters = (locale: Locales): void => 26 | void (loadedFormatters[locale] = initFormatters(locale)) 27 | -------------------------------------------------------------------------------- /src/i18n/i18n-util.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' 5 | import type { LocaleDetector } from 'typesafe-i18n/detectors' 6 | import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' 7 | import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' 8 | import { initExtendDictionary } from 'typesafe-i18n/utils' 9 | import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types.js' 10 | 11 | export const baseLocale: Locales = 'en' 12 | 13 | export const locales: Locales[] = [ 14 | 'de', 15 | 'en' 16 | ] 17 | 18 | export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) 19 | 20 | export const loadedLocales: Record = {} as Record 21 | 22 | export const loadedFormatters: Record = {} as Record 23 | 24 | export const extendDictionary = initExtendDictionary() 25 | 26 | export const i18nString = (locale: Locales): TranslateByString => initI18nString(locale, loadedFormatters[locale]) 27 | 28 | export const i18nObject = (locale: Locales): TranslationFunctions => 29 | initI18nObject( 30 | locale, 31 | loadedLocales[locale], 32 | loadedFormatters[locale] 33 | ) 34 | 35 | export const i18n = (): LocaleTranslationFunctions => 36 | initI18n(loadedLocales, loadedFormatters) 37 | 38 | export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn(baseLocale, locales, ...detectors) 39 | -------------------------------------------------------------------------------- /src/lib/components/_reusables/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucagoslar/sveltekit-fullstack/ad2e43a8917414d4800158b959e32b808522b649/src/lib/components/_reusables/.gitkeep -------------------------------------------------------------------------------- /src/lib/components/atoms/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucagoslar/sveltekit-fullstack/ad2e43a8917414d4800158b959e32b808522b649/src/lib/components/atoms/.gitkeep -------------------------------------------------------------------------------- /src/lib/components/atoms/Button/Button.stories.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/lib/components/atoms/Button/Button.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | -------------------------------------------------------------------------------- /src/lib/components/atoms/Logo/Logo.stories.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/atoms/Logo/Logo.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/lib/components/atoms/Logo/svelte.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 |