add i18n support

This commit is contained in:
DoraTiger 2024-02-29 14:09:55 +00:00
parent c708b1759e
commit c9345c13d8
22 changed files with 2140 additions and 680 deletions

View file

@ -1,11 +1,11 @@
<template>
<BaseModal v-model="modal">
<template #title> Create Item </template>
<template #title> {{ $t("item.create.title") }} </template>
<form @submit.prevent="create()">
<LocationSelector v-model="form.location" />
<FormTextField ref="nameInput" v-model="form.name" :trigger-focus="focused" :autofocus="true" label="Item Name" />
<FormTextArea v-model="form.description" label="Item Description" />
<FormMultiselect v-model="form.labels" label="Labels" :items="labels ?? []" />
<FormTextField ref="nameInput" v-model="form.name" :trigger-focus="focused" :autofocus="true" :label="$t('item.create.name')" />
<FormTextArea v-model="form.description" :label="$t('item.create.desp')" />
<FormMultiselect v-model="form.labels" :label="$t('item.create.label')" :items="labels ?? []" />
<div class="modal-action">
<div class="flex justify-center">
<BaseButton class="rounded-r-none" :loading="loading" type="submit">
@ -13,7 +13,7 @@
<Icon name="mdi-package-variant" class="swap-off h-5 w-5" />
<Icon name="mdi-package-variant-closed" class="swap-on h-5 w-5" />
</template>
Create
{{ $t("item.create.button1") }}
</BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
@ -21,7 +21,7 @@
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<li>
<button type="button" @click="create(false)">Create and Add Another</button>
<button type="button" @click="create(false)">{{ $t("item.create.button2") }}</button>
</li>
</ul>
</div>
@ -29,7 +29,7 @@
</div>
</form>
<p class="text-sm text-center mt-4">
use <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> to create and add another
{{ $t("item.create.tips1") }} <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> {{ $t("item.create.tips2") }}
</p>
</BaseModal>
</template>

View file

@ -26,7 +26,7 @@
<template>
<section>
<BaseSectionHeader class="mb-2 flex justify-between items-center">
Items
{{ $t("item.selectable.title") }}
<template #description>
<div v-if="!viewSet" class="dropdown dropdown-hover dropdown-left">
<label tabindex="0" class="btn btn-ghost m-1">
@ -36,13 +36,13 @@
<li>
<button @click="setViewPreference('card')">
<Icon name="mdi-card-text-outline" class="h-5 w-5" />
Card
{{ $t("item.selectable.card") }}
</button>
</li>
<li>
<button @click="setViewPreference('table')">
<Icon name="mdi-table" class="h-5 w-5" />
Table
{{ $t("item.selectable.table") }}
</button>
</li>
</ul>
@ -56,7 +56,7 @@
<template v-else>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<ItemCard v-for="item in items" :key="item.id" :item="item" />
<div class="first:block hidden text-lg">No Items to Display</div>
<div class="first:block hidden text-lg">{{ $t("item.selectable.empty") }}</div>
</div>
</template>
</section>

View file

@ -1,25 +1,25 @@
<template>
<BaseModal v-model="modal">
<template #title> Create Label </template>
<template #title> {{ $t("label.create.title") }} </template>
<form @submit.prevent="create()">
<FormTextField
ref="locationNameRef"
v-model="form.name"
:trigger-focus="focused"
:autofocus="true"
label="Label Name"
:label="$t('label.create.name')"
/>
<FormTextArea v-model="form.description" label="Label Description" />
<FormTextArea v-model="form.description" :label="$t('label.create.desp')" />
<div class="modal-action">
<div class="flex justify-center">
<BaseButton class="rounded-r-none" :loading="loading" type="submit"> Create </BaseButton>
<BaseButton class="rounded-r-none" :loading="loading" type="submit"> {{ $t("label.create.button1") }} </BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<Icon class="h-5 w-5" name="mdi-chevron-down" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<li>
<button type="button" @click="create(false)">Create and Add Another</button>
<button type="button" @click="create(false)">{{ $t("label.create.button2") }}</button>
</li>
</ul>
</div>
@ -27,7 +27,7 @@
</div>
</form>
<p class="text-sm text-center mt-4">
use <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> to create and add another
{{ $t("label.create.tips1") }} <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> {{ $t("label.create.tips2") }}
</p>
</BaseModal>
</template>

View file

@ -1,26 +1,26 @@
<template>
<BaseModal v-model="modal">
<template #title> Create Location </template>
<template #title> {{ $t("location.create.title") }} </template>
<form @submit.prevent="create()">
<FormTextField
ref="locationNameRef"
v-model="form.name"
:trigger-focus="focused"
:autofocus="true"
label="Location Name"
:label="$t('location.create.name')"
/>
<FormTextArea v-model="form.description" label="Location Description" />
<FormTextArea v-model="form.description" :label="$t('location.create.desp')" />
<LocationSelector v-model="form.parent" />
<div class="modal-action">
<div class="flex justify-center">
<BaseButton class="rounded-r-none" type="submit" :loading="loading"> Create </BaseButton>
<BaseButton class="rounded-r-none" type="submit" :loading="loading"> {{ $t("location.create.button1") }} </BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<Icon class="h-5 w-5" name="mdi-chevron-down" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<li>
<button type="button" @click="create(false)">Create and Add Another</button>
<button type="button" @click="create(false)">{{ $t("location.create.button2") }}</button>
</li>
</ul>
</div>
@ -28,7 +28,7 @@
</div>
</form>
<p class="text-sm text-center mt-4">
use <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> to create and add another
{{ $t("location.create.tips1") }} <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> {{ $t("location.create.tips2") }}
</p>
</BaseModal>
</template>

View file

@ -1,5 +1,11 @@
<template>
<FormAutocomplete2 v-if="locations" v-model="value" :items="locations" display="name" label="Parent Location">
<FormAutocomplete2
v-if="locations"
v-model="value"
:items="locations"
display="name"
:label="$t('location.selectable.parent')"
>
<template #display="{ item, selected, active }">
<div>
<div class="flex w-full">

317
frontend/lang/en-US.json Normal file
View file

@ -0,0 +1,317 @@
{
"index": {
"login": "Login",
"login_email": "Email",
"login_password": "Password",
"login_remember": "Remember me",
"register": "Register",
"register_email": "Set your email?",
"register_username": "What's your name?",
"register_password": "Set your password",
"slogon": "Track, Organize, and Manage your Things.",
"refuse":"Registration Disabled"
},
"home": {
"quick_statistics": "Quick Statistics",
"recently_added": "Recently Added",
"storage_locations": "Storage Locations",
"labels": "Labels"
},
"label": {
"create": {
"title": "Create Label",
"name": "Label Name",
"desp": "Label Description",
"button1": "Create",
"button2": "Create and Add Another",
"tips1": "use",
"tips2": "to create and add another"
},
"update": {
"title": "Update Label",
"name": "Label Name",
"desp": "Label Description",
"button": "Update"
},
"edit": {
"created": "Created",
"edit_button": "Edit",
"delete_button": "Delete"
}
},
"location": {
"selectable": {
"parent": "Parent Location"
},
"child_locations": "Child Locations",
"create": {
"title": "Create Location",
"name": "Location Name",
"desp": "Location Description",
"button1": "Create",
"button2": "Create and Add Another",
"tips1": "use",
"tips2": "to create and add another"
},
"update": {
"title": "Update Location",
"name": "Location Name",
"desp": "Location Description",
"button": "Update"
},
"edit": {
"created": "Created",
"edit_button": "Edit",
"delete_button": "Delete"
}
},
"item": {
"selectable": {
"title": "Items",
"card": "Card",
"table": "Table",
"empty": "No Items to Display"
},
"edit":{
"editState":{
"title":"Attachment Edit",
"attachment":{
"title":"Attachment Title",
"type":"Attachment Type"
},
"tips":"This options is only available for photos. Only one photo can be primary. If you select this option, the current primary photo, if any will be unselected.",
"button":"Update"
},
"advanced":"Advanced",
"save_button":"Save",
"delete_button":"Delete",
"details":{
"title":"Edit Details",
"labels":"Labels",
"parent":"Parent Item"
},
"custom":{
"title":"Custom Fields",
"name":"Name",
"value":"Value",
"button":"Add"
},
"attachments":{
"title":"Attachments",
"tips":"Changes to attachments will be saved immediately",
"dropZone":{
"photo":"Photo",
"warranty":"Warranty",
"manual":"Manual",
"attachment":"Attachment",
"receipt":"Receipt",
"tips":"Drag and drop files here or click to select files"
}
},
"purchase":{
"title":"Purchase Details"
},
"warranty":{
"title":"Warranty"
},
"sold":{
"title":"Sold Details"
}
},
"create": {
"title": "Create Item",
"name": "Item Name",
"desp": "Item Description",
"label":"Labels",
"button1": "Create",
"button2": "Create and Add Another",
"tips1": "use",
"tips2": "to create and add another"
},
"new":{
"title":"Add an Item To Your Inventory",
"info":"Required Information",
"name":"Name",
"desp":"Description",
"product":{
"button":"Product Information",
"serialNumber":"Serial Number",
"modelNumber":"Model Number",
"manufacturer":"Manufacturer"
},
"purchase":{
"button":"Purchase Information",
"purchaseTime":"Purchase Time",
"purchasePrice":"Purchase Price",
"purchaseFrom":"Purchase From"
},
"sold":{
"button":"Sold Information",
"soldTime":"Sold Time",
"soldPrice":"Sold Price",
"soldTo":"Sold To",
"soldNotes":"Sold Notes"
},
"extras":{
"button":"Extras",
"notes":"Notes"
}
}
},
"assets":{
"title":"This Asset Id is associated with multiple items"
},
"items": {
"querying_Asset_ID_Number": "Querying Asset ID Number",
"query": {
"search": "Search",
"locations": "Locations",
"labels": "Labels",
"options": "Options",
"options_dropdown": {
"includeArchived": "Include Archived Items",
"fieldSelector": "Field Selector",
"reset": "Reset Search"
},
"fieldSelector_dropdown": {
"custom_fields": "Custom Fields",
"field": "Field",
"field_value": "Field Value",
"add": "Add"
}
},
"result": {
"items": "Items",
"results": "Results",
"page": "Page",
"empty": "No Items Found",
"prev": "Prev",
"next": "Next",
"first": "First",
"last": "Last"
},
"tips": "Tips",
"tips_dropdown": {
"search_tips": "Search Tips",
"tips1": "Location and label filters use the 'OR' operation. If more than one is selected only one will be required for a match.",
"tips2": "Searches prefixed with '#' will query for a asset ID (example '#000-001')",
"tips3": "Field filters use the 'OR' operation. If more than one is selected only one will be required for a match."
}
},
"default": {
"welcome": "Welcome",
"create": "Create",
"sign_out": "Sign Out",
"dropdown": {
"item_asset": "Item / Asset",
"location": "Location",
"label": "Label"
},
"nav": {
"home": "Home",
"locations": "Locations",
"search": "Search",
"profile": "Profile",
"tools": "Tools"
}
},
"profile": {
"user": {
"title": "User Profile",
"desp": "Invite users, and manage your account.",
"openPassChange": "Change Password",
"generateToken": "Generate Invite Link"
},
"notifier": {
"title": "Notifiers",
"desp": "Get notifications for up coming maintenance reminders.",
"active": "Active",
"inactive": "Inactive",
"create": "Create",
"created": "Created"
},
"group": {
"title": "Group Settings",
"desp": "Shared Group Settings. You may need to refresh your browser for some settings to apply.",
"currency_format": "Currency Format",
"example": "Example",
"update_group": "Update Group",
"language":"Language"
},
"theme": {
"title": "Theme Settings",
"desp": "Theme settings are stored in your browser's local storage. You can change the theme at any time. If you're having trouble setting your theme try refreshing your browser."
},
"account": {
"title": "Delete Account",
"desp": "Delete your account and all its associated data.",
"delete_account": "Delete Account"
},
"passwordChangeDialog": {
"title": "Change Password",
"current": "Current Password",
"new": "New Password",
"submit": "Submit"
},
"notifierDialog": {
"title": "Notifier",
"name": "Name",
"url": "URL",
"enable": "Enabled",
"test": "Test",
"submit": "Submit"
}
},
"footer": {
"version": "Version",
"build": "Build"
},
"tools": {
"report": {
"title": "Reports",
"desp": "Generate different reports for your inventory.",
"asset": {
"title": "Asset ID Labels",
"desp": "Generates a printable PDF of labels for a range of Asset ID. These are not specific to your inventory so you are able to print labels ahead of time and apply them to your inventory when you receive them.",
"button": "Label Generator"
},
"bom": {
"title": "Bill of Materials",
"desp": "Generates a TSV (Tab Separated Values) file that can be imported into a spreadsheet program. This is a summary of your inventory with basic item and pricing information.",
"button": "Generate BOM"
}
},
"import_export": {
"title": "Import / Export",
"desp": "Import and export your inventory to and from a CSV file. This is useful for migrating your inventory to a new instance of Homebox.",
"import": {
"title": "Import Inventory",
"desp": "Imports the standard CSV format for Homebox. This will not overwrite any existing items in your inventory. It will only add new items."
},
"export": {
"title": "Export Inventory",
"desp": "Exports the standard CSV format for Homebox. This will export all items in your inventory."
}
},
"inventory": {
"title": "Inventory Actions",
"desp": "Apply Actions to your inventory in bulk. These are irreversible actions. Be careful.",
"ensureAssetIDs": {
"title": "Ensure Asset IDs",
"desp": "Ensures that all items in your inventory have a valid asset_id field. This is done by finding the highest current asset_id field in the database and applying the next value to each item that has an unset asset_id field. This is done in order of the created_at field."
},
"ensureImportRefs": {
"title": "Ensures Import Refs",
"desp": "Ensures that all items in your inventory have a valid import_ref field. This is done by randomly generating a 8 character string for each item that has an unset import_ref field."
},
"resetItemDateTimes": {
"title": "Zero Item Date Times",
"desp": "Resets the time value for all date time fields in your inventory to the beginning of the date. This is to fix a bug that was introduced early on in the development of the site that caused the time value to be stored with the time which caused issues with date fields displaying accurate values."
},
"setPrimaryPhotos": {
"title": "Set Primary Photos",
"desp": "In version v0.10.0 of Homebox, the primary image field was added to attachments of type photo. This action will set the primary image field to the first image in the attachments array in the database, if it is not already set."
}
}
}
}

317
frontend/lang/zh-CN.json Normal file
View file

@ -0,0 +1,317 @@
{
"index": {
"login": "登录",
"login_email": "邮箱",
"login_password": "密码",
"login_remember": "记住我",
"register": "注册",
"register_email": "邮箱",
"register_username": "用户名",
"register_password": "密码",
"slogon": "跟踪,组织并管理你的物品",
"refuse":"禁止注册"
},
"home": {
"quick_statistics": "快速统计",
"recently_added": "最近添加",
"storage_locations": "存储位置",
"labels": "标签"
},
"label": {
"create": {
"title": "创建标签",
"name": "标签名称",
"desp": "标签描述",
"button1": "创建",
"button2": "创建并添加下一个",
"tips1": "使用",
"tips2": "去创建并添加下一个"
},
"update": {
"title": "更新标签",
"name": "标签名称",
"desp": "标签描述",
"button": "更新"
},
"edit": {
"created": "已创建",
"edit_button": "编辑",
"delete_button": "删除"
}
},
"location": {
"selectable": {
"parent": "上级位置"
},
"child_locations": "下级位置",
"create": {
"title": "创建位置",
"name": "位置名称",
"desp": "位置描述",
"button1": "创建",
"button2": "创建并添加下一个",
"tips1": "使用",
"tips2": "去创建并添加下一个"
},
"update": {
"title": "更新位置",
"name": "位置名称",
"desp": "位置描述",
"button": "更新"
},
"edit": {
"created": "已创建",
"edit_button": "编辑",
"delete_button": "删除"
}
},
"item": {
"selectable": {
"title": "物品",
"card": "卡片",
"table": "表格",
"empty": "没有相关物品"
},
"edit":{
"editState":{
"title":"附件编辑",
"attachment":{
"title":"附件标题",
"type":"附件类型"
},
"tips":"此选项仅适用于照片。只能有一张照片是主照片。如果选择此选项,则当前的主照片(如果有的话)将被取消选中。",
"button":"更新"
},
"advanced":"高级设置",
"save_button":"保存",
"delete_button":"删除",
"details":{
"title":"详细信息",
"labels":"标签",
"parent":"上级物品"
},
"custom":{
"title":"自定义字段",
"name":"名称",
"value":"值",
"button":"添加"
},
"attachments":{
"title":"附件",
"tips":"对附件的更改将立即保存",
"dropZone":{
"photo":"图片",
"warranty":"保修",
"manual":"手册",
"attachment":"附件",
"receipt":"收据",
"tips":"向此处拖放文件或单击以选择文件"
}
},
"purchase":{
"title":"购买信息"
},
"warranty":{
"title":"保修信息"
},
"sold":{
"title":"销售信息"
}
},
"create": {
"title": "创建物品",
"name": "物品名称",
"desp": "物品描述",
"label":"标签",
"button1": "创建",
"button2": "创建并添加下一个",
"tips1": "使用",
"tips2": "去创建并添加下一个"
},
"new":{
"title":"在你的物品清单中添加一个物品",
"info":"基本信息",
"name":"名称",
"desp":"描述",
"product":{
"button":"产品信息",
"serialNumber":"序列号SN",
"modelNumber":"型号",
"manufacturer":"制造商"
},
"purchase":{
"button":"购买信息",
"purchaseTime":"购买时间",
"purchasePrice":"购买价格",
"purchaseFrom":"购买来源"
},
"sold":{
"button":"销售信息",
"soldTime":"销售时间",
"soldPrice":"销售价格",
"soldTo":"购买方",
"soldNotes":"销售备注"
},
"extras":{
"button":"附属设备",
"notes":"记录"
}
}
},
"assets":{
"title":"这个资产ID与多个物品关联"
},
"items": {
"querying_Asset_ID_Number": "查询资产ID",
"query": {
"search": "搜索",
"locations": "位置",
"labels": "标签",
"options": "选项",
"options_dropdown": {
"includeArchived": "包括已归档物品",
"fieldSelector": "字段选择",
"reset": "重置"
},
"fieldSelector_dropdown": {
"custom_fields": "自定义字段",
"field": "字段",
"field_value": "值",
"add": "添加"
}
},
"result": {
"items": "物品",
"results": "结果",
"page": "页码",
"empty": "未找到物品",
"prev": "上一页",
"next": "下一页",
"first": "第一页",
"last": "最后一页"
},
"tips": "提示",
"tips_dropdown": {
"search_tips": "搜索提示",
"tips1": "位置过滤器和标签过滤器使用 '或' 操作. 如果存在多个符合的选项,则只需一个即可进行匹配。",
"tips2": "以“#”为前缀的搜索将查询资产ID(例如“#000-001”)。",
"tips3": "字段过滤器使用“或”操作。如果存在多个符合的选项,则只需一个即可进行匹配。"
}
},
"default": {
"welcome": "欢迎",
"create": "创建",
"sign_out": "退出",
"dropdown": {
"item_asset": "物品/资产",
"location": "位置",
"label": "标签"
},
"nav": {
"home": "主页",
"locations": "位置",
"search": "搜索",
"profile": "配置",
"tools": "工具"
}
},
"profile": {
"user": {
"title": "用户配置",
"desp": "邀请用户并管理你的账户。",
"openPassChange": "修改密码",
"generateToken": "生成邀请链接"
},
"notifier": {
"title": "通知",
"desp": "获取即将到来的维护提醒的通知。",
"active": "启用",
"inactive": "禁用",
"create": "创建",
"created": "已创建"
},
"group": {
"title": "群组设置",
"desp": "共享群组设置,您可能需要刷新浏览器才能应用某些设置。",
"currency_format": "当前格式",
"example": "例",
"update_group": "保存",
"language":"语言"
},
"theme": {
"title": "主题设置",
"desp": "主题设置存储在浏览器的本地存储中。您可以随时更改主题。如果你在设置主题时遇到问题,可以尝试刷新浏览器。"
},
"account": {
"title": "删除账户",
"desp": "删除您的帐户及其所有相关数据。",
"delete_account": "删除"
},
"passwordChangeDialog": {
"title": "修改密码",
"current": "当前密码",
"new": "新密码",
"submit": "提交"
},
"notifierDialog": {
"title": "通知",
"name": "名称",
"url": "URL",
"enable": "启用",
"test": "测试",
"submit": "提交"
}
},
"footer": {
"version": "版本",
"build": "构建号"
},
"tools": {
"report": {
"title": "报告",
"desp": "为您的库存生成不同的报告。",
"asset": {
"title": "资产ID标签",
"desp": "生成资产ID标签的可打印PDF。这些标签不是特定于您的库存所以您可以提前打印标签并在收到资产时将其应用于您的库存。",
"button": "标签生成"
},
"bom": {
"title": "物料清单",
"desp": "生成一个TSV(Tab Separated Values)文件,可以导入到电子表格程序。这是一个包含基本项目和价格信息的库存摘要。",
"button": "生成BOM"
}
},
"import_export": {
"title": "导入 / 导出",
"desp": "在CSV文件中导入和导出您的库存。这对于将您的库存迁移到Homebox的新实例非常有用。",
"import": {
"title": "导入库存",
"desp": "为Homebox导入标准CSV格式文件。这不会覆盖您的库存中的任何现有项目。它只会添加新项目。"
},
"export": {
"title": "导出库存",
"desp": "为Homebox导出标准CSV格式文件。这将导出您的库存中的所有项目。"
}
},
"inventory": {
"title": "库存操作",
"desp": "批量对库存应用操作。这些都是不可逆转的行为。(慎重)",
"ensureAssetIDs": {
"title": "维护 Asset IDs",
"desp": "确保库存中的所有物品都有一个有效的asset_id字段。这是通过在数据库中查找当前最高的asset_id字段并将下一个值应用于具有未设置的asset_id字段的每个项来完成的。这是按照created_at字段的顺序完成的。"
},
"ensureImportRefs": {
"title": "维护 Import Refs",
"desp": "确保库存中的所有项目都有一个有效的import_ref字段。这是通过为每个具有未设置的import_ref字段的项目随机生成一个8个字符的字符串来实现的。"
},
"resetItemDateTimes": {
"title": "重置日期",
"desp": "将库存中所有日期时间字段的时间值重置为日期的开始。这是为了修复在网站开发早期引入的一个错误,该错误导致时间值与时间一起存储,从而导致日期字段显示准确值的问题。"
},
"setPrimaryPhotos": {
"title": "设置主图",
"desp": "在Homebox v0.10.0版本中主图像字段被添加到photo类型的附件中。此操作将主图像字段设置为数据库中附件数组中的第一个图像(如果尚未设置)。"
}
}
}
}

View file

@ -39,7 +39,7 @@
<div class="w-60 py-5 md:py-10 bg-base-200 flex flex-grow-1 flex-col">
<div class="space-y-8">
<div class="flex flex-col items-center gap-4">
<p>Welcome, {{ username }}</p>
<p>{{ $t("default.welcome") }}, {{ username }}</p>
<NuxtLink class="avatar placeholder" to="/home">
<div class="bg-base-300 text-neutral-content rounded-full w-24 p-4">
<AppLogo />
@ -53,12 +53,12 @@
<span>
<Icon name="mdi-plus" class="mr-1 -ml-1" />
</span>
Create
{{ $t("default.create") }}
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40">
<li v-for="btn in dropdown" :key="btn.name">
<button @click="btn.action">
{{ btn.name }}
{{ $t(btn.name) }}
</button>
</li>
</ul>
@ -75,7 +75,7 @@
}"
>
<Icon :name="n.icon" class="h-6 w-6 mr-4" />
{{ n.name }}
{{ $t(n.name) }}
</NuxtLink>
</li>
</ul>
@ -83,7 +83,9 @@
</div>
<!-- Bottom -->
<button class="mt-auto mx-2 hover:bg-base-300 p-3 rounded-btn" @click="logout">Sign Out</button>
<button class="mt-auto mx-2 hover:bg-base-300 p-3 rounded-btn" @click="logout">
{{ $t("default.sign_out") }}
</button>
</div>
</div>
</div>
@ -108,19 +110,19 @@
const dropdown = [
{
name: "Item / Asset",
name: "default.dropdown.item_asset",
action: () => {
modals.item = true;
},
},
{
name: "Location",
name: "default.dropdown.location",
action: () => {
modals.location = true;
},
},
{
name: "Label",
name: "default.dropdown.label",
action: () => {
modals.label = true;
},
@ -141,35 +143,35 @@
icon: "mdi-home",
active: computed(() => route.path === "/home"),
id: 0,
name: "Home",
name: "default.nav.home",
to: "/home",
},
{
icon: "mdi-file-tree",
id: 4,
active: computed(() => route.path === "/locations"),
name: "Locations",
name: "default.nav.locations",
to: "/locations",
},
{
icon: "mdi-magnify",
id: 3,
active: computed(() => route.path === "/items"),
name: "Search",
name: "default.nav.search",
to: "/items",
},
{
icon: "mdi-account",
id: 1,
active: computed(() => route.path === "/profile"),
name: "Profile",
name: "default.nav.profile",
to: "/profile",
},
{
icon: "mdi-cog",
id: 6,
active: computed(() => route.path === "/tools"),
name: "Tools",
name: "default.nav.tools",
to: "/tools",
},
];

View file

@ -3,7 +3,14 @@ import { defineNuxtConfig } from "nuxt/config";
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
ssr: false,
modules: ["@nuxtjs/tailwindcss", "@pinia/nuxt", "@vueuse/nuxt", "@vite-pwa/nuxt", "./nuxt.proxyoverride.ts"],
modules: [
"@nuxtjs/tailwindcss",
"@nuxtjs/i18n",
"@pinia/nuxt",
"@vueuse/nuxt",
"@vite-pwa/nuxt",
"./nuxt.proxyoverride.ts",
],
nitro: {
devProxy: {
"/api": {
@ -52,4 +59,29 @@ export default defineNuxtConfig({
],
},
},
i18n: {
strategy: "no_prefix",
lazy: true,
langDir: "lang",
defaultLocale: "en",
detectBrowserLanguage: {
useCookie: true,
cookieKey: "i18n_redirected",
redirectOn: "root",
},
locales: [
{
code: "en",
name: "English",
iso: "en-US",
file: "en-US.json",
},
{
code: "zh",
name: "简体中文",
iso: "zh-Hans",
file: "zh-CN.json",
},
],
},
});

View file

@ -16,6 +16,7 @@
"devDependencies": {
"@faker-js/faker": "^8.0.0",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@nuxtjs/i18n": "^8.1.1",
"@types/dompurify": "^3.0.0",
"@types/markdown-it": "^13.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",

View file

@ -33,7 +33,7 @@
<template>
<BaseContainer>
<section v-if="!pending">
<BaseSectionHeader class="mb-5"> This Asset Id is associated with multiple items</BaseSectionHeader>
<BaseSectionHeader class="mb-5"> {{ $t("assets.title") }}</BaseSectionHeader>
<div class="grid gap-2 grid-cols-1 sm:grid-cols-2">
<ItemCard v-for="item in items" :key="item.id" :item="item" />
</div>

View file

@ -28,14 +28,14 @@
<div>
<BaseContainer class="flex flex-col gap-12 pb-16">
<section>
<Subtitle> Quick Statistics </Subtitle>
<Subtitle> {{$t("home.quick_statistics")}} </Subtitle>
<div class="grid grid-cols-2 gap-2 md:grid-cols-4 md:gap-6">
<StatCard v-for="(stat, i) in stats" :key="i" :title="stat.label" :value="stat.value" :type="stat.type" />
</div>
</section>
<section>
<Subtitle> Recently Added </Subtitle>
<Subtitle> {{$t("home.recently_added")}} </Subtitle>
<BaseCard v-if="breakpoints.lg">
<ItemViewTable :items="itemTable.items" />
@ -46,14 +46,14 @@
</section>
<section>
<Subtitle> Storage Locations </Subtitle>
<Subtitle> {{$t("home.storage_locations")}} </Subtitle>
<div class="grid grid-cols-1 sm:grid-cols-2 card md:grid-cols-3 gap-4">
<LocationCard v-for="location in locations" :key="location.id" :location="location" />
</div>
</section>
<section>
<Subtitle> Labels </Subtitle>
<Subtitle> {{$t("home.labels")}} </Subtitle>
<div class="flex gap-4 flex-wrap">
<LabelChip v-for="label in labels" :key="label.id" size="lg" :label="label" class="shadow-md" />
</div>

View file

@ -111,6 +111,12 @@
}
const [registerForm, toggleLogin] = useToggle();
// i18n Lang Switcher
const { locale, locales, setLocale } = useI18n()
const availableLocales = computed(() => {
return locales.value.filter(i => i.code !== locale.value);
});
</script>
<template>
@ -137,7 +143,7 @@
<AppLogo class="w-12 -mb-4" />
x
</h2>
<p class="ml-1 text-lg text-base-content/50">Track, Organize, and Manage your Things.</p>
<p class="ml-1 text-lg text-base-content/50">{{ $t("index.slogon") }}</p>
</div>
<div class="flex mt-6 sm:mt-0 gap-4 ml-auto text-neutral-content">
<a class="tooltip" data-tip="Project Github" href="https://github.com/hay-kot/homebox" target="_blank">
@ -152,6 +158,14 @@
<a href="https://hay-kot.github.io/homebox/" class="tooltip" data-tip="Read The Docs" target="_blank">
<Icon name="mdi-folder" class="h-8 w-8" />
</a>
<a
v-for="locale in availableLocales"
:key="locale.code"
href="#"
@click.prevent.stop="setLocale(locale.code)"
>
{{ locale.name }}
</a>
</div>
</header>
<div class="grid p-6 sm:place-items-center min-h-[50vh]">
@ -162,17 +176,17 @@
<div class="card-body">
<h2 class="card-title text-2xl align-center">
<Icon name="heroicons-user" class="mr-1 w-7 h-7" />
Register
{{ $t("index.register") }}
</h2>
<FormTextField v-model="email" label="Set your email?" />
<FormTextField v-model="username" label="What's your name?" />
<FormTextField v-model="email" :label="$t('index.register_email')" />
<FormTextField v-model="username" :label="$t('index.register_username')" />
<div v-if="!(groupToken == '')" class="pt-4 pb-1 text-center">
<p>You're Joining an Existing Group!</p>
<button type="button" class="text-xs underline" @click="groupToken = ''">
Don't Want To Join a Group?
</button>
</div>
<FormPassword v-model="password" label="Set your password" />
<FormPassword v-model="password" :label="$t('index.register_password')" />
<PasswordScore v-model:valid="canRegister" :password="password" />
<div class="card-actions justify-end">
<button
@ -181,7 +195,7 @@
:class="loading ? 'loading' : ''"
:disabled="loading || !canRegister"
>
Register
{{ $t("index.register") }}
</button>
</div>
</div>
@ -192,17 +206,17 @@
<div class="card-body">
<h2 class="card-title text-2xl align-center">
<Icon name="heroicons-user" class="mr-1 w-7 h-7" />
Login
{{ $t("index.login") }}
</h2>
<template v-if="status && status.demo">
<p class="text-xs italic text-center">This is a demo instance</p>
<p class="text-xs text-center"><b>Email</b> demo@example.com</p>
<p class="text-xs text-center"><b>Password</b> demo</p>
</template>
<FormTextField v-model="email" label="Email" />
<FormPassword v-model="loginPassword" label="Password" />
<FormTextField v-model="email" :label="$t('index.login_email')" />
<FormPassword v-model="loginPassword" :label="$t('index.login_password')" />
<div class="max-w-[140px]">
<FormCheckbox v-model="remember" label="Remember Me" />
<FormCheckbox v-model="remember" :label="$t('index.login_remember')" />
</div>
<div class="card-actions justify-end">
<button
@ -211,7 +225,7 @@
:class="loading ? 'loading' : ''"
:disabled="loading"
>
Login
{{ $t("index.login") }}
</button>
</div>
</div>
@ -229,18 +243,18 @@
<Icon v-else name="mdi-login" class="w-5 h-5 swap-off" />
<Icon name="mdi-arrow-right" class="w-5 h-5 swap-on" />
</template>
{{ registerForm ? "Login" : "Register" }}
{{ registerForm ? $t("index.login") : $t("index.register") }}
</BaseButton>
<p v-else class="text-base-content italic text-sm inline-flex items-center gap-2">
<Icon name="mdi-lock" class="w-4 h-4 inline-block" />
Registration Disabled
{{ $t("index.refus") }}
</p>
</div>
</div>
</div>
</div>
<footer v-if="status" class="mt-auto text-center w-full bottom-0 pb-4">
<p class="text-center text-sm">Version: {{ status.build.version }} ~ Build: {{ status.build.commit }}</p>
<p class="text-center text-sm">{{ $t("footer.version") }}: {{ status.build.version }} ~ {{ $t("footer.build") }}: {{ status.build.commit }}</p>
</footer>
</div>
</template>

View file

@ -412,12 +412,12 @@
<template>
<div v-if="item" class="pb-8">
<BaseModal v-model="editState.modal">
<template #title> Attachment Edit </template>
<template #title> {{ $t("item.edit.editState.title") }} </template>
<FormTextField v-model="editState.title" label="Attachment Title" />
<FormTextField v-model="editState.title" :label="$t('item.edit.editState.attachment.title')" />
<FormSelect
v-model:value="editState.type"
label="Attachment Type"
:label="$t('item.edit.editState.attachment.type')"
value-key="value"
name="text"
:items="attachmentOpts"
@ -426,12 +426,11 @@
<input v-model="editState.primary" type="checkbox" class="checkbox" />
<p class="text-sm">
<span class="font-semibold">Primary Photo</span>
This options is only available for photos. Only one photo can be primary. If you select this option, the
current primary photo, if any will be unselected.
{{ $t("item.edit.editState.tips") }}
</p>
</div>
<div class="modal-action">
<BaseButton :loading="editState.loading" @click="updateAttachment"> Update </BaseButton>
<BaseButton :loading="editState.loading" @click="updateAttachment"> {{ $t("item.edit.editState.button") }} </BaseButton>
</div>
</BaseModal>
@ -440,36 +439,36 @@
<div class="mr-auto tooltip tooltip-right" data-tip="Show Advanced View Options">
<label class="label cursor-pointer mr-auto">
<input v-model="preferences.editorAdvancedView" type="checkbox" class="toggle toggle-primary" />
<span class="label-text ml-4"> Advanced </span>
<span class="label-text ml-4"> {{ $t("item.edit.advanced") }} </span>
</label>
</div>
<BaseButton size="sm" @click="saveItem">
<template #icon>
<Icon name="mdi-content-save-outline" />
</template>
Save
{{ $t("item.edit.save_button") }}
</BaseButton>
<BaseButton class="btn btn-sm btn-error" @click="deleteItem()">
<Icon name="mdi-delete" class="mr-2" />
Delete
{{ $t("item.edit.delete_button") }}
</BaseButton>
</div>
<div v-if="!requestPending" class="space-y-6">
<BaseCard class="overflow-visible">
<template #title> Edit Details </template>
<template #title> {{ $t("item.edit.details.title") }} </template>
<template #title-actions>
<div class="flex flex-wrap justify-between items-center mt-2 gap-4"></div>
</template>
<div class="px-5 pt-2 border-t mb-6 grid md:grid-cols-2 gap-4">
<LocationSelector v-model="item.location" />
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
<FormMultiselect v-model="item.labels" :label="$t('item.edit.details.labels')" :items="labels ?? []" />
<Autocomplete
v-if="preferences.editorAdvancedView"
v-model="parent"
v-model:search="query"
:items="results"
item-text="name"
label="Parent Item"
:label="$t('item.edit.details.parent')"
no-results-text="Type to search..."
/>
</div>
@ -509,7 +508,7 @@
</BaseCard>
<BaseCard>
<template #title> Custom Fields </template>
<template #title> {{ $t("item.edit.custom.title") }} </template>
<div class="px-5 border-t divide-y divide-gray-300 space-y-4">
<div
v-for="(field, idx) in item.fields"
@ -517,9 +516,9 @@
class="grid grid-cols-2 md:grid-cols-4 gap-2"
>
<!-- <FormSelect v-model:value="field.type" label="Field Type" :items="fieldTypes" value-key="value" /> -->
<FormTextField v-model="field.name" label="Name" />
<FormTextField v-model="field.name" :label="$t('item.edit.custom.name')" />
<div class="flex items-end col-span-3">
<FormTextField v-model="field.textValue" label="Value" />
<FormTextField v-model="field.textValue" :label="$t('item.edit.custom.value')" />
<div class="tooltip" data-tip="Delete">
<button class="btn btn-sm btn-square mb-2 ml-2" @click="item.fields.splice(idx, 1)">
<Icon name="mdi-delete" />
@ -529,7 +528,7 @@
</div>
</div>
<div class="px-5 pb-4 mt-4 flex justify-end">
<BaseButton size="sm" @click="addField"> Add </BaseButton>
<BaseButton size="sm" @click="addField"> {{ $t("item.edit.custom.button") }} </BaseButton>
</div>
</BaseCard>
@ -539,16 +538,16 @@
class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg"
>
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Attachments</h3>
<p class="text-xs">Changes to attachments will be saved immediately</p>
<h3 class="text-lg font-medium leading-6">{{ $t("item.edit.attachments.title") }}</h3>
<p class="text-xs">{{ $t("item.edit.attachments.tips") }}</p>
</div>
<div class="border-t border-gray-300 p-4">
<div v-if="attDropZoneActive" class="grid grid-cols-4 gap-4">
<DropZone @drop="dropPhoto"> Photo </DropZone>
<DropZone @drop="dropWarranty"> Warranty </DropZone>
<DropZone @drop="dropManual"> Manual </DropZone>
<DropZone @drop="dropAttachment"> Attachment </DropZone>
<DropZone @drop="dropReceipt"> Receipt </DropZone>
<DropZone @drop="dropPhoto"> {{ $t("item.edit.attachments.dropZone.photo") }} </DropZone>
<DropZone @drop="dropWarranty"> {{ $t("item.edit.attachments.dropZone.warranty") }} </DropZone>
<DropZone @drop="dropManual"> {{ $t("item.edit.attachments.dropZone.manual") }} </DropZone>
<DropZone @drop="dropAttachment"> {{ $t("item.edit.attachments.dropZone.attachment") }} </DropZone>
<DropZone @drop="dropReceipt"> {{ $t("item.edit.attachments.dropZone.receipt") }} </DropZone>
</div>
<button
v-else
@ -556,7 +555,7 @@
@click="clickUpload"
>
<input ref="refAttachmentInput" hidden type="file" @change="uploadImage" />
<p>Drag and drop files here or click to select files</p>
<p>{{ $t("item.edit.attachments.dropZone.tips") }}</p>
</button>
</div>
@ -592,7 +591,7 @@
<div v-if="preferences.editorAdvancedView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Purchase Details</h3>
<h3 class="text-lg font-medium leading-6">{{ $t("item.edit.purchase.title") }}</h3>
</div>
<div class="border-t border-gray-300 sm:p-0">
<div
@ -634,7 +633,7 @@
<div v-if="preferences.editorAdvancedView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Warranty Details</h3>
<h3 class="text-lg font-medium leading-6">{{ $t("item.edit.warranty.title") }}</h3>
</div>
<div class="border-t border-gray-300 sm:p-0">
<div
@ -676,7 +675,7 @@
<div v-if="preferences.editorAdvancedView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Sold Details</h3>
<h3 class="text-lg font-medium leading-6">{{ $t("item.edit.sold.title") }}</h3>
</div>
<div class="border-t border-gray-300 sm:p-0">
<div v-for="field in soldFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">

View file

@ -39,57 +39,65 @@
<template>
<BaseContainer cmp="section">
<BaseSectionHeader> Add an Item To Your Inventory </BaseSectionHeader>
<BaseSectionHeader> {{ $t("item.new.title") }} </BaseSectionHeader>
<form class="max-w-3xl mx-auto my-5 space-y-6" @submit.prevent="submit">
<div class="divider collapse-title px-0 cursor-pointer">Required Information</div>
<div class="divider collapse-title px-0 cursor-pointer">{{ $t("item.new.info") }}</div>
<div class="bg-base-200 card">
<div class="card-body">
<FormTextField v-model="form.name" label="Name" />
<FormTextArea v-model="form.description" label="Description" limit="1000" />
<FormTextField v-model="form.name" :label="$t('item.new.name')" />
<FormTextArea v-model="form.description" :label="$t('item.new.desp')" limit="1000" />
</div>
</div>
<div class="divider">
<button class="btn btn-sm" @click="show.identification = !show.identification">Product Information</button>
<button class="btn btn-sm" @click="show.identification = !show.identification">
{{ $t("item.new.product.button") }}
</button>
</div>
<div v-if="show.identification" class="card bg-base-200">
<div class="card-body grid md:grid-cols-2">
<FormTextField v-model="form.serialNumber" label="Serial Number" />
<FormTextField v-model="form.modelNumber" label="Model Number" />
<FormTextField v-model="form.manufacturer" label="Manufacturer" />
<FormTextField v-model="form.serialNumber" :label="$t('item.new.product.serialNumber')" />
<FormTextField v-model="form.modelNumber" :label="$t('item.new.product.modelNumber')" />
<FormTextField v-model="form.manufacturer" :label="$t('item.new.product.manufacturer')" />
</div>
</div>
<div class="">
<button class="btn btn-sm" @click="show.purchase = !show.purchase">Purchase Information</button>
<button class="btn btn-sm" @click="show.purchase = !show.purchase">
{{ $t("item.new.purchase.button") }}
</button>
<div class="divider"></div>
</div>
<div v-if="show.purchase" class="card bg-base-200">
<div class="card-body grid md:grid-cols-2">
<FormTextField v-model="form.purchaseTime" label="Purchase Time" />
<FormTextField v-model="form.purchasePrice" label="Purchase Price" />
<FormTextField v-model="form.purchaseFrom" label="Purchase From" />
<FormTextField v-model="form.purchaseTime" :label="$t('item.new.purchase.purchaseTime')" />
<FormTextField v-model="form.purchasePrice" :label="$t('item.new.purchase.purchasePrice')" />
<FormTextField v-model="form.purchaseFrom" :label="$t('item.new.purchase.purchaseFrom')" />
</div>
</div>
<div class="divider">
<button class="btn btn-sm" @click="show.sold = !show.sold">Sold Information</button>
<button class="btn btn-sm" @click="show.sold = !show.sold">
{{ $t("item.new.sold.button") }}
</button>
</div>
<div v-if="show.sold" class="card bg-base-200">
<div class="card-body">
<div class="grid md:grid-cols-2 gap-2">
<FormTextField v-model="form.soldTime" label="Sold Time" />
<FormTextField v-model="form.soldPrice" label="Sold Price" />
<FormTextField v-model="form.soldTo" label="Sold To" />
<FormTextField v-model="form.soldTime" :label="$t('item.new.sold.soldTime')" />
<FormTextField v-model="form.soldPrice" :label="$t('item.new.sold.soldPrice')" />
<FormTextField v-model="form.soldTo" :label="$t('item.new.sold.soldTo')" />
</div>
<FormTextArea v-model="form.soldNotes" label="Sold Notes" limit="1000" />
<FormTextArea v-model="form.soldNotes" :label="$t('item.new.sold.soldNotes')" limit="1000" />
</div>
</div>
<div class="divider">
<button class="btn btn-sm" @click="show.extras = !show.extras">Extras</button>
<button class="btn btn-sm" @click="show.extras = !show.extras">
{{ $t("item.new.extras.button") }}
</button>
</div>
<div v-if="show.extras" class="card bg-base-200">
<div class="card-body">
<FormTextArea v-model="form.notes" label="Notes" limit="1000" />
<FormTextArea v-model="form.notes" :label="$t('item.new.extras.notes')" limit="1000" />
</div>
</div>
</form>

View file

@ -314,7 +314,7 @@
<div class="w-full">
<FormTextField v-model="query" placeholder="Search" />
<div v-if="byAssetId" class="text-sm pl-2 pt-2">
<p>Querying Asset ID Number: {{ parsedAssetId }}</p>
<p>{{ $t("items.querying_Asset_ID_Number") }}: {{ parsedAssetId }}</p>
</div>
</div>
<BaseButton class="btn-block md:w-auto" @click.prevent="submit">
@ -322,12 +322,12 @@
<Icon v-if="loading" name="mdi-loading" class="animate-spin" />
<Icon v-else name="mdi-search" />
</template>
Search
{{ $t("items.query.search") }}
</BaseButton>
</div>
<div class="flex flex-wrap md:flex-nowrap gap-2 w-full py-2">
<SearchFilter v-model="selectedLocations" label="Locations" :options="locationFlatTree">
<SearchFilter v-model="selectedLocations" :label="$t('items.query.locations')" :options="locationFlatTree">
<template #display="{ item }">
<div>
<div class="flex w-full">
@ -339,52 +339,54 @@
</div>
</template>
</SearchFilter>
<SearchFilter v-model="selectedLabels" label="Labels" :options="labels" />
<SearchFilter v-model="selectedLabels" :label="$t('items.query.labels')" :options="labels" />
<div class="dropdown">
<label tabindex="0" class="btn btn-xs">Options</label>
<label tabindex="0" class="btn btn-xs"> {{ $t("items.query.options") }} </label>
<div
tabindex="0"
class="dropdown-content mt-1 max-h-72 p-4 w-64 overflow-auto shadow bg-base-100 rounded-md -translate-x-24"
>
<label class="label cursor-pointer mr-auto">
<input v-model="includeArchived" type="checkbox" class="toggle toggle-sm toggle-primary" />
<span class="label-text ml-4"> Include Archived Items </span>
<span class="label-text ml-4"> {{ $t("items.query.options_dropdown.includeArchived") }} </span>
</label>
<label class="label cursor-pointer mr-auto">
<input v-model="fieldSelector" type="checkbox" class="toggle toggle-sm toggle-primary" />
<span class="label-text ml-4"> Field Selector </span>
<span class="label-text ml-4"> {{ $t("items.query.options_dropdown.fieldSelector") }} </span>
</label>
<hr class="my-2" />
<BaseButton class="btn-block btn-sm" @click="reset"> Reset Search</BaseButton>
<BaseButton class="btn-block btn-sm" @click="reset">
{{ $t("items.query.options_dropdown.reset") }}
</BaseButton>
</div>
</div>
<div class="dropdown ml-auto dropdown-end">
<label tabindex="0" class="btn btn-xs">Tips</label>
<label tabindex="0" class="btn btn-xs"> {{ $t("items.tips") }} </label>
<div
tabindex="0"
class="dropdown-content mt-1 p-4 w-[325px] text-sm overflow-auto shadow bg-base-100 rounded-md"
>
<p class="text-base">Search Tips</p>
<p class="text-base">{{ $t("items.tips_dropdown.search_tips") }}</p>
<ul class="mt-1 list-disc pl-6">
<li>
Location and label filters use the 'OR' operation. If more than one is selected only one will be
required for a match.
{{ $t("items.tips_dropdown.tips1") }}
</li>
<li>Searches prefixed with '#'' will query for a asset ID (example '#000-001')</li>
<li>
Field filters use the 'OR' operation. If more than one is selected only one will be required for a
match.
{{ $t("items.tips_dropdown.tips2") }}
</li>
<li>
{{ $t("items.tips_dropdown.tips3") }}
</li>
</ul>
</div>
</div>
</div>
<div v-if="fieldSelector" class="py-4 space-y-2">
<p>Custom Fields</p>
<p>{{ $t("items.query.fieldSelector_dropdown.custom_fields") }}</p>
<div v-for="(f, idx) in fieldTuples" :key="idx" class="flex flex-wrap gap-2">
<div class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">Field</span>
<span class="label-text"> {{ $t("items.query.fieldSelector_dropdown.field") }} </span>
</label>
<select
v-model="fieldTuples[idx][0]"
@ -397,7 +399,7 @@
</div>
<div class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">Field Value</span>
<span class="label-text"> {{ $t("items.query.fieldSelector_dropdown.field_value") }} </span>
</label>
<select v-model="fieldTuples[idx][1]" class="select-bordered select" :items="fieldValuesCache[f[0]]">
<option v-for="v in fieldValuesCache[f[0]]" :key="v" :value="v">{{ v }}</option>
@ -411,38 +413,38 @@
<Icon name="mdi-trash" class="w-5 h-5" />
</button>
</div>
<BaseButton type="button" class="btn-sm mt-2" @click="() => fieldTuples.push(['', ''])"> Add</BaseButton>
<BaseButton type="button" class="btn-sm mt-2" @click="() => fieldTuples.push(['', ''])"> {{ $t("items.query.fieldSelector_dropdown.add") }} </BaseButton>
</div>
</div>
<section class="mt-10">
<BaseSectionHeader ref="itemsTitle"> Items </BaseSectionHeader>
<BaseSectionHeader ref="itemsTitle"> {{ $t("items.result.items") }} </BaseSectionHeader>
<p class="text-base font-medium flex items-center">
{{ total }} Results
<span class="text-base ml-auto"> Page {{ page }} of {{ totalPages }}</span>
{{ total }} {{ $t("items.result.results") }}
<span class="text-base ml-auto"> {{ $t("items.result.page") }} {{ page }} of {{ totalPages }}</span>
</p>
<div ref="cardgrid" class="grid mt-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<ItemCard v-for="item in items" :key="item.id" :item="item" />
<div class="hidden first:inline text-xl">No Items Found</div>
<div class="hidden first:inline text-xl">{{ $t("items.result.empty") }}</div>
</div>
<div v-if="items.length > 0 && (hasNext || hasPrev)" class="mt-10 flex gap-2 flex-col items-center">
<div class="flex">
<div class="btn-group">
<button :disabled="!hasPrev" class="btn text-no-transform" @click="prev">
<Icon class="mr-1 h-6 w-6" name="mdi-chevron-left" />
Prev
{{ $t("items.result.prev") }}
</button>
<button v-if="hasPrev" class="btn text-no-transform" @click="page = 1">First</button>
<button v-if="hasNext" class="btn text-no-transform" @click="page = totalPages">Last</button>
<button v-if="hasPrev" class="btn text-no-transform" @click="page = 1">{{ $t("items.result.first") }}</button>
<button v-if="hasNext" class="btn text-no-transform" @click="page = totalPages">{{ $t("items.result.last") }}</button>
<button :disabled="!hasNext" class="btn text-no-transform" @click="next">
Next
{{ $t("items.result.next") }}
<Icon class="ml-1 h-6 w-6" name="mdi-chevron-right" />
</button>
</div>
</div>
<p class="text-sm font-bold">Page {{ page }} of {{ totalPages }}</p>
<p class="text-sm font-bold">{{ $t("items.result.page") }} {{ page }} of {{ totalPages }}</p>
</div>
</section>
</BaseContainer>

View file

@ -91,12 +91,12 @@
<template>
<BaseContainer>
<BaseModal v-model="updateModal">
<template #title> Update Label </template>
<template #title> {{ $t("label.update.title") }} </template>
<form v-if="label" @submit.prevent="update">
<FormTextField v-model="updateData.name" :autofocus="true" label="Label Name" />
<FormTextArea v-model="updateData.description" label="Label Description" />
<FormTextField v-model="updateData.name" :autofocus="true" :label="$t('label.update.name')" />
<FormTextArea v-model="updateData.description" :label="$t('label.update.desp')" />
<div class="modal-action">
<BaseButton type="submit" :loading="updating"> Update </BaseButton>
<BaseButton type="submit" :loading="updating"> {{ $t("label.update.button") }} </BaseButton>
</div>
</form>
</BaseModal>
@ -116,7 +116,7 @@
</h1>
<div class="flex gap-1 flex-wrap text-xs">
<div>
Created
{{ $t("label.edit.created") }}
<DateTime :date="label?.createdAt" />
</div>
</div>
@ -126,12 +126,12 @@
<PageQRCode class="dropdown-left" />
<BaseButton size="sm" @click="openUpdate">
<Icon class="mr-1" name="mdi-pencil" />
Edit
{{ $t("label.edit.edit_button") }}
</BaseButton>
</div>
<BaseButton class="btn btn-sm" @click="confirmDelete()">
<Icon name="mdi-delete" class="mr-2" />
Delete
{{ $t("label.edit.delete_button") }}
</BaseButton>
</div>
</div>

View file

@ -106,13 +106,13 @@
<div>
<!-- Update Dialog -->
<BaseModal v-model="updateModal">
<template #title> Update Location </template>
<template #title> {{ $t("location.update.title") }} </template>
<form v-if="location" @submit.prevent="update">
<FormTextField v-model="updateData.name" :autofocus="true" label="Location Name" />
<FormTextArea v-model="updateData.description" label="Location Description" />
<FormTextField v-model="updateData.name" :autofocus="true" :label="$t('location.update.name')" />
<FormTextArea v-model="updateData.description" :label="$t('location.update.desp')" />
<LocationSelector v-model="parent" />
<div class="modal-action">
<BaseButton type="submit" :loading="updating"> Update </BaseButton>
<BaseButton type="submit" :loading="updating"> {{ $t("location.update.button") }} </BaseButton>
</div>
</form>
</BaseModal>
@ -140,7 +140,7 @@
</h1>
<div class="flex gap-1 flex-wrap text-xs">
<div>
Created
{{ $t("location.edit.created") }}
<DateTime :date="location?.createdAt" />
</div>
</div>
@ -150,12 +150,12 @@
<PageQRCode class="dropdown-left" />
<BaseButton size="sm" @click="openUpdate">
<Icon class="mr-1" name="mdi-pencil" />
Edit
{{ $t("location.edit.edit_button") }}
</BaseButton>
</div>
<BaseButton class="btn btn-sm" @click="confirmDelete()">
<Icon name="mdi-delete" class="mr-2" />
Delete
{{ $t("location.edit.delete_button") }}
</BaseButton>
</div>
</div>
@ -168,7 +168,7 @@
</section>
<section v-if="location && location.children.length > 0" class="mt-6">
<BaseSectionHeader class="mb-5"> Child Locations </BaseSectionHeader>
<BaseSectionHeader class="mb-5"> {{ $t("location.child_locations") }} </BaseSectionHeader>
<div class="grid gap-2 grid-cols-1 sm:grid-cols-3">
<LocationCard v-for="item in location.children" :key="item.id" :location="item" />
</div>

View file

@ -61,7 +61,7 @@
<template>
<BaseContainer class="mb-16">
<BaseSectionHeader> Locations </BaseSectionHeader>
<BaseSectionHeader>{{ $t("default.nav.locations") }} </BaseSectionHeader>
<BaseCard>
<div class="p-4">
<div class="flex justify-end mb-2">

View file

@ -299,11 +299,15 @@
<template>
<div>
<BaseModal v-model="passwordChange.dialog">
<template #title> Change Password </template>
<template #title> {{ $t("profile.passwordChangeDialog.title") }} </template>
<form @submit.prevent="changePassword">
<FormPassword v-model="passwordChange.current" label="Current Password" placeholder="" />
<FormPassword v-model="passwordChange.new" label="New Password" placeholder="" />
<FormPassword
v-model="passwordChange.current"
:label="$t('profile.passwordChangeDialog.current')"
placeholder=""
/>
<FormPassword v-model="passwordChange.new" :label="$t('profile.passwordChangeDialog.new')" placeholder="" />
<PasswordScore v-model:valid="passwordChange.isValid" :password="passwordChange.new" />
<div class="flex">
@ -313,26 +317,28 @@
:disabled="!passwordChange.isValid"
type="submit"
>
Submit
{{ $t("profile.passwordChangeDialog.submit") }}
</BaseButton>
</div>
</form>
</BaseModal>
<BaseModal v-model="notifierDialog">
<template #title> {{ notifier ? "Edit" : "Create" }} Notifier </template>
<template #title> {{ notifier ? "Edit" : "Create" }} {{ $t("profile.notifierDialog.title") }} </template>
<form @submit.prevent="createNotifier">
<template v-if="notifier">
<FormTextField v-model="notifier.name" label="Name" />
<FormTextField v-model="notifier.url" label="URL" />
<FormTextField v-model="notifier.name" :label="$t('profile.notifierDialog.name')" />
<FormTextField v-model="notifier.url" :label="$t('profile.notifierDialog.url')" />
<div class="max-w-[100px]">
<FormCheckbox v-model="notifier.isActive" label="Enabled" />
<FormCheckbox v-model="notifier.isActive" :label="$t('profile.notifierDialog.enable')" />
</div>
</template>
<div class="flex gap-2 justify-between mt-4">
<BaseButton :disabled="!(notifier && notifier.url)" type="button" @click="testNotifier"> Test </BaseButton>
<BaseButton type="submit"> Submit </BaseButton>
<BaseButton :disabled="!(notifier && notifier.url)" type="button" @click="testNotifier">
{{ $t("profile.notifierDialog.test") }}
</BaseButton>
<BaseButton type="submit"> {{ $t("profile.notifierDialog.submit") }} </BaseButton>
</div>
</form>
</BaseModal>
@ -342,8 +348,8 @@
<template #title>
<BaseSectionHeader>
<Icon name="mdi-account" class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> User Profile </span>
<template #description> Invite users, and manage your account. </template>
<span class="text-base-600"> {{ $t("profile.user.title") }} </span>
<template #description> {{ $t("profile.user.desp") }} </template>
</BaseSectionHeader>
</template>
@ -351,8 +357,8 @@
<div class="p-4">
<div class="flex gap-2">
<BaseButton size="sm" @click="openPassChange"> Change Password </BaseButton>
<BaseButton size="sm" @click="generateToken"> Generate Invite Link </BaseButton>
<BaseButton size="sm" @click="openPassChange"> {{ $t("profile.user.openPassChange") }} </BaseButton>
<BaseButton size="sm" @click="generateToken"> {{ $t("profile.user.generateToken") }} </BaseButton>
</div>
<div v-if="token" class="pt-4 flex items-center pl-1">
<CopyText class="mr-2 btn-primary btn btn-outline btn-square btn-sm" :text="tokenUrl" />
@ -369,8 +375,8 @@
<template #title>
<BaseSectionHeader>
<Icon name="mdi-megaphone" class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> Notifiers </span>
<template #description> Get notifications for up coming maintenance reminders </template>
<span class="text-base-600"> {{ $t("profile.notifier.title") }} </span>
<template #description> {{ $t("profile.notifier.desp") }} </template>
</BaseSectionHeader>
</template>
@ -393,11 +399,11 @@
</div>
<div class="flex justify-between py-1 flex-wrap text-sm">
<p>
<span v-if="n.isActive" class="badge badge-success"> Active </span>
<span v-else class="badge badge-error"> Inactive</span>
<span v-if="n.isActive" class="badge badge-success"> {{ $t("profile.notifier.active") }} </span>
<span v-else class="badge badge-error"> {{ $t("profile.notifier.inactive") }} </span>
</p>
<p>
Created
{{ $t("profile.notifier.created") }}
<DateTime format="relative" datetime-type="time" :date="n.createdAt" />
</p>
</div>
@ -405,7 +411,7 @@
</div>
<div class="p-4">
<BaseButton size="sm" @click="openNotifierDialog"> Create </BaseButton>
<BaseButton size="sm" @click="openNotifierDialog"> {{ $t("profile.notifier.create") }} </BaseButton>
</div>
</BaseCard>
@ -413,19 +419,19 @@
<template #title>
<BaseSectionHeader class="pb-0">
<Icon name="mdi-accounts" class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> Group Settings </span>
<span class="text-base-600"> {{ $t("profile.group.title") }} </span>
<template #description>
Shared Group Settings. You may need to refresh your browser for some settings to apply.
{{ $t("profile.group.desp") }}
</template>
</BaseSectionHeader>
</template>
<div v-if="group && currencies && currencies.length > 0" class="p-5 pt-0">
<FormSelect v-model="currency" label="Currency Format" :items="currencies" />
<p class="m-2 text-sm">Example: {{ currencyExample }}</p>
<FormSelect v-model="currency" :label="$t('profile.group.currency_format')" :items="currencies" />
<p class="m-2 text-sm">{{ $t("profile.group.example") }}: {{ currencyExample }}</p>
<div class="mt-4">
<BaseButton size="sm" @click="updateGroup"> Update Group </BaseButton>
<BaseButton size="sm" @click="updateGroup"> {{ $t("profile.group.update_group") }} </BaseButton>
</div>
</div>
</BaseCard>
@ -434,10 +440,9 @@
<template #title>
<BaseSectionHeader>
<Icon name="mdi-fill" class="mr-2 text-base-600" />
<span class="text-base-600"> Theme Settings </span>
<span class="text-base-600"> {{ $t("profile.theme.title") }} </span>
<template #description>
Theme settings are stored in your browser's local storage. You can change the theme at any time. If you're
having trouble setting your theme try refreshing your browser.
{{ $t("profile.theme.desp") }}
</template>
</BaseSectionHeader>
</template>
@ -485,17 +490,17 @@
<template #title>
<BaseSectionHeader>
<Icon name="mdi-delete" class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> Delete Account</span>
<template #description> Delete your account and all its associated data. </template>
<span class="text-base-600"> {{ $t("profile.account.title") }}</span>
<template #description> {{ $t("profile.account.desp") }} </template>
</BaseSectionHeader>
</template>
<div class="p-4 px-6 border-t-2 border-gray-300">
<BaseButton size="sm" class="btn-error" @click="deleteProfile"> Delete Account </BaseButton>
<BaseButton size="sm" class="btn-error" @click="deleteProfile"> {{ $t("profile.account.delete_account") }} </BaseButton>
</div>
</BaseCard>
</BaseContainer>
<footer v-if="status" class="text-center w-full bottom-0 pb-4">
<p class="text-center text-sm">Version: {{ status.build.version }} ~ Build: {{ status.build.commit }}</p>
<p class="text-center text-sm">{{ $t("footer.version") }}: {{ status.build.version }} ~ {{ $t("footer.build") }}: {{ status.build.commit }}</p>
</footer>
</div>
</template>

View file

@ -6,25 +6,23 @@
<template #title>
<BaseSectionHeader>
<Icon name="mdi-file-chart" class="mr-2 -mt-1" />
<span> Reports </span>
<template #description> Generate different reports for your inventory. </template>
<span> {{ $t("tools.report.title") }} </span>
<template #description> {{ $t("tools.report.desp") }} </template>
</BaseSectionHeader>
</template>
<div class="border-t px-6 pb-3 border-gray-300 divide-gray-300 divide-y">
<DetailAction @action="navigateTo('/reports/label-generator')">
<template #title>Asset ID Labels</template>
Generates a printable PDF of labels for a range of Asset ID. These are not specific to your inventory so you
are able to print labels ahead of time and apply them to your inventory when you receive them.
<template #title> {{ $t("tools.report.asset.title") }} </template>
{{ $t("tools.report.asset.desp") }}
<template #button>
Label Generator
{{ $t("tools.report.asset.button") }}
<Icon name="mdi-arrow-right" class="ml-2" />
</template>
</DetailAction>
<DetailAction @action="getBillOfMaterials()">
<template #title>Bill of Materials</template>
Generates a TSV (Tab Separated Values) file that can be imported into a spreadsheet program. This is a
summary of your inventory with basic item and pricing information.
<template #button> Generate BOM </template>
<template #title>{{ $t("tools.report.bom.title") }}</template>
{{ $t("tools.report.bom.desp") }}
<template #button> {{ $t("tools.report.bom.button") }} </template>
</DetailAction>
</div>
</BaseCard>
@ -32,22 +30,20 @@
<template #title>
<BaseSectionHeader>
<Icon name="mdi-database" class="mr-2 -mt-1" />
<span> Import / Export </span>
<span> {{ $t("tools.import_export.title") }} </span>
<template #description>
Import and export your inventory to and from a CSV file. This is useful for migrating your inventory to a
new instance of Homebox.
{{ $t("tools.import_export.desp") }}
</template>
</BaseSectionHeader>
</template>
<div class="border-t px-6 pb-3 border-gray-300 divide-gray-300 divide-y">
<DetailAction @action="modals.import = true">
<template #title>Import Inventory</template>
Imports the standard CSV format for Homebox. This will <b>not</b> overwrite any existing items in your
inventory. It will only add new items.
<template #title> {{ $t("tools.import_export.import.title") }} </template>
{{ $t("tools.import_export.import.desp") }}
</DetailAction>
<DetailAction @action="getExportTSV()">
<template #title>Export Inventory</template>
Exports the standard CSV format for Homebox. This will export all items in your inventory.
<template #title>{{ $t("tools.import_export.export.title") }}</template>
{{ $t("tools.import_export.export.desp") }}
</DetailAction>
</div>
</BaseCard>
@ -55,38 +51,32 @@
<template #title>
<BaseSectionHeader>
<Icon name="mdi-warning" class="mr-2 -mt-1" />
<span> Inventory Actions </span>
<span> {{ $t("tools.inventory.title") }} </span>
<template #description>
Apply Actions to your inventory in bulk. These are irreversible actions. <b>Be careful.</b>
{{ $t("tools.inventory.desp") }}
</template>
</BaseSectionHeader>
</template>
<div class="border-t px-6 pb-3 border-gray-300 divide-gray-300 divide-y">
<DetailAction @action="ensureAssetIDs">
<template #title>Ensure Asset IDs</template>
Ensures that all items in your inventory have a valid asset_id field. This is done by finding the highest
current asset_id field in the database and applying the next value to each item that has an unset asset_id
field. This is done in order of the created_at field.
<template #title> {{ $t("tools.inventory.ensureAssetIDs.title") }} </template>
{{ $t("tools.inventory.ensureAssetIDs.desp") }}
</DetailAction>
<DetailAction @action="ensureImportRefs">
<template #title>Ensures Import Refs</template>
Ensures that all items in your inventory have a valid import_ref field. This is done by randomly generating
a 8 character string for each item that has an unset import_ref field.
<template #title> {{ $t("tools.inventory.ensureImportRefs.title") }} </template>
{{ $t("tools.inventory.ensureImportRefs.desp") }}
</DetailAction>
<DetailAction @action="resetItemDateTimes">
<template #title> Zero Item Date Times</template>
Resets the time value for all date time fields in your inventory to the beginning of the date. This is to
fix a bug that was introduced early on in the development of the site that caused the time value to be
stored with the time which caused issues with date fields displaying accurate values.
<template #title> {{ $t("tools.inventory.resetItemDateTimes.title") }}</template>
{{ $t("tools.inventory.resetItemDateTimes.desp") }}
<a class="link" href="https://github.com/hay-kot/homebox/issues/236" target="_blank">
See Github Issue #236 for more details.
</a>
</DetailAction>
<DetailAction @action="setPrimaryPhotos">
<template #title> Set Primary Photos </template>
In version v0.10.0 of Homebox, the primary image field was added to attachments of type photo. This action
will set the primary image field to the first image in the attachments array in the database, if it is not
already set. <a class="link" href="https://github.com/hay-kot/homebox/pull/576">See GitHub PR #576</a>
<template #title> {{ $t("tools.inventory.setPrimaryPhotos.title") }}</template>
{{ $t("tools.inventory.setPrimaryPhotos.desp") }}
<a class="link" href="https://github.com/hay-kot/homebox/pull/576">See GitHub PR #576</a>
</DetailAction>
</div>
</BaseCard>

1699
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff