@plutocms/supabase 0.9.1 → 0.10.0

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.
Files changed (35) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +8 -0
  3. package/FEATURES.md +6 -0
  4. package/app/components/EnvPersistWarning.vue +30 -13
  5. package/app/components/SupabaseMessageParts.vue +29 -0
  6. package/app/components/migrations/MigrationsBanner.vue +3 -4
  7. package/app/components/navbar/NavbarAdmin.vue +15 -9
  8. package/app/components/settings/SettingsGeneralPanel.vue +21 -6
  9. package/app/composables/auth.ts +17 -14
  10. package/app/pages/admin/(auth)/confirm.vue +6 -5
  11. package/app/pages/admin/(auth)/forgot-password.vue +15 -11
  12. package/app/pages/admin/(auth)/login.vue +10 -8
  13. package/app/pages/admin/(auth)/signup.vue +30 -22
  14. package/app/pages/admin/(auth)/update-password.vue +15 -12
  15. package/app/pages/admin/migrations.vue +87 -51
  16. package/app/pages/admin/setup.vue +88 -74
  17. package/app/pages/admin.vue +3 -2
  18. package/app/plugins/pluto-extension.ts +24 -3
  19. package/app/plugins/pluto-locale-sync.client.ts +30 -0
  20. package/app/utils/auth-error.ts +53 -0
  21. package/db/migrations/005_profile_locale.sql +44 -0
  22. package/i18n/locales/en.json +209 -0
  23. package/i18n/locales/pt-BR.json +209 -0
  24. package/package.json +3 -3
  25. package/server/api/profile/locale.get.ts +39 -0
  26. package/server/api/profile/locale.post.ts +71 -0
  27. package/server/api/setup/create.post.ts +6 -4
  28. package/server/api/users/[id].get.ts +4 -2
  29. package/server/api/users/index.get.ts +4 -2
  30. package/server/utils/admin-guard.ts +10 -2
  31. package/server/utils/capability-guard.ts +18 -4
  32. package/server/utils/content-adapter.ts +6 -4
  33. package/server/utils/pending-migrations.ts +0 -0
  34. package/server/utils/validate.ts +6 -1
  35. package/shared/types/supabase.ts +7 -0
@@ -0,0 +1,2 @@
1
+ # Agent skill files keep their own formatting. Prettier must not rewrite them.
2
+ .agents/**/*.md
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.0](https://github.com/plutocms/supabase/compare/v0.9.1...v0.10.0) (2026-10-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * **i18n:** store each user's admin language ([#79](https://github.com/plutocms/supabase/issues/79)) ([2ca171a](https://github.com/plutocms/supabase/commit/2ca171a97acbc2de4537b82b6718acbcc2c1fbd5))
9
+ * **i18n:** translate the supabase admin pages ([#82](https://github.com/plutocms/supabase/issues/82)) ([bae1372](https://github.com/plutocms/supabase/commit/bae1372fdb89414fcb20ec74c428812f25077f6b))
10
+
3
11
  ## [0.9.1](https://github.com/plutocms/supabase/compare/v0.9.0...v0.9.1) (2026-10-18)
4
12
 
5
13
 
package/FEATURES.md CHANGED
@@ -13,3 +13,9 @@ policies shared by every project here.
13
13
  - Content adapter: a Supabase/PostgREST-backed `PlutoContentAdapter`, plugging any layer's
14
14
  declared content type into `@plutocms/pluto`'s generic content routes.
15
15
  @.claude/skills/content-adapter/SKILL.md
16
+ - Profile language: stores each user's admin language in `profiles.locale`, written through a
17
+ security-definer function, and plugs it into pluto's i18n as a locale driver.
18
+ @.claude/skills/profile-language/SKILL.md
19
+ - Admin translation: every admin page, component, toast, and server message of this layer is
20
+ translated (`en`, `pt-BR`) in the `supabase.*` namespace of pluto's i18n system.
21
+ @.claude/skills/admin-translation/SKILL.md
@@ -14,6 +14,7 @@ const props = defineProps<{
14
14
  }>()
15
15
 
16
16
  const toast = useToast()
17
+ const { t } = usePlutoI18n()
17
18
 
18
19
  const envLine = computed(() => `DATABASE_URL="${props.connectionString}"`)
19
20
 
@@ -22,15 +23,15 @@ async function copyEnvLine() {
22
23
  await navigator.clipboard.writeText(envLine.value)
23
24
 
24
25
  toast.add({
25
- title: 'Copied',
26
- description: 'The DATABASE_URL line is on your clipboard.',
26
+ title: t('supabase.envWarning.copied'),
27
+ description: t('supabase.envWarning.copiedDescription'),
27
28
  icon: 'lucide:check-circle',
28
29
  color: 'success',
29
30
  })
30
31
  } catch {
31
32
  toast.add({
32
- title: 'Could not copy',
33
- description: 'Select and copy the line by hand instead.',
33
+ title: t('supabase.envWarning.copyFailed'),
34
+ description: t('supabase.envWarning.copyFailedDescription'),
34
35
  icon: 'lucide:circle-x',
35
36
  color: 'error',
36
37
  })
@@ -40,24 +41,40 @@ async function copyEnvLine() {
40
41
 
41
42
  <template>
42
43
  <UAlert
44
+ :title="t('supabase.envWarning.title')"
43
45
  color="warning"
44
46
  variant="outline"
45
47
  icon="lucide:triangle-alert"
46
- title="Connection string not saved automatically"
47
48
  >
48
49
  <template #description>
49
50
  <div class="flex flex-col gap-y-3">
50
51
  <p>
51
- The database change already succeeded. Pluto could not write
52
- <code>DATABASE_URL</code> to your project's
53
- <code>.env</code> file, so it will ask again next time unless you
54
- add it by hand:
52
+ <SupabaseMessageParts :message="t('supabase.envWarning.intro')">
53
+ <template #variable>
54
+ <code>DATABASE_URL</code>
55
+ </template>
56
+ <template #file>
57
+ <code>.env</code>
58
+ </template>
59
+ </SupabaseMessageParts>
55
60
  </p>
56
61
 
57
62
  <ol class="list-inside list-decimal">
58
- <li>Open (or create) <code>.env</code> in your project's root folder.</li>
59
- <li>Add this line. Replace any existing <code>DATABASE_URL</code> line with it.</li>
60
- <li>Restart the server.</li>
63
+ <li>
64
+ <SupabaseMessageParts :message="t('supabase.envWarning.step1')">
65
+ <template #file>
66
+ <code>.env</code>
67
+ </template>
68
+ </SupabaseMessageParts>
69
+ </li>
70
+ <li>
71
+ <SupabaseMessageParts :message="t('supabase.envWarning.step2')">
72
+ <template #variable>
73
+ <code>DATABASE_URL</code>
74
+ </template>
75
+ </SupabaseMessageParts>
76
+ </li>
77
+ <li>{{ t('supabase.envWarning.step3') }}</li>
61
78
  </ol>
62
79
 
63
80
  <div class="flex items-center gap-x-2">
@@ -68,11 +85,11 @@ async function copyEnvLine() {
68
85
  </code>
69
86
 
70
87
  <UButton
88
+ :aria-label="t('supabase.envWarning.copyLine')"
71
89
  icon="lucide:copy"
72
90
  variant="soft"
73
91
  color="neutral"
74
92
  size="sm"
75
- aria-label="Copy the DATABASE_URL line"
76
93
  @click="copyEnvLine"
77
94
  />
78
95
  </div>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ // Renders one translated message that holds links or markup in the middle of
3
+ // a sentence. Each `{name}` in the message becomes the slot with that name.
4
+ // The word order stays in the translation, so each language can move the
5
+ // parts. Example: <SupabaseMessageParts :message="t('…')"><template #link>…
6
+ const props = defineProps<{
7
+ /** The translated message. Write each slot as `{name}`. */
8
+ message: string
9
+ }>()
10
+
11
+ const parts = computed(() =>
12
+ props.message
13
+ .split(/(\{\w+\})/)
14
+ .filter((part) => part !== '')
15
+ .map((part) => {
16
+ const slot = /^\{(\w+)\}$/.exec(part)?.[1]
17
+
18
+ return slot ? { slot } : { text: part }
19
+ })
20
+ )
21
+ </script>
22
+
23
+ <template>
24
+ <template v-for="(part, index) in parts" :key="index">
25
+ <slot v-if="part.slot" :name="part.slot" />
26
+
27
+ <template v-else>{{ part.text }}</template>
28
+ </template>
29
+ </template>
@@ -2,11 +2,10 @@
2
2
  // Renders nothing when there is no logged-in admin (status reads as null)
3
3
  // or when no layer is pending. See `useMigrations` for the 401/403 handling.
4
4
  const { status, pendingCount } = useMigrations()
5
+ const { t } = usePlutoI18n()
5
6
 
6
7
  const title = computed(() =>
7
- pendingCount.value === 1
8
- ? '1 layer needs a database migration'
9
- : `${pendingCount.value} layers need a database migration`
8
+ t('supabase.migrations.banner', pendingCount.value)
10
9
  )
11
10
  </script>
12
11
 
@@ -19,7 +18,7 @@ const title = computed(() =>
19
18
  icon="lucide:triangle-alert"
20
19
  >
21
20
  <template #description>
22
- <ULink to="/admin/migrations" class="underline"> Go to Migrations </ULink
21
+ <ULink to="/admin/migrations" class="underline"> {{ t('supabase.migrations.goTo') }} </ULink
23
22
  >.
24
23
  </template>
25
24
  </UAlert>
@@ -11,6 +11,8 @@ function openSidebar() {
11
11
  isSidebarOpen.value = true
12
12
  }
13
13
 
14
+ const { t } = usePlutoI18n()
15
+
14
16
  const { isLoggedIn, user, logout } = await useAuth()
15
17
 
16
18
  const displayName = computed(() => {
@@ -19,7 +21,7 @@ const displayName = computed(() => {
19
21
  user.value?.full_name ||
20
22
  user.value?.name ||
21
23
  user.value?.username ||
22
- 'User'
24
+ t('supabase.navbar.defaultUserName')
23
25
  )
24
26
  })
25
27
 
@@ -66,7 +68,7 @@ const items = computed<DropdownItems>(() => [
66
68
 
67
69
  [
68
70
  {
69
- label: 'Settings',
71
+ label: t('pluto.common.settings'),
70
72
  icon: 'i-lucide-cog',
71
73
  type: 'link',
72
74
  to: '/admin/settings',
@@ -81,7 +83,7 @@ const items = computed<DropdownItems>(() => [
81
83
 
82
84
  [
83
85
  {
84
- label: 'Pluto on GitHub',
86
+ label: t('supabase.navbar.github'),
85
87
  icon: 'i-simple-icons-github',
86
88
  type: 'link',
87
89
  to: 'https://github.com/ojvribeiro/pluto',
@@ -92,7 +94,7 @@ const items = computed<DropdownItems>(() => [
92
94
 
93
95
  [
94
96
  {
95
- label: 'Logout',
97
+ label: t('supabase.navbar.logout'),
96
98
  icon: 'i-lucide-log-out',
97
99
  kbds: ['meta', 'shift', 'q'],
98
100
  class: 'cursor-pointer',
@@ -113,18 +115,18 @@ defineShortcuts(extractShortcuts(items.value))
113
115
  >
114
116
  <div class="flex min-w-0 items-center gap-x-1 lg:gap-x-3">
115
117
  <UButton
118
+ :aria-label="t('supabase.navbar.openNavigation')"
116
119
  class="lg:hidden"
117
120
  icon="lucide:menu"
118
121
  color="neutral"
119
122
  variant="ghost"
120
- aria-label="Open navigation"
121
123
  square
122
124
  @click="openSidebar"
123
125
  />
124
126
 
125
127
  <div class="h-full shrink-0 py-1">
126
128
  <NuxtLink
127
- title="Go to dashboard home"
129
+ :title="t('supabase.navbar.goHome')"
128
130
  to="/admin/home"
129
131
  class="group block h-full"
130
132
  >
@@ -139,7 +141,11 @@ defineShortcuts(extractShortcuts(items.value))
139
141
  <ul class="flex h-full min-w-0 items-center text-sm">
140
142
  <NavbarAdminActionButton
141
143
  :show="route.path.startsWith('/admin')"
142
- :title="`Visit ${domainFromUrl(maybeDevUrl)}`"
144
+ :title="
145
+ t('supabase.navbar.visitSite', {
146
+ domain: domainFromUrl(maybeDevUrl),
147
+ })
148
+ "
143
149
  :icon="
144
150
  status === 'pending'
145
151
  ? 'lucide:loader-circle'
@@ -147,8 +153,8 @@ defineShortcuts(extractShortcuts(items.value))
147
153
  "
148
154
  :disabled="status === 'pending'"
149
155
  :to="maybeDevUrl"
156
+ :label="t('supabase.navbar.viewSite')"
150
157
  target="_blank"
151
- label="View site"
152
158
  />
153
159
 
154
160
  <ClientOnly>
@@ -180,8 +186,8 @@ defineShortcuts(extractShortcuts(items.value))
180
186
  :modal="false"
181
187
  >
182
188
  <button
189
+ :aria-label="t('supabase.navbar.openUserMenu')"
183
190
  class="group h-full py-1"
184
- aria-label="Open user menu"
185
191
  type="button"
186
192
  >
187
193
  <div
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  const { field } = usePlutoSettings()
3
+ const { t } = usePlutoI18n()
3
4
 
4
5
  const websiteTitle = field('website_title')
5
6
  const websiteDescription = field('website_description')
@@ -8,22 +9,36 @@ const websiteUrl = field('website_url')
8
9
 
9
10
  <template>
10
11
  <div class="flex flex-col gap-y-6">
11
- <UFormField label="Website Title" class="w-full lg:w-1/2">
12
- <UInput v-model="websiteTitle" placeholder="e.g. My Website" />
12
+ <UFormField
13
+ :label="t('supabase.settings.general.websiteTitle')"
14
+ class="w-full lg:w-1/2"
15
+ >
16
+ <UInput
17
+ v-model="websiteTitle"
18
+ :placeholder="t('supabase.settings.general.websiteTitlePlaceholder')"
19
+ />
13
20
  </UFormField>
14
21
 
15
- <UFormField label="Website Description" class="w-full lg:w-1/2">
22
+ <UFormField
23
+ :label="t('supabase.settings.general.websiteDescription')"
24
+ class="w-full lg:w-1/2"
25
+ >
16
26
  <UInput
17
27
  v-model="websiteDescription"
18
- placeholder="e.g. My Website Description"
28
+ :placeholder="
29
+ t('supabase.settings.general.websiteDescriptionPlaceholder')
30
+ "
19
31
  />
20
32
  </UFormField>
21
33
 
22
- <UFormField label="Website URL" class="w-full lg:w-1/2">
34
+ <UFormField
35
+ :label="t('supabase.settings.general.websiteUrl')"
36
+ class="w-full lg:w-1/2"
37
+ >
23
38
  <UInput
24
39
  v-model="websiteUrl"
40
+ :placeholder="t('supabase.settings.general.websiteUrlPlaceholder')"
25
41
  type="url"
26
- placeholder="e.g. https://example.com"
27
42
  />
28
43
  </UFormField>
29
44
  </div>
@@ -29,6 +29,11 @@ export const allowedUnauthenticatedPaths: RouteLocationRaw[] = [
29
29
 
30
30
  export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
31
31
  const toast = useToast()
32
+ // Read at setup, while the Nuxt context exists. The functions below run
33
+ // later, after an await, when the context is gone.
34
+ const { t, te } = usePlutoI18n()
35
+ const authErrorText = (error: unknown) =>
36
+ resolveAuthErrorText(error, { t, te })
32
37
 
33
38
  const supabase = useSupabaseClient()
34
39
  const supabaseSession = useSupabaseSession()
@@ -79,8 +84,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
79
84
  console.error(`Error signing in user: ${error?.message}`)
80
85
 
81
86
  toast.add({
82
- title: 'Could not log in',
83
- description: error?.message,
87
+ title: t('supabase.auth.toasts.loginFailed'),
88
+ description: authErrorText(error),
84
89
  icon: 'lucide:circle-x',
85
90
  color: 'error',
86
91
  })
@@ -133,7 +138,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
133
138
  console.warn(`User logged out`)
134
139
 
135
140
  toast.add({
136
- title: 'Logged out successfully',
141
+ title: t('supabase.auth.toasts.loggedOut'),
137
142
  icon: 'lucide:check',
138
143
  color: 'success',
139
144
  })
@@ -160,8 +165,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
160
165
 
161
166
  if (error) {
162
167
  toast.add({
163
- title: 'Could not send reset email',
164
- description: error.message,
168
+ title: t('supabase.auth.toasts.resetFailed'),
169
+ description: authErrorText(error),
165
170
  icon: 'lucide:circle-x',
166
171
  color: 'error',
167
172
  })
@@ -172,9 +177,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
172
177
  return { success: true }
173
178
  } catch (error) {
174
179
  toast.add({
175
- title: 'Could not send reset email',
176
- description:
177
- error instanceof Error ? error.message : 'An unknown error occurred.',
180
+ title: t('supabase.auth.toasts.resetFailed'),
181
+ description: authErrorText(error),
178
182
  icon: 'lucide:circle-x',
179
183
  color: 'error',
180
184
  })
@@ -193,8 +197,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
193
197
 
194
198
  if (error) {
195
199
  toast.add({
196
- title: 'Could not update password',
197
- description: error.message,
200
+ title: t('supabase.auth.toasts.updateFailed'),
201
+ description: authErrorText(error),
198
202
  icon: 'lucide:circle-x',
199
203
  color: 'error',
200
204
  })
@@ -203,7 +207,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
203
207
  }
204
208
 
205
209
  toast.add({
206
- title: 'Password updated successfully',
210
+ title: t('supabase.auth.toasts.passwordUpdated'),
207
211
  icon: 'lucide:check',
208
212
  color: 'success',
209
213
  })
@@ -211,9 +215,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
211
215
  return { success: true }
212
216
  } catch (error) {
213
217
  toast.add({
214
- title: 'Could not update password',
215
- description:
216
- error instanceof Error ? error.message : 'An unknown error occurred.',
218
+ title: t('supabase.auth.toasts.updateFailed'),
219
+ description: authErrorText(error),
217
220
  icon: 'lucide:circle-x',
218
221
  color: 'error',
219
222
  })
@@ -3,8 +3,10 @@ definePageMeta({
3
3
  layout: 'auth',
4
4
  })
5
5
 
6
+ const { t } = usePlutoI18n()
7
+
6
8
  useHead({
7
- title: 'Account Verification',
9
+ title: () => t('supabase.auth.confirm.pageTitle'),
8
10
  })
9
11
  </script>
10
12
 
@@ -18,17 +20,16 @@ useHead({
18
20
  <UIcon name="lucide:mail-check" class="inline-block" />
19
21
  </span>
20
22
 
21
- <h1 class="text-3xl font-bold">Account verified!</h1>
23
+ <h1 class="text-3xl font-bold">{{ t('supabase.auth.confirm.title') }}</h1>
22
24
  </hgroup>
23
25
 
24
26
  <p>
25
- Your account has been successfully verified. You can now start using
26
- the Dashboard.
27
+ {{ t('supabase.auth.confirm.description') }}
27
28
  </p>
28
29
 
29
30
  <div class="flex items-center justify-center gap-x-4">
30
31
  <UButton to="/admin" color="primary" icon="lucide:layout-dashboard">
31
- Go to Dashboard
32
+ {{ t('supabase.auth.confirm.goToDashboard') }}
32
33
  </UButton>
33
34
  </div>
34
35
  </div>
@@ -3,8 +3,10 @@ definePageMeta({
3
3
  layout: 'auth',
4
4
  })
5
5
 
6
+ const { t } = usePlutoI18n()
7
+
6
8
  useHead({
7
- title: 'Forgot Password',
9
+ title: () => t('supabase.auth.forgot.pageTitle'),
8
10
  })
9
11
 
10
12
  const { isSubmitting, requestPasswordReset } = await useAuth()
@@ -31,18 +33,20 @@ async function submitForm() {
31
33
  <UIcon name="lucide:mail" class="inline-block" />
32
34
  </span>
33
35
 
34
- <h1 class="text-3xl font-bold">Check your inbox</h1>
36
+ <h1 class="text-3xl font-bold">{{ t('supabase.auth.forgot.sentTitle') }}</h1>
35
37
  </hgroup>
36
38
 
37
39
  <p>
38
- If an account exists for <strong>{{ email }}</strong
39
- >, a password reset link has been sent. Check your email and follow
40
- the link.
40
+ <SupabaseMessageParts :message="t('supabase.auth.forgot.sentDescription')">
41
+ <template #email>
42
+ <strong>{{ email }}</strong>
43
+ </template>
44
+ </SupabaseMessageParts>
41
45
  </p>
42
46
 
43
47
  <div class="flex items-center justify-center">
44
48
  <UButton variant="link" to="/admin/login" class="px-0">
45
- Back to Login
49
+ {{ t('supabase.auth.backToLogin') }}
46
50
  </UButton>
47
51
  </div>
48
52
  </div>
@@ -51,13 +55,13 @@ async function submitForm() {
51
55
  <form v-else @submit.prevent="submitForm">
52
56
  <div class="flex flex-col gap-y-6">
53
57
  <hgroup class="flex flex-col gap-y-1">
54
- <h1 class="text-3xl font-bold">Forgot Password</h1>
58
+ <h1 class="text-3xl font-bold">{{ t('supabase.auth.forgot.title') }}</h1>
55
59
  <p class="text-sm text-muted">
56
- Enter your email and we'll send you a reset link.
60
+ {{ t('supabase.auth.forgot.description') }}
57
61
  </p>
58
62
  </hgroup>
59
63
 
60
- <UFormField label="Email">
64
+ <UFormField :label="t('supabase.auth.fields.email')">
61
65
  <UInput
62
66
  v-model="email"
63
67
  placeholder="victor@example.com"
@@ -78,13 +82,13 @@ async function submitForm() {
78
82
  icon="lucide:send"
79
83
  class="w-full justify-center sm:w-auto"
80
84
  >
81
- Send Reset Link
85
+ {{ t('supabase.auth.forgot.submit') }}
82
86
  </UButton>
83
87
  </UFormField>
84
88
 
85
89
  <UFormField>
86
90
  <UButton variant="link" to="/admin/login" class="px-0">
87
- Back to Login
91
+ {{ t('supabase.auth.backToLogin') }}
88
92
  </UButton>
89
93
  </UFormField>
90
94
  </div>
@@ -8,8 +8,10 @@ definePageMeta({
8
8
  layout: 'auth',
9
9
  })
10
10
 
11
+ const { t } = usePlutoI18n()
12
+
11
13
  useHead({
12
- title: 'Login',
14
+ title: () => t('supabase.auth.login.title'),
13
15
  })
14
16
 
15
17
  const { isSubmitting, login } = await useAuth()
@@ -29,9 +31,9 @@ function submitForm() {
29
31
  <UCard class="w-full max-w-100">
30
32
  <form @submit.prevent="submitForm">
31
33
  <div class="flex flex-col gap-y-6">
32
- <h1 class="text-3xl font-bold">Login</h1>
34
+ <h1 class="text-3xl font-bold">{{ t('supabase.auth.login.title') }}</h1>
33
35
 
34
- <UFormField label="Email">
36
+ <UFormField :label="t('supabase.auth.fields.email')">
35
37
  <UInput
36
38
  v-model="form.email"
37
39
  placeholder="victor@example.com"
@@ -40,7 +42,7 @@ function submitForm() {
40
42
  />
41
43
  </UFormField>
42
44
 
43
- <UFormField label="Password">
45
+ <UFormField :label="t('supabase.auth.fields.password')">
44
46
  <UInput
45
47
  v-model="form.password"
46
48
  placeholder="••••"
@@ -59,13 +61,13 @@ function submitForm() {
59
61
  icon="lucide:log-in"
60
62
  class="w-full justify-center sm:w-auto"
61
63
  >
62
- Login
64
+ {{ t('supabase.auth.login.submit') }}
63
65
  </UButton>
64
66
  </UFormField>
65
67
 
66
68
  <UFormField>
67
69
  <UButton variant="link" to="/admin/signup" class="px-0">
68
- Create Account
70
+ {{ t('supabase.auth.login.createAccount') }}
69
71
  </UButton>
70
72
  </UFormField>
71
73
  </div>
@@ -79,11 +81,11 @@ function submitForm() {
79
81
  to="/"
80
82
  class="px-0"
81
83
  >
82
- Back to Home
84
+ {{ t('supabase.auth.login.backToHome') }}
83
85
  </UButton>
84
86
 
85
87
  <UButton variant="link" to="/admin/forgot-password" class="px-0">
86
- Forgot password?
88
+ {{ t('supabase.auth.login.forgotPassword') }}
87
89
  </UButton>
88
90
  </div>
89
91
  </div>