create-mikstack 0.1.24 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/base/AGENTS.md +46 -3
- package/templates/base/src/lib/server/auth.ts +5 -0
- package/templates/base/src/lib/zero/mutators.ts +7 -2
- package/templates/i18n/lingui.config.ts +4 -0
- package/templates/i18n/src/lib/LocaleSwitcher.svelte +1 -0
- package/templates/i18n/src/lib/i18n.ts +14 -1
- package/templates/i18n/src/lib/server/emails/magic-link.ts +67 -0
- package/templates/i18n/src/lib/server/notifications/definitions.ts +12 -0
- package/templates/i18n/src/locales/en.po +14 -25
- package/templates/i18n/src/locales/fi.po +14 -25
package/package.json
CHANGED
package/templates/base/AGENTS.md
CHANGED
|
@@ -8,10 +8,14 @@ SvelteKit app with Drizzle ORM, Zero (real-time sync), and better-auth.
|
|
|
8
8
|
- **Database**: PostgreSQL via Drizzle ORM
|
|
9
9
|
- **Real-time**: Zero (@rocicorp/zero) for client-side sync
|
|
10
10
|
- **Auth**: better-auth with magic link
|
|
11
|
-
- **
|
|
11
|
+
- **UI**: @mikstack/ui components (Button, Input, FormField, Textarea, Separator)
|
|
12
|
+
- **Styling**: Design tokens in `src/app.css` — oklch colors, spacing/radius variables, dark mode via `prefers-color-scheme`
|
|
12
13
|
<!-- {{#if:i18n}} -->
|
|
13
14
|
- **i18n**: @mikstack/svelte-lingui (Lingui-based)
|
|
14
15
|
<!-- {{/if:i18n}} -->
|
|
16
|
+
<!-- {{#if:testing}} -->
|
|
17
|
+
- **Testing**: Vitest + @testcontainers/postgresql for integration tests
|
|
18
|
+
<!-- {{/if:testing}} -->
|
|
15
19
|
|
|
16
20
|
## Key Patterns
|
|
17
21
|
|
|
@@ -28,11 +32,19 @@ const data = $derived(query.data);
|
|
|
28
32
|
await z.mutate(mutators.myMutation(args));
|
|
29
33
|
```
|
|
30
34
|
|
|
35
|
+
Mutators verify ownership before update/delete via `tx.run()`:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const entity = await tx.run(zql.note.where("id", args.id).one());
|
|
39
|
+
if (!entity || entity.userId !== ctx.userID) return;
|
|
40
|
+
```
|
|
41
|
+
|
|
31
42
|
### Auth
|
|
32
43
|
|
|
33
44
|
Server: `src/lib/server/auth.ts` (better-auth config)
|
|
34
45
|
Client: `src/lib/auth-client.ts` (magic link + client helpers)
|
|
35
46
|
Session is available in `locals.user` and `locals.session` via `src/hooks.server.ts`.
|
|
47
|
+
Routes under `(app)/` require authentication — unauthenticated requests redirect to `/sign-in`.
|
|
36
48
|
|
|
37
49
|
### Forms (@mikstack/form)
|
|
38
50
|
|
|
@@ -85,9 +97,23 @@ Email delivery tracking and retries are handled automatically.
|
|
|
85
97
|
|
|
86
98
|
### Database
|
|
87
99
|
|
|
88
|
-
Schema: `src/lib/server/db/schema.ts`
|
|
100
|
+
Schema: `src/lib/server/db/schema.ts` (uses `casing: "snake_case"` — no explicit column names needed)
|
|
89
101
|
Connection: `src/lib/server/db/index.ts` (lazy-initialized via Proxy)
|
|
90
102
|
|
|
103
|
+
<!-- {{#if:testing}} -->
|
|
104
|
+
### Testing
|
|
105
|
+
|
|
106
|
+
Test utils: `src/lib/server/db/test-utils.ts` — `createTestDatabase()` spins up a PostgreSQL container via @testcontainers/postgresql.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { createTestDatabase, stopTestDatabase, type TestDatabase } from "$lib/server/db/test-utils";
|
|
110
|
+
|
|
111
|
+
let testDb: TestDatabase;
|
|
112
|
+
beforeAll(async () => { testDb = await createTestDatabase(); });
|
|
113
|
+
afterAll(async () => { await stopTestDatabase(testDb); });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
<!-- {{/if:testing}} -->
|
|
91
117
|
<!-- {{#if:i18n}} -->
|
|
92
118
|
### i18n
|
|
93
119
|
|
|
@@ -95,7 +121,7 @@ Setup: `src/lib/i18n.ts` — initialized in root layout
|
|
|
95
121
|
Config: `lingui.config.ts`
|
|
96
122
|
Catalogs: `src/locales/{locale}.po`
|
|
97
123
|
|
|
98
|
-
Use `useLingui()` for translations
|
|
124
|
+
Use `useLingui()` for translations in Svelte components:
|
|
99
125
|
|
|
100
126
|
```svelte
|
|
101
127
|
<script lang="ts">
|
|
@@ -106,9 +132,23 @@ Use `useLingui()` for translations, `<T>` component for rich text:
|
|
|
106
132
|
<h1>{t`Hello world`}</h1>
|
|
107
133
|
```
|
|
108
134
|
|
|
135
|
+
Use `<T>` component for rich text with embedded elements:
|
|
136
|
+
|
|
137
|
+
```svelte
|
|
138
|
+
<T>Read the <a href="/docs">documentation</a></T>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The extractor and Svelte preprocessor handle the transformation automatically.
|
|
142
|
+
|
|
109
143
|
Run `{{pmRun}} i18n:extract` to extract messages into `.po` catalogs.
|
|
110
144
|
<!-- {{/if:i18n}} -->
|
|
111
145
|
|
|
146
|
+
### Deployment
|
|
147
|
+
|
|
148
|
+
- `Dockerfile` — multi-stage build for the Node adapter
|
|
149
|
+
- `docker-compose.yml` — local dev (PostgreSQL + zero-cache)
|
|
150
|
+
- `docker-compose.prod.yml` — production (PostgreSQL + zero-cache + app)
|
|
151
|
+
|
|
112
152
|
## Commands
|
|
113
153
|
|
|
114
154
|
- `{{pmRun}} dev` — start dev server
|
|
@@ -121,3 +161,6 @@ Run `{{pmRun}} i18n:extract` to extract messages into `.po` catalogs.
|
|
|
121
161
|
<!-- {{#if:i18n}} -->
|
|
122
162
|
- `{{pmRun}} i18n:extract` — extract messages to .po catalogs
|
|
123
163
|
<!-- {{/if:i18n}} -->
|
|
164
|
+
<!-- {{#if:testing}} -->
|
|
165
|
+
- `{{pmRun}} test` — run tests (requires Docker for testcontainers)
|
|
166
|
+
<!-- {{/if:testing}} -->
|
|
@@ -24,7 +24,12 @@ function createAuth() {
|
|
|
24
24
|
await notif.send({
|
|
25
25
|
type: "magic-link",
|
|
26
26
|
recipientEmail: email,
|
|
27
|
+
// {{#if:i18n}}
|
|
28
|
+
data: { url, locale: getRequestEvent().cookies.get("locale") },
|
|
29
|
+
// {{/if:i18n}}
|
|
30
|
+
// {{#if:!i18n}}
|
|
27
31
|
data: { url },
|
|
32
|
+
// {{/if:!i18n}}
|
|
28
33
|
});
|
|
29
34
|
},
|
|
30
35
|
}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineMutators, defineMutator } from "@rocicorp/zero";
|
|
2
2
|
import * as v from "valibot";
|
|
3
|
+
import { zql } from "./schema";
|
|
3
4
|
|
|
4
5
|
export const mutators = defineMutators({
|
|
5
6
|
note: {
|
|
@@ -19,7 +20,9 @@ export const mutators = defineMutators({
|
|
|
19
20
|
),
|
|
20
21
|
update: defineMutator(
|
|
21
22
|
v.object({ id: v.string(), title: v.string(), content: v.string() }),
|
|
22
|
-
async ({ tx, args }) => {
|
|
23
|
+
async ({ tx, ctx, args }) => {
|
|
24
|
+
const note = await tx.run(zql.note.where("id", args.id).one());
|
|
25
|
+
if (!note || note.userId !== ctx.userID) return;
|
|
23
26
|
await tx.mutate.note.update({
|
|
24
27
|
id: args.id,
|
|
25
28
|
title: args.title,
|
|
@@ -28,7 +31,9 @@ export const mutators = defineMutators({
|
|
|
28
31
|
});
|
|
29
32
|
},
|
|
30
33
|
),
|
|
31
|
-
delete: defineMutator(v.object({ id: v.string() }), async ({ tx, args }) => {
|
|
34
|
+
delete: defineMutator(v.object({ id: v.string() }), async ({ tx, ctx, args }) => {
|
|
35
|
+
const note = await tx.run(zql.note.where("id", args.id).one());
|
|
36
|
+
if (!note || note.userId !== ctx.userID) return;
|
|
32
37
|
await tx.mutate.note.delete({ id: args.id });
|
|
33
38
|
}),
|
|
34
39
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { setupI18n } from "@lingui/core";
|
|
1
|
+
import { setupI18n, type I18n } from "@lingui/core";
|
|
2
2
|
import { setI18n } from "@mikstack/svelte-lingui";
|
|
3
3
|
import { messages as enMessages } from "../locales/en.po";
|
|
4
4
|
import { messages as fiMessages } from "../locales/fi.po";
|
|
@@ -27,3 +27,16 @@ export function getLocale(): string {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export const locales = Object.keys(allMessages);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a standalone i18n instance for server-side use (e.g. emails).
|
|
33
|
+
* Each call returns a fresh instance — safe for concurrent requests.
|
|
34
|
+
*/
|
|
35
|
+
export function createServerI18n(locale = "en"): I18n {
|
|
36
|
+
const serverI18n = setupI18n();
|
|
37
|
+
serverI18n.loadAndActivate({
|
|
38
|
+
locale,
|
|
39
|
+
messages: allMessages[locale] ?? allMessages["en"],
|
|
40
|
+
});
|
|
41
|
+
return serverI18n;
|
|
42
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { html, body, section, text, button, render } from "@mikstack/email";
|
|
2
|
+
import { msg } from "@mikstack/svelte-lingui";
|
|
3
|
+
import { createServerI18n } from "$lib/i18n";
|
|
4
|
+
|
|
5
|
+
export function magicLinkEmail(url: string, locale = "en") {
|
|
6
|
+
const i18n = createServerI18n(locale);
|
|
7
|
+
const _ = (d: { id: string; message: string }) => i18n._(d) as string;
|
|
8
|
+
|
|
9
|
+
const email = html(
|
|
10
|
+
body(
|
|
11
|
+
[
|
|
12
|
+
section(
|
|
13
|
+
[
|
|
14
|
+
text(_(msg`Sign in to {{projectName}}`), {
|
|
15
|
+
fontSize: 24,
|
|
16
|
+
fontWeight: "bold",
|
|
17
|
+
color: "#111827",
|
|
18
|
+
marginBottom: 40,
|
|
19
|
+
marginTop: 40,
|
|
20
|
+
}),
|
|
21
|
+
button(_(msg`Sign in`), {
|
|
22
|
+
href: url,
|
|
23
|
+
backgroundColor: "#111827",
|
|
24
|
+
color: "#ffffff",
|
|
25
|
+
fontSize: 14,
|
|
26
|
+
fontWeight: "bold",
|
|
27
|
+
padding: [12, 34],
|
|
28
|
+
borderRadius: 6,
|
|
29
|
+
marginBottom: 16,
|
|
30
|
+
}),
|
|
31
|
+
text(_(msg`Or, copy and paste this temporary login URL:`), {
|
|
32
|
+
fontSize: 14,
|
|
33
|
+
lineHeight: 1.7,
|
|
34
|
+
color: "#111827",
|
|
35
|
+
marginTop: 24,
|
|
36
|
+
marginBottom: 14,
|
|
37
|
+
}),
|
|
38
|
+
text(url, {
|
|
39
|
+
fontSize: 13,
|
|
40
|
+
color: "#111827",
|
|
41
|
+
backgroundColor: "#f4f4f4",
|
|
42
|
+
borderRadius: 6,
|
|
43
|
+
padding: [16, 24],
|
|
44
|
+
border: "1px solid #eee",
|
|
45
|
+
}),
|
|
46
|
+
text(_(msg`If you didn't try to sign in, you can safely ignore this email.`), {
|
|
47
|
+
fontSize: 14,
|
|
48
|
+
lineHeight: 1.7,
|
|
49
|
+
color: "#ababab",
|
|
50
|
+
marginTop: 14,
|
|
51
|
+
marginBottom: 38,
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
{ padding: [0, 24] },
|
|
55
|
+
),
|
|
56
|
+
],
|
|
57
|
+
{ maxWidth: 480, backgroundColor: "#ffffff" },
|
|
58
|
+
),
|
|
59
|
+
{ lang: locale },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
subject: _(msg`Sign in to {{projectName}}`),
|
|
64
|
+
html: render(email),
|
|
65
|
+
text: render(email, { plainText: true }),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineNotification } from "@mikstack/notifications";
|
|
2
|
+
import { magicLinkEmail } from "../emails/magic-link";
|
|
3
|
+
|
|
4
|
+
export const notifications = {
|
|
5
|
+
"magic-link": defineNotification({
|
|
6
|
+
key: "magic-link",
|
|
7
|
+
critical: true, // Auth emails bypass user preferences
|
|
8
|
+
channels: {
|
|
9
|
+
email: (data: { url: string; locale?: string }) => magicLinkEmail(data.url, data.locale),
|
|
10
|
+
},
|
|
11
|
+
}),
|
|
12
|
+
} as const;
|
|
@@ -1,98 +1,87 @@
|
|
|
1
1
|
msgid ""
|
|
2
2
|
msgstr ""
|
|
3
|
+
"Project-Id-Version: {{projectName}}\n"
|
|
3
4
|
"Language: en\n"
|
|
5
|
+
"Language-Team: English, United States\n"
|
|
6
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
4
7
|
"MIME-Version: 1.0\n"
|
|
5
8
|
"Content-Type: text/plain; charset=UTF-8\n"
|
|
6
9
|
"Content-Transfer-Encoding: 8bit\n"
|
|
7
10
|
|
|
8
|
-
#: src/routes/(app)/+page.svelte
|
|
9
11
|
msgid "Cancel"
|
|
10
12
|
msgstr "Cancel"
|
|
11
13
|
|
|
12
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
13
14
|
msgid "Check your email for a magic link to sign in."
|
|
14
15
|
msgstr "Check your email for a magic link to sign in."
|
|
15
16
|
|
|
16
|
-
#: src/routes/(app)/+page.svelte
|
|
17
17
|
msgid "Content"
|
|
18
18
|
msgstr "Content"
|
|
19
19
|
|
|
20
|
-
#: src/routes/(app)/+page.svelte
|
|
21
20
|
msgid "Create note"
|
|
22
21
|
msgstr "Create note"
|
|
23
22
|
|
|
24
|
-
#: src/routes/(app)/+page.svelte
|
|
25
23
|
msgid "Creating..."
|
|
26
24
|
msgstr "Creating..."
|
|
27
25
|
|
|
28
|
-
#: src/routes/(app)/+page.svelte
|
|
29
26
|
msgid "Delete"
|
|
30
27
|
msgstr "Delete"
|
|
31
28
|
|
|
32
|
-
#: src/routes/(app)/+page.svelte
|
|
33
29
|
msgid "Edit"
|
|
34
30
|
msgstr "Edit"
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
msgid "If you didn't try to sign in, you can safely ignore this email."
|
|
33
|
+
msgstr "If you didn't try to sign in, you can safely ignore this email."
|
|
34
|
+
|
|
37
35
|
msgid "Email"
|
|
38
36
|
msgstr "Email"
|
|
39
37
|
|
|
40
|
-
#: src/routes/(app)/+page.svelte
|
|
41
38
|
msgid "New note"
|
|
42
39
|
msgstr "New note"
|
|
43
40
|
|
|
44
|
-
#: src/routes/(app)/+page.svelte
|
|
45
41
|
msgid "No notes yet. Create one above!"
|
|
46
42
|
msgstr "No notes yet. Create one above!"
|
|
47
43
|
|
|
48
|
-
#: src/routes/(app)/+page.svelte
|
|
49
44
|
msgid "Note title"
|
|
50
45
|
msgstr "Note title"
|
|
51
46
|
|
|
52
|
-
#: src/routes/(app)/+page.svelte
|
|
53
47
|
msgid "Notes"
|
|
54
48
|
msgstr "Notes"
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
msgid "Or, copy and paste this temporary login URL:"
|
|
51
|
+
msgstr "Or, copy and paste this temporary login URL:"
|
|
52
|
+
|
|
57
53
|
msgid "Please enter a valid email address"
|
|
58
54
|
msgstr "Please enter a valid email address"
|
|
59
55
|
|
|
60
|
-
#: src/routes/(app)/+page.svelte
|
|
61
56
|
msgid "Save"
|
|
62
57
|
msgstr "Save"
|
|
63
58
|
|
|
64
|
-
#: src/routes/(app)/+page.svelte
|
|
65
59
|
msgid "Saving..."
|
|
66
60
|
msgstr "Saving..."
|
|
67
61
|
|
|
68
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
69
62
|
msgid "Sending magic link..."
|
|
70
63
|
msgstr "Sending magic link..."
|
|
71
64
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
msgstr "Sign out"
|
|
65
|
+
msgid "Sign in"
|
|
66
|
+
msgstr "Sign in"
|
|
75
67
|
|
|
76
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
77
68
|
msgid "Sign in to {{projectName}}"
|
|
78
69
|
msgstr "Sign in to {{projectName}}"
|
|
79
70
|
|
|
80
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
81
71
|
msgid "Sign in with magic link"
|
|
82
72
|
msgstr "Sign in with magic link"
|
|
83
73
|
|
|
84
|
-
|
|
74
|
+
msgid "Sign out"
|
|
75
|
+
msgstr "Sign out"
|
|
76
|
+
|
|
85
77
|
msgid "Title"
|
|
86
78
|
msgstr "Title"
|
|
87
79
|
|
|
88
|
-
#: src/routes/(app)/+page.svelte
|
|
89
80
|
msgid "Title is required"
|
|
90
81
|
msgstr "Title is required"
|
|
91
82
|
|
|
92
|
-
#: src/routes/(app)/+page.svelte
|
|
93
83
|
msgid "Write something..."
|
|
94
84
|
msgstr "Write something..."
|
|
95
85
|
|
|
96
|
-
#: src/routes/(app)/+page.svelte
|
|
97
86
|
msgid "Your notes"
|
|
98
87
|
msgstr "Your notes"
|
|
@@ -1,98 +1,87 @@
|
|
|
1
1
|
msgid ""
|
|
2
2
|
msgstr ""
|
|
3
|
+
"Project-Id-Version: {{projectName}}\n"
|
|
3
4
|
"Language: fi\n"
|
|
5
|
+
"Language-Team: Finnish\n"
|
|
6
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
4
7
|
"MIME-Version: 1.0\n"
|
|
5
8
|
"Content-Type: text/plain; charset=UTF-8\n"
|
|
6
9
|
"Content-Transfer-Encoding: 8bit\n"
|
|
7
10
|
|
|
8
|
-
#: src/routes/(app)/+page.svelte
|
|
9
11
|
msgid "Cancel"
|
|
10
12
|
msgstr "Peruuta"
|
|
11
13
|
|
|
12
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
13
14
|
msgid "Check your email for a magic link to sign in."
|
|
14
15
|
msgstr "Tarkista sähköpostisi kirjautumislinkin varalta."
|
|
15
16
|
|
|
16
|
-
#: src/routes/(app)/+page.svelte
|
|
17
17
|
msgid "Content"
|
|
18
18
|
msgstr "Sisältö"
|
|
19
19
|
|
|
20
|
-
#: src/routes/(app)/+page.svelte
|
|
21
20
|
msgid "Create note"
|
|
22
21
|
msgstr "Luo muistiinpano"
|
|
23
22
|
|
|
24
|
-
#: src/routes/(app)/+page.svelte
|
|
25
23
|
msgid "Creating..."
|
|
26
24
|
msgstr "Luodaan..."
|
|
27
25
|
|
|
28
|
-
#: src/routes/(app)/+page.svelte
|
|
29
26
|
msgid "Delete"
|
|
30
27
|
msgstr "Poista"
|
|
31
28
|
|
|
32
|
-
#: src/routes/(app)/+page.svelte
|
|
33
29
|
msgid "Edit"
|
|
34
30
|
msgstr "Muokkaa"
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
msgid "If you didn't try to sign in, you can safely ignore this email."
|
|
33
|
+
msgstr "Jos et yrittänyt kirjautua, voit jättää tämän sähköpostin huomiotta."
|
|
34
|
+
|
|
37
35
|
msgid "Email"
|
|
38
36
|
msgstr "Sähköposti"
|
|
39
37
|
|
|
40
|
-
#: src/routes/(app)/+page.svelte
|
|
41
38
|
msgid "New note"
|
|
42
39
|
msgstr "Uusi muistiinpano"
|
|
43
40
|
|
|
44
|
-
#: src/routes/(app)/+page.svelte
|
|
45
41
|
msgid "No notes yet. Create one above!"
|
|
46
42
|
msgstr "Ei vielä muistiinpanoja. Luo ensimmäinen yllä!"
|
|
47
43
|
|
|
48
|
-
#: src/routes/(app)/+page.svelte
|
|
49
44
|
msgid "Note title"
|
|
50
45
|
msgstr "Muistiinpanon otsikko"
|
|
51
46
|
|
|
52
|
-
#: src/routes/(app)/+page.svelte
|
|
53
47
|
msgid "Notes"
|
|
54
48
|
msgstr "Muistiinpanot"
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
msgid "Or, copy and paste this temporary login URL:"
|
|
51
|
+
msgstr "Tai kopioi ja liitä tämä väliaikainen kirjautumislinkki:"
|
|
52
|
+
|
|
57
53
|
msgid "Please enter a valid email address"
|
|
58
54
|
msgstr "Syötä kelvollinen sähköpostiosoite"
|
|
59
55
|
|
|
60
|
-
#: src/routes/(app)/+page.svelte
|
|
61
56
|
msgid "Save"
|
|
62
57
|
msgstr "Tallenna"
|
|
63
58
|
|
|
64
|
-
#: src/routes/(app)/+page.svelte
|
|
65
59
|
msgid "Saving..."
|
|
66
60
|
msgstr "Tallennetaan..."
|
|
67
61
|
|
|
68
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
69
62
|
msgid "Sending magic link..."
|
|
70
63
|
msgstr "Lähetetään kirjautumislinkkiä..."
|
|
71
64
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
msgstr "Kirjaudu ulos"
|
|
65
|
+
msgid "Sign in"
|
|
66
|
+
msgstr "Kirjaudu"
|
|
75
67
|
|
|
76
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
77
68
|
msgid "Sign in to {{projectName}}"
|
|
78
69
|
msgstr "Kirjaudu palveluun {{projectName}}"
|
|
79
70
|
|
|
80
|
-
#: src/routes/(public)/sign-in/+page.svelte
|
|
81
71
|
msgid "Sign in with magic link"
|
|
82
72
|
msgstr "Kirjaudu kirjautumislinkillä"
|
|
83
73
|
|
|
84
|
-
|
|
74
|
+
msgid "Sign out"
|
|
75
|
+
msgstr "Kirjaudu ulos"
|
|
76
|
+
|
|
85
77
|
msgid "Title"
|
|
86
78
|
msgstr "Otsikko"
|
|
87
79
|
|
|
88
|
-
#: src/routes/(app)/+page.svelte
|
|
89
80
|
msgid "Title is required"
|
|
90
81
|
msgstr "Otsikko on pakollinen"
|
|
91
82
|
|
|
92
|
-
#: src/routes/(app)/+page.svelte
|
|
93
83
|
msgid "Write something..."
|
|
94
84
|
msgstr "Kirjoita jotain..."
|
|
95
85
|
|
|
96
|
-
#: src/routes/(app)/+page.svelte
|
|
97
86
|
msgid "Your notes"
|
|
98
87
|
msgstr "Muistiinpanosi"
|