diff --git a/apps/dashboard/src/components/admin/RolesSettings.tsx b/apps/dashboard/src/components/admin/RolesSettings.tsx index 0090bac..da9a8ca 100644 --- a/apps/dashboard/src/components/admin/RolesSettings.tsx +++ b/apps/dashboard/src/components/admin/RolesSettings.tsx @@ -1,5 +1,6 @@ import { useMemo, useState } from 'react'; import { Plus, Trash2, Lock } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; import Modal from '../ui/Modal'; import { useRoles, @@ -10,6 +11,7 @@ import { } from '../../hooks/useAdmin'; export default function RolesSettings({ propertyId }: { propertyId: string }) { + const { t } = useTranslation(); const { data: roles = [] } = useRoles(propertyId); const { data: catalog = [] } = usePermissionCatalog(propertyId); const { create, remove, setPermissions } = useRoleMutations(propertyId); @@ -47,9 +49,9 @@ export default function RolesSettings({ propertyId }: { propertyId: string }) { {/* Role list */}
-

Roles

+

{t('admin.roles')}

-

{r.permissions.length} permissions

+

{t('admin.permissionsCount', { count: r.permissions.length })}

))} @@ -87,18 +89,18 @@ export default function RolesSettings({ propertyId }: { propertyId: string }) { } /> ) : ( -
Select a role
+
{t('admin.selectARole')}
)} - setCreateOpen(false)} title="New Role"> + setCreateOpen(false)} title={t('admin.newRole')}>
-
setKey(e.target.value)} placeholder="spa_manager" className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
-
setName(e.target.value)} placeholder="Spa Manager" className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
-
setDescription(e.target.value)} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
- {create.isError &&

Could not create role (key may already exist).

} +
setKey(e.target.value)} placeholder={t('admin.keyPlaceholder')} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
+
setName(e.target.value)} placeholder={t('admin.namePlaceholder')} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
+
setDescription(e.target.value)} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
+ {create.isError &&

{t('admin.couldNotCreateRole')}

}
@@ -121,6 +123,7 @@ function PermissionMatrix({ onSave: (keys: string[]) => void; onDelete: () => void; }) { + const { t } = useTranslation(); const [selected, setSelected] = useState(role.permissions); const readOnly = role.isSystem; const toggle = (key: string) => @@ -136,13 +139,13 @@ function PermissionMatrix({

{role.name} - {readOnly && system role (read-only)} + {readOnly && {t('admin.systemRoleReadOnly')}}

{role.description &&

{role.description}

}
{!readOnly && ( )} @@ -172,9 +175,9 @@ function PermissionMatrix({ {!readOnly && (
- {dirty && Unsaved changes} + {dirty && {t('admin.unsavedChanges')}}
)} diff --git a/apps/dashboard/src/components/admin/UserSettings.tsx b/apps/dashboard/src/components/admin/UserSettings.tsx index 0a61c81..76a8652 100644 --- a/apps/dashboard/src/components/admin/UserSettings.tsx +++ b/apps/dashboard/src/components/admin/UserSettings.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { UserPlus, ShieldCheck, Ban } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; import Modal from '../ui/Modal'; import { useUsers, @@ -10,6 +11,7 @@ import { } from '../../hooks/useAdmin'; export default function UserSettings({ propertyId }: { propertyId: string }) { + const { t } = useTranslation(); const { data: users = [], isLoading } = useUsers(propertyId); const { data: roles = [] } = useRoles(propertyId); const { create, disable, assignRoles } = useUserMutations(propertyId); @@ -39,20 +41,20 @@ export default function UserSettings({ propertyId }: { propertyId: string }) { return (
-

Users

+

{t('admin.users')}

- - - - - + + + + + @@ -76,41 +78,41 @@ export default function UserSettings({ propertyId }: { propertyId: string }) { ))} {!isLoading && users.length === 0 && ( - + )}
NameEmailStatusRolesActions{t('admin.name')}{t('admin.email')}{t('admin.status')}{t('admin.roles')}{t('admin.actions')}
- {u.status !== 'disabled' && ( - )}
No users yet
{t('admin.noUsersYet')}
{/* Create user */} - setCreateOpen(false)} title="Add User"> + setCreateOpen(false)} title={t('admin.addUser')}>
-
setName(e.target.value)} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
-
setEmail(e.target.value)} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
+
setName(e.target.value)} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
+
setEmail(e.target.value)} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
- + setNewRoleIds((s) => toggle(s, id))} />
- {create.isError &&

Could not create user (email may already exist).

} + {create.isError &&

{t('admin.couldNotCreateUser')}

}
{/* Edit roles */} - setEditUser(null)} title={editUser ? `Roles — ${editUser.name}` : ''}> + setEditUser(null)} title={editUser ? t('admin.rolesForUser', { name: editUser.name }) : ''}> {editUser && ( void }) { + const { t } = useTranslation(); return (
{roles.map((r) => ( ))}
@@ -141,13 +144,14 @@ function RoleChecklist({ roles, selected, onToggle }: { roles: AdminRole[]; sele } function EditRoles({ roles, initial, pending, onSave }: { roles: AdminRole[]; initial: string[]; pending: boolean; onSave: (roleIds: string[]) => void }) { + const { t } = useTranslation(); const [selected, setSelected] = useState(initial); const toggle = (id: string) => setSelected((s) => (s.includes(id) ? s.filter((x) => x !== id) : [...s, id])); return (
); diff --git a/apps/dashboard/src/components/media/MediaGallery.tsx b/apps/dashboard/src/components/media/MediaGallery.tsx index 6d527b0..4af3f42 100644 --- a/apps/dashboard/src/components/media/MediaGallery.tsx +++ b/apps/dashboard/src/components/media/MediaGallery.tsx @@ -1,5 +1,6 @@ import { useState, useRef } from 'react'; import { Star, Trash2, ArrowLeft, ArrowRight, ImagePlus, Upload, Link as LinkIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; import { useMediaList, useMediaConfig, @@ -22,6 +23,7 @@ interface Props { * (only when object storage is configured), set-primary, delete, and reorder. */ export default function MediaGallery({ propertyId, ownerType, ownerId, canManage = true }: Props) { + const { t } = useTranslation(); const { data: items = [], isLoading } = useMediaList(propertyId, ownerType, ownerId); const { data: config } = useMediaConfig(); const { addByUrl, upload, remove, setPrimary, reorder } = useMediaMutations(propertyId, ownerType, ownerId); @@ -53,11 +55,11 @@ export default function MediaGallery({ propertyId, ownerType, ownerId, canManage return (
{isLoading ? ( -

Loading images…

+

{t('media.loadingImages')}

) : items.length === 0 ? (
- No images yet + {t('media.noImagesYet')}
) : (
@@ -66,7 +68,7 @@ export default function MediaGallery({ propertyId, ownerType, ownerId, canManage {m.altText {m.isPrimary && ( - Primary + {t('media.primary')} )} {m.caption && ( @@ -75,21 +77,21 @@ export default function MediaGallery({ propertyId, ownerType, ownerId, canManage {canManage && (
{!m.isPrimary && ( - )} -
)} {canManage && items.length > 1 && (
- -
@@ -107,14 +109,14 @@ export default function MediaGallery({ propertyId, ownerType, ownerId, canManage type="url" value={url} onChange={(e) => setUrl(e.target.value)} - placeholder="Paste an image URL" + placeholder={t('media.urlPlaceholder')} className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" /> setCaption(e.target.value)} - placeholder="Caption (optional)" + placeholder={t('media.captionPlaceholder')} className="w-40 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-telivity-teal" />
@@ -144,11 +146,11 @@ export default function MediaGallery({ propertyId, ownerType, ownerId, canManage disabled={upload.isPending} className="flex items-center gap-2 text-sm text-telivity-slate hover:text-telivity-teal disabled:opacity-50" > - {upload.isPending ? 'Uploading…' : 'Upload a file'} + {upload.isPending ? t('media.uploading') : t('media.uploadFile')}
)} - {addByUrl.isError &&

Could not add image. Check the URL.

} + {addByUrl.isError &&

{t('media.couldNotAddImage')}

}
)} diff --git a/apps/dashboard/src/locales/de.json b/apps/dashboard/src/locales/de.json index 7d545b5..326ccbe 100644 --- a/apps/dashboard/src/locales/de.json +++ b/apps/dashboard/src/locales/de.json @@ -1,33 +1,83 @@ { "nav": { - "tagline": "", - "mainNavigation": "", - "closeMenu": "", - "dashboard": "", - "frontDesk": "", + "tagline": "Hotel AI Plattform", + "mainNavigation": "Hauptmenü", + "closeMenu": "Menü schließen", + "dashboard": "Dashboard", + "frontDesk": "Front Office", "reservations": "Reservierungen", "guests": "Gäste", "rooms": "Zimmer", - "housekeeping": "", - "foliosBilling": "", - "ratePlans": "", - "revenueManagement": "", - "nightAudit": "", - "reports": "", - "channels": "", - "communications": "", - "reviews": "", + "housekeeping": "Housekeeping", + "foliosBilling": "Gästekonten & Rechnungstellung", + "ratePlans": "Raten Pläne", + "revenueManagement": "Revenue-Management", + "nightAudit": "Tagesabschluss", + "reports": "Berichte", + "channels": "Kanäle", + "communications": "Kommunikation", + "reviews": "Bewertungen", "usersRoles": "Benutzer & Rollen", - "settings": "" + "settings": "Einstellungen" }, "header": { "selectProperty": "Unterkunft auswählen", - "openMenu": "", - "notifications": "", - "logout": "", - "user": "", - "statusLive": "", - "statusOffline": "", + "openMenu": "Menü öffnen", + "notifications": "Nachrichten", + "logout": "Logout", + "user": "Nutzer", + "statusLive": "Status Online", + "statusOffline": "Status Offline", "language": "Sprache" + }, + "admin": { + "users": "", + "roles": "", + "addUser": "", + "new": "", + "newRole": "", + "name": "", + "email": "", + "status": "", + "actions": "", + "editRoles": "", + "disableUser": "", + "disable": "", + "noUsersYet": "", + "rolesForUser": "", + "system": "", + "creating": "", + "createUser": "", + "createRole": "", + "couldNotCreateUser": "", + "couldNotCreateRole": "", + "saving": "", + "saveRoles": "", + "savePermissions": "", + "delete": "", + "unsavedChanges": "", + "selectARole": "", + "permissionsCount": "", + "systemRoleReadOnly": "", + "keyLabel": "", + "keyPlaceholder": "", + "namePlaceholder": "", + "description": "", + "required": "*" + }, + "media": { + "loadingImages": "", + "noImagesYet": "", + "primary": "", + "setAsPrimary": "", + "delete": "", + "moveLeft": "", + "moveRight": "", + "urlPlaceholder": "", + "captionPlaceholder": "", + "add": "", + "uploading": "", + "uploadFile": "", + "couldNotAddImage": "" } } diff --git a/apps/dashboard/src/locales/en.json b/apps/dashboard/src/locales/en.json index bf7e478..8787a15 100644 --- a/apps/dashboard/src/locales/en.json +++ b/apps/dashboard/src/locales/en.json @@ -29,5 +29,55 @@ "statusLive": "Live", "statusOffline": "Offline", "language": "Language" + }, + "admin": { + "users": "Users", + "roles": "Roles", + "addUser": "Add User", + "new": "New", + "newRole": "New Role", + "name": "Name", + "email": "Email", + "status": "Status", + "actions": "Actions", + "editRoles": "Edit roles", + "disableUser": "Disable user", + "disable": "Disable", + "noUsersYet": "No users yet", + "rolesForUser": "Roles — {{name}}", + "system": "system", + "creating": "Creating…", + "createUser": "Create User", + "createRole": "Create Role", + "couldNotCreateUser": "Could not create user (email may already exist).", + "couldNotCreateRole": "Could not create role (key may already exist).", + "saving": "Saving…", + "saveRoles": "Save Roles", + "savePermissions": "Save Permissions", + "delete": "Delete", + "unsavedChanges": "Unsaved changes", + "selectARole": "Select a role", + "permissionsCount": "{{count}} permissions", + "systemRoleReadOnly": "system role (read-only)", + "keyLabel": "Key * (lowercase, underscores)", + "keyPlaceholder": "spa_manager", + "namePlaceholder": "Spa Manager", + "description": "Description", + "required": "*" + }, + "media": { + "loadingImages": "Loading images…", + "noImagesYet": "No images yet", + "primary": "Primary", + "setAsPrimary": "Set as primary", + "delete": "Delete", + "moveLeft": "Move left", + "moveRight": "Move right", + "urlPlaceholder": "Paste an image URL", + "captionPlaceholder": "Caption (optional)", + "add": "Add", + "uploading": "Uploading…", + "uploadFile": "Upload a file", + "couldNotAddImage": "Could not add image. Check the URL." } }