diff --git a/api/.vscode/i18n-ally-custom-framework.yml b/api/.vscode/i18n-ally-custom-framework.yml index 4201f349..55c6a0d0 100644 --- a/api/.vscode/i18n-ally-custom-framework.yml +++ b/api/.vscode/i18n-ally-custom-framework.yml @@ -14,7 +14,7 @@ usageMatchRegex: - "[^\\w\\d]new I18nException\\(['\"`]({key})['\"`]" - "[^\\w\\d]i18n\\??.t\\(['\"`]({key})['\"`]" - "(?:{{ |\\()t ['\"`]({key})['\"`]" - - "[^\\w\\d]k\\(['\"`]({key})['\"`]" + - "[^\\w\\d]['\"`]({key})['\"`] satisfies I18nKey" # A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys # and works like how the i18next framework identifies the namespace scope from the @@ -28,6 +28,6 @@ refactorTemplates: - i18n.t('$1') - this.i18n.t('$1') - new I18nException('$1') - - "k('$1')" + - "'$1' satisfies I18nKey" # If set to true, only enables this custom framework (will disable all built-in frameworks) # monopoly: true diff --git a/api/src/@shared/schemas/auth.ts b/api/src/@shared/schemas/auth.ts index f8dd1fb2..e1221ba9 100644 --- a/api/src/@shared/schemas/auth.ts +++ b/api/src/@shared/schemas/auth.ts @@ -1,12 +1,12 @@ import { z } from 'zod'; -import { k } from '../utils/i18n'; +import type { I18nKey } from '../utils/i18n'; export const PASSWORD_MIN_LENGTH = 4; export const passwordSchema = z - .string({ required_error: k('common.errors.validation.required') }) - .min(PASSWORD_MIN_LENGTH, k('common.errors.validation.password.minlength')); + .string({ required_error: 'common.errors.validation.required' satisfies I18nKey }) + .min(PASSWORD_MIN_LENGTH, 'common.errors.validation.password.minlength' satisfies I18nKey); export const emailSchema = z - .string({ required_error: k('common.errors.validation.required') }) - .email(k('common.errors.validation.email-schema')); + .string({ required_error: 'common.errors.validation.required' satisfies I18nKey }) + .email('common.errors.validation.email-schema' satisfies I18nKey); diff --git a/api/src/@shared/utils/i18n.ts b/api/src/@shared/utils/i18n.ts index f9f05060..83975717 100644 --- a/api/src/@shared/utils/i18n.ts +++ b/api/src/@shared/utils/i18n.ts @@ -1,3 +1 @@ -export function k(key: string) { - return key; -} +export type I18nKey = string; diff --git a/client/.vscode/i18n-ally-custom-framework.yml b/client/.vscode/i18n-ally-custom-framework.yml index c3315396..1b8ba142 100644 --- a/client/.vscode/i18n-ally-custom-framework.yml +++ b/client/.vscode/i18n-ally-custom-framework.yml @@ -12,6 +12,7 @@ usageMatchRegex: # the `{key}` will be placed by a proper keypath matching regex, # you can ignore it and use your own matching rules as well - "[^\\w\\d]k\\(['\"`]({key})['\"`]" + - "[^\\w\\d]['\"`]({key})['\"`] satisfies I18nKey" # A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys # and works like how the i18next framework identifies the namespace scope from the @@ -24,7 +25,7 @@ usageMatchRegex: refactorTemplates: - "{$t('$1')}" - "t.get('$1')" - - "k('$1')" + - "'$1' satisfies I18nKey" # If set to true, only enables this custom framework (will disable all built-in frameworks) # monopoly: true diff --git a/client/src/hooks.server.ts b/client/src/hooks.server.ts index b237dba6..8aec820b 100644 --- a/client/src/hooks.server.ts +++ b/client/src/hooks.server.ts @@ -4,7 +4,7 @@ import { createTsRestClient } from '$lib/ts-rest/client'; import type { Handle, HandleFetch } from '@sveltejs/kit'; import { StatusCodes } from 'http-status-codes'; import { parseString } from 'set-cookie-parser'; -import { SESSION_COOKIE_NAME, k } from '~shared'; +import { SESSION_COOKIE_NAME, type I18nKey } from '~shared'; import { getAuthUser } from './auth/auth-handler'; import { themeCookieName, themes, type Theme } from './lib/stores'; import { HASJS_COOKIE_NAME } from './lib/utils/js-handling'; @@ -64,7 +64,7 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { } const response = await fetch(request).catch(() => { - const fakeResponse = { message: k('common.errors.server.down') }; + const fakeResponse = { message: 'common.errors.server.down' satisfies I18nKey }; const blob = new Blob([JSON.stringify(fakeResponse)], { type: 'application/json', diff --git a/client/src/i18n/fr/shared/userform.json b/client/src/i18n/fr/shared/userform.json index 28250851..ab2427b7 100644 --- a/client/src/i18n/fr/shared/userform.json +++ b/client/src/i18n/fr/shared/userform.json @@ -7,7 +7,8 @@ "new-email": "Nouveau Courriel" }, "roles": { - "admin": "Administrateur" + "admin": "Administrateur", + "chat": "Chat" }, "placeholders": { "email": "exemple@exemple.com" diff --git a/client/src/lib/utils/assertions.ts b/client/src/lib/utils/assertions.ts index c3ef2c35..83e18a48 100644 --- a/client/src/lib/utils/assertions.ts +++ b/client/src/lib/utils/assertions.ts @@ -6,7 +6,7 @@ import { StatusCodes } from 'http-status-codes'; import { redirect } from 'sveltekit-flash-message/server'; import type { Infer, SuperValidated } from 'sveltekit-superforms'; import type { AnyZodObject } from 'zod'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; export type ValidResult = T extends { status: StatusCodes.OK } ? T : never; export type InvalidResult = Exclude; @@ -59,7 +59,7 @@ export function assertTsRestActionResultOK { if ('layoutAlert' in args) { diff --git a/client/src/navigation/routes.ts b/client/src/navigation/routes.ts index e6fb8138..eeea0421 100644 --- a/client/src/navigation/routes.ts +++ b/client/src/navigation/routes.ts @@ -1,27 +1,27 @@ import type { NavElement } from '$lib/components/nav/nav-elements'; -import { Roles, k } from '~shared'; +import { Roles, type I18nKey } from '~shared'; export const navElements: NavElement[] = [ { - title: k('navbar.navigation.about'), + title: 'navbar.navigation.about' satisfies I18nKey, url: '/about', isPublic: true, drawerIconPath: 'i-mdi-information', }, { - title: k('navbar.navigation.chat'), + title: 'navbar.navigation.chat' satisfies I18nKey, url: '/chat', drawerIconPath: 'i-mdi-chat', roles: [Roles.CHAT], }, { - title: k('navbar.navigation.admin'), + title: 'navbar.navigation.admin' satisfies I18nKey, id: 'admin', drawerIconPath: 'i-mdi-shield-crown', roles: [Roles.ADMIN], elements: [ { - title: k('navbar.navigation.admin-users'), + title: 'navbar.navigation.admin-users' satisfies I18nKey, url: '/admin/users', drawerIconPath: 'i-mdi-account-details', matches: [/\/admin\/users\/.+/], diff --git a/client/src/routes/(auth)/forgot_password/+page.server.ts b/client/src/routes/(auth)/forgot_password/+page.server.ts index 0c03b506..3d76d1be 100644 --- a/client/src/routes/(auth)/forgot_password/+page.server.ts +++ b/client/src/routes/(auth)/forgot_password/+page.server.ts @@ -6,7 +6,7 @@ import { error } from '@sveltejs/kit'; import { StatusCodes } from 'http-status-codes'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; import type { Actions, PageServerLoad } from './$types'; import { requestPasswordSchema, resetPasswordSchema } from './schemas'; @@ -59,7 +59,7 @@ export const actions = { result: () => tsrest.auth.forgotPasswordRequest({ query: form.data }), onValid: () => ({ layoutAlert: createLayoutAlert({ - text: k('(auth).forgot_password.request.alert'), + text: '(auth).forgot_password.request.alert' satisfies I18nKey, }), }), }); @@ -74,7 +74,7 @@ export const actions = { onValid: () => ({ redirectTo: '/login', layoutAlert: createLayoutAlert({ - text: k('(auth).forgot_password.reset.action.success'), + text: '(auth).forgot_password.reset.action.success' satisfies I18nKey, }), }), }); diff --git a/client/src/routes/(auth)/forgot_password/schemas.ts b/client/src/routes/(auth)/forgot_password/schemas.ts index 6f057f49..97cf786b 100644 --- a/client/src/routes/(auth)/forgot_password/schemas.ts +++ b/client/src/routes/(auth)/forgot_password/schemas.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { emailSchema, k, passwordSchema } from '~shared'; +import { emailSchema, passwordSchema, type I18nKey } from '~shared'; export const requestPasswordSchema = z.object({ email: emailSchema, @@ -7,5 +7,5 @@ export const requestPasswordSchema = z.object({ export const resetPasswordSchema = z.object({ password: passwordSchema, - resetToken: z.string({ required_error: k('(auth).forgot_password.reset.action.no-token') }), + resetToken: z.string({ required_error: '(auth).forgot_password.reset.action.no-token' satisfies I18nKey }), }); diff --git a/client/src/routes/(auth)/login/+page.server.ts b/client/src/routes/(auth)/login/+page.server.ts index f0c68079..285d3dd6 100644 --- a/client/src/routes/(auth)/login/+page.server.ts +++ b/client/src/routes/(auth)/login/+page.server.ts @@ -6,7 +6,7 @@ import { StatusCodes } from 'http-status-codes'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; -import { emailSchema, k, passwordSchema } from '~shared'; +import { emailSchema, passwordSchema, type I18nKey } from '~shared'; import type { Actions, PageServerLoad } from './$types'; const schema = z.object({ @@ -29,7 +29,7 @@ export const load = (async ({ url, locals: { sessionUser } }) => { } return createLayoutAlert({ - text: k('(auth).login.errors.access'), + text: '(auth).login.errors.access' satisfies I18nKey, type: 'warning', }); })(); diff --git a/client/src/routes/(auth)/register/+page.server.ts b/client/src/routes/(auth)/register/+page.server.ts index fcf90dd5..c18bece4 100644 --- a/client/src/routes/(auth)/register/+page.server.ts +++ b/client/src/routes/(auth)/register/+page.server.ts @@ -3,7 +3,7 @@ import { error, type Actions } from '@sveltejs/kit'; import { StatusCodes } from 'http-status-codes'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; import type { PageServerLoad } from './$types'; import { registerSchema } from './schema'; @@ -11,7 +11,7 @@ export const load = (async ({ url, locals: { tsrest } }) => { const registerToken = url.searchParams.get('token'); if (!registerToken) { - error(StatusCodes.BAD_REQUEST, { message: k('(auth).register.errors.missing-token') }); + error(StatusCodes.BAD_REQUEST, { message: '(auth).register.errors.missing-token' satisfies I18nKey }); } const result = await tsrest.auth.initRegistration({ query: { registerToken } }); diff --git a/client/src/routes/(user)/settings/experience/+page.server.ts b/client/src/routes/(user)/settings/experience/+page.server.ts index 86c8e3c8..d55a64fd 100644 --- a/client/src/routes/(user)/settings/experience/+page.server.ts +++ b/client/src/routes/(user)/settings/experience/+page.server.ts @@ -4,7 +4,7 @@ import { assertTsRestActionResultOK } from '$lib/utils/assertions'; import { superValidate } from 'sveltekit-superforms'; import { zod, type Infer } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; import type { Actions, PageServerLoad } from './$types'; const langSchema = z.object({ @@ -35,7 +35,9 @@ export const actions = { }, onValid: () => ({ toasts: createToasts({ - text: lang ? k('settings.experience.lang.toast.targetted') : k('settings.experience.lang.toast.automatic'), + text: lang + ? ('settings.experience.lang.toast.targetted' satisfies I18nKey) + : ('settings.experience.lang.toast.automatic' satisfies I18nKey), timeout: 3000, }), }), diff --git a/client/src/routes/(user)/settings/profile/+page.server.ts b/client/src/routes/(user)/settings/profile/+page.server.ts index 74aec8c4..fdae404b 100644 --- a/client/src/routes/(user)/settings/profile/+page.server.ts +++ b/client/src/routes/(user)/settings/profile/+page.server.ts @@ -5,7 +5,7 @@ import { fail, type Actions } from '@sveltejs/kit'; import { StatusCodes } from 'http-status-codes'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; import type { PageServerLoad } from './$types'; export const load = (async ({ locals: { sessionUser } }) => { @@ -31,7 +31,7 @@ export const actions = { if (!(profilePictureFile instanceof File)) { const toasts = createToasts({ - text: k('settings.profile.picture.errors.missing'), + text: 'settings.profile.picture.errors.missing' satisfies I18nKey, }); return fail(StatusCodes.BAD_REQUEST, { toasts }); diff --git a/client/src/routes/(user)/settings/profile/email/+page.server.ts b/client/src/routes/(user)/settings/profile/email/+page.server.ts index 4b878251..fdf81639 100644 --- a/client/src/routes/(user)/settings/profile/email/+page.server.ts +++ b/client/src/routes/(user)/settings/profile/email/+page.server.ts @@ -7,7 +7,7 @@ import { redirect } from 'sveltekit-flash-message/server'; import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; -import { emailSchema, k } from '~shared'; +import { emailSchema, type I18nKey } from '~shared'; import type { PageServerLoad } from './$types'; const emailChangeSchema = z.object({ @@ -60,7 +60,7 @@ export const actions = { }, onValid: () => ({ toasts: createToasts({ - text: k('settings.profile.email.request.success'), + text: 'settings.profile.email.request.success' satisfies I18nKey, i18nPayload: { email: form.data.email }, }), }), diff --git a/client/src/routes/(user)/settings/security/+page.server.ts b/client/src/routes/(user)/settings/security/+page.server.ts index 18389518..734b04f4 100644 --- a/client/src/routes/(user)/settings/security/+page.server.ts +++ b/client/src/routes/(user)/settings/security/+page.server.ts @@ -4,7 +4,7 @@ import type { Actions } from '@sveltejs/kit'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; -import { k, passwordSchema } from '~shared'; +import { passwordSchema, type I18nKey } from '~shared'; import type { PageServerLoad } from './$types'; const newPasswordFormSchema = z @@ -13,7 +13,7 @@ const newPasswordFormSchema = z confirm: z.string(), }) .refine(({ password, confirm }) => password === confirm, { - message: k('settings.security.actions.password.errors.not-matching'), + message: 'settings.security.actions.password.errors.not-matching' satisfies I18nKey, path: ['confirm'], }); @@ -34,7 +34,7 @@ export const actions = { onValid: () => ({ form, toasts: createToasts({ - text: k('settings.security.actions.password.updated.success'), + text: 'settings.security.actions.password.updated.success' satisfies I18nKey, }), }), }); diff --git a/client/src/routes/+layout.server.ts b/client/src/routes/+layout.server.ts index 58a50add..94ae8c34 100644 --- a/client/src/routes/+layout.server.ts +++ b/client/src/routes/+layout.server.ts @@ -2,7 +2,7 @@ import type { AppPageData } from '$app-types'; import { createLayoutAlert } from '$lib/components/LayoutAlert/helper'; import { HASJS_COOKIE_NAME } from '$lib/utils/js-handling'; import { loadFlash } from 'sveltekit-flash-message/server'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; export const load = loadFlash(async (event) => { const { @@ -28,13 +28,13 @@ export const load = loadFlash(async (event) => { if (sessionUser === undefined) { layoutAlert = createLayoutAlert({ - text: k('common.errors.server.down'), + text: 'common.errors.server.down' satisfies I18nKey, i18nPayload: { userHasJs }, type: 'error', }); } else if (url.searchParams.has('forbidden')) { layoutAlert = createLayoutAlert({ - text: k('common.errors.access.forbidden'), + text: 'common.errors.access.forbidden' satisfies I18nKey, type: 'error', }); } diff --git a/client/src/routes/admin/users/+page.server.ts b/client/src/routes/admin/users/+page.server.ts index 14f9c2cd..97b16bbb 100644 --- a/client/src/routes/admin/users/+page.server.ts +++ b/client/src/routes/admin/users/+page.server.ts @@ -3,7 +3,7 @@ import { assertTsRestActionResultOK } from '$lib/utils/assertions'; import { streamed } from '$lib/utils/streaming'; import { StatusCodes } from 'http-status-codes'; import { redirect } from 'sveltekit-flash-message/server'; -import { emailSchema, k } from '~shared'; +import { emailSchema, type I18nKey } from '~shared'; import type { Actions, PageServerLoad } from './$types'; export const load = (async ({ locals: { tsrest } }) => { @@ -42,9 +42,9 @@ export const actions = { '/admin/users', { toasts: createToasts({ - text: k('admin.users.actions.delete.errors.missing-email.error'), + text: 'admin.users.actions.delete.errors.missing-email.error' satisfies I18nKey, type: 'warning', - extraData: k('admin.users.actions.delete.errors.missing-email.details'), + extraData: 'admin.users.actions.delete.errors.missing-email.details' satisfies I18nKey, }), }, cookies, @@ -56,7 +56,7 @@ export const actions = { result: () => tsrest.users.admin.deleteUser({ body: { email } }), onValid: () => ({ toasts: createToasts({ - text: k('admin.users.actions.delete.success'), + text: 'admin.users.actions.delete.success' satisfies I18nKey, i18nPayload: { email }, }), }), diff --git a/client/src/routes/admin/users/[email]/+page.server.ts b/client/src/routes/admin/users/[email]/+page.server.ts index 81803ef0..a9135e8d 100644 --- a/client/src/routes/admin/users/[email]/+page.server.ts +++ b/client/src/routes/admin/users/[email]/+page.server.ts @@ -2,7 +2,7 @@ import { createToasts } from '$lib/components/ToastManager/helper'; import { assertTsRestActionResultOK, assertTsRestResultOK } from '$lib/utils/assertions'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; import { adminUserFormSchema } from '../schema/schema'; import type { Actions, PageServerLoad } from './$types'; @@ -50,7 +50,7 @@ export const actions = { onValid: () => ({ redirectTo: '/admin/users', toasts: createToasts({ - text: k('admin.users.actions.edit.success'), + text: 'admin.users.actions.edit.success' satisfies I18nKey, i18nPayload: { email: editableUserEmail }, }), }), diff --git a/client/src/routes/admin/users/components/UsersTable/ResendInviteButton.svelte b/client/src/routes/admin/users/components/UsersTable/ResendInviteButton.svelte index 4b45f464..608ac05b 100644 --- a/client/src/routes/admin/users/components/UsersTable/ResendInviteButton.svelte +++ b/client/src/routes/admin/users/components/UsersTable/ResendInviteButton.svelte @@ -4,7 +4,7 @@ import type { TsRestClient } from '$lib/ts-rest/client'; import { Button } from 'flowbite-svelte'; import { StatusCodes } from 'http-status-codes'; - import { k } from '~shared'; + import type { I18nKey } from '~shared'; import type { BaseUser } from '../../types'; let i18n = getI18n(); $: ({ t } = $i18n); @@ -37,14 +37,14 @@ }); } else { toasts = createToasts({ - text: k('admin.users.tables.actions.resend-invite.toasts.error-server'), + text: 'admin.users.tables.actions.resend-invite.toasts.error-server' satisfies I18nKey, timeout: showInviteResentTextDuration, // extraData: `
${errors}
`, }); } } catch (error) { toasts = createToasts({ - text: k('admin.users.tables.actions.resend-invite.toasts.error-local'), + text: 'admin.users.tables.actions.resend-invite.toasts.error-local' satisfies I18nKey, type: 'warning', timeout: showInviteResentTextDuration, extraData: `
${error}
`, diff --git a/client/src/routes/admin/users/new/+page.server.ts b/client/src/routes/admin/users/new/+page.server.ts index a2bb69e4..06c7e890 100644 --- a/client/src/routes/admin/users/new/+page.server.ts +++ b/client/src/routes/admin/users/new/+page.server.ts @@ -5,7 +5,7 @@ import { fail } from '@sveltejs/kit'; import { StatusCodes } from 'http-status-codes'; import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; import { adminNewUserFormSchema } from '../schema/schema'; import type { Actions, PageServerLoad } from './$types'; @@ -58,7 +58,7 @@ export const actions = { onValid: () => ({ redirectTo: '/admin/users', toasts: createToasts({ - text: k('admin.users.actions.create.success'), + text: 'admin.users.actions.create.success' satisfies I18nKey, i18nPayload: { email: form.data.email }, }), }), diff --git a/client/src/routes/chat/schema.ts b/client/src/routes/chat/schema.ts index edf00efd..37a104e8 100644 --- a/client/src/routes/chat/schema.ts +++ b/client/src/routes/chat/schema.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { k } from '~shared'; +import type { I18nKey } from '~shared'; export const chatSchema = z.object({ - message: z.string().min(1, k('chat.errors.message.empty')), + message: z.string().min(1, 'chat.errors.message.empty' satisfies I18nKey), }); diff --git a/fullstacked.code-workspace b/fullstacked.code-workspace index db8aa246..3b783ddc 100644 --- a/fullstacked.code-workspace +++ b/fullstacked.code-workspace @@ -105,6 +105,10 @@ "i18n-ally.tabStyle": "tab", "i18n-ally.keystyle": "nested", "i18n-ally.regex.key": "[\\w\\d\\. \\(\\)\\-\\[\\]\\/:]*?", + "i18n-ally.localesPaths": [ + "api/src/i18n", + "client/src/i18n", + ], "triggerTaskOnSave.tasks": { "format prisma file": [ "**/schema.prisma",