mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 13:23:14 +01:00
Compare commits
122 Commits
katos/purc
...
v0.14.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25c76522d6 | ||
|
|
c0e2aa5c62 | ||
|
|
d0b9f742ae | ||
|
|
80d56829c5 | ||
|
|
0946310f60 | ||
|
|
7c855cf55d | ||
|
|
0ab95fb670 | ||
|
|
1e81b4bab4 | ||
|
|
67c50068d9 | ||
|
|
c3628e36f7 | ||
|
|
526799c6da | ||
|
|
4ef7529533 | ||
|
|
b06d670dff | ||
|
|
02c0453ff3 | ||
|
|
09358aa5b2 | ||
|
|
dbe77ea19d | ||
|
|
85e5c7e8e7 | ||
|
|
3c273b370d | ||
|
|
343e56b440 | ||
|
|
3a949aee5a | ||
|
|
1601e52c9c | ||
|
|
760cc8e35c | ||
|
|
6051e1fb8b | ||
|
|
7b146947df | ||
|
|
5497a10f9f | ||
|
|
3e6f4b3657 | ||
|
|
7baf58ad61 | ||
|
|
d72437d18c | ||
|
|
ea57981953 | ||
|
|
c2d0cce02d | ||
|
|
9f7a119e95 | ||
|
|
0dacc97e99 | ||
|
|
52a44da56b | ||
|
|
7114f262c2 | ||
|
|
7647ea96d1 | ||
|
|
593da25cdb | ||
|
|
f22bce7ccb | ||
|
|
1688773bba | ||
|
|
b56b5d2400 | ||
|
|
33ee208071 | ||
|
|
fe880cc2c7 | ||
|
|
cffe57b74e | ||
|
|
66882d6fd9 | ||
|
|
050f22f051 | ||
|
|
7891af3a9a | ||
|
|
40cbccf50a | ||
|
|
0348da362c | ||
|
|
f0a3780f3a | ||
|
|
39163f3cfc | ||
|
|
43676ab407 | ||
|
|
639f795b9a | ||
|
|
fc95d2cab8 | ||
|
|
695b6d68e6 | ||
|
|
b6c265098d | ||
|
|
2a80d348bd | ||
|
|
c6542de93d | ||
|
|
5928678564 | ||
|
|
fac52ca122 | ||
|
|
e9d270269f | ||
|
|
0a4c5fbb28 | ||
|
|
2bfb0283d9 | ||
|
|
94e8aee36f | ||
|
|
8051956a2e | ||
|
|
c7020503be | ||
|
|
28edce96d9 | ||
|
|
b4481fcc84 | ||
|
|
2be2bebb4e | ||
|
|
d4bb8def62 | ||
|
|
7442cb01b7 | ||
|
|
95ba8275e8 | ||
|
|
2f4a0dd212 | ||
|
|
52a621e9ba | ||
|
|
1f77fad829 | ||
|
|
8d93a3f56e | ||
|
|
9c572e7ab2 | ||
|
|
1f15e74730 | ||
|
|
7570a04c02 | ||
|
|
fc2e89c448 | ||
|
|
be216ff7fe | ||
|
|
388208571b | ||
|
|
1891903007 | ||
|
|
41a7e73ff4 | ||
|
|
81d9fb0700 | ||
|
|
76312d6eb6 | ||
|
|
f31528c841 | ||
|
|
6d869fdece | ||
|
|
d0784a7773 | ||
|
|
791f843bc8 | ||
|
|
a0cdb231fd | ||
|
|
e0004842e6 | ||
|
|
fdbfa0e76f | ||
|
|
005516013f | ||
|
|
9ec3dd4b16 | ||
|
|
4dacf981a9 | ||
|
|
9f7b76b37d | ||
|
|
0d51558e74 | ||
|
|
236c257892 | ||
|
|
3540ce4297 | ||
|
|
0bcb155756 | ||
|
|
12219522ab | ||
|
|
13864997ab | ||
|
|
2ab2766534 | ||
|
|
e051352070 | ||
|
|
3bf1e50620 | ||
|
|
42f3c88396 | ||
|
|
c9f31ef934 | ||
|
|
01f54aeb52 | ||
|
|
2d1016d362 | ||
|
|
b48c961ac1 | ||
|
|
4d47567995 | ||
|
|
6b2e3accf7 | ||
|
|
c1f8520c4f | ||
|
|
41eb99ec40 | ||
|
|
97a74127fb | ||
|
|
a9396167bf | ||
|
|
3385e5684e | ||
|
|
bb9672214c | ||
|
|
1b93672417 | ||
|
|
8b1cedd4a8 | ||
|
|
2c34047b6d | ||
|
|
f0942f0714 | ||
|
|
967e574ea8 |
65
.github/scripts/update_currencies.py
vendored
Normal file
65
.github/scripts/update_currencies.py
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
def fetch_currencies():
|
||||
try:
|
||||
response = requests.get('https://restcountries.com/v3.1/all')
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.Timeout:
|
||||
print("Request to the API timed out.")
|
||||
return []
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"An error occurred while making the request: {e}")
|
||||
return []
|
||||
|
||||
try:
|
||||
countries = response.json()
|
||||
except json.JSONDecodeError:
|
||||
print("Failed to decode JSON from the response.")
|
||||
return []
|
||||
|
||||
currencies_list = []
|
||||
for country in countries:
|
||||
country_name = country.get('name', {}).get('common')
|
||||
country_currencies = country.get('currencies', {})
|
||||
for currency_code, currency_info in country_currencies.items():
|
||||
symbol = currency_info.get('symbol', '')
|
||||
currencies_list.append({
|
||||
'code': currency_code,
|
||||
'local': country_name,
|
||||
'symbol': symbol,
|
||||
'name': currency_info.get('name')
|
||||
})
|
||||
|
||||
return currencies_list
|
||||
|
||||
def save_currencies(currencies, file_path):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(currencies, f, ensure_ascii=False, indent=4)
|
||||
except IOError as e:
|
||||
print(f"An error occurred while writing to the file: {e}")
|
||||
|
||||
def load_existing_currencies(file_path):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (IOError, json.JSONDecodeError):
|
||||
return [] # Return an empty list if file doesn't exist or is invalid
|
||||
|
||||
def main():
|
||||
save_path = 'backend/internal/core/currencies/currencies.json'
|
||||
|
||||
existing_currencies = load_existing_currencies(save_path)
|
||||
new_currencies = fetch_currencies()
|
||||
|
||||
if new_currencies == existing_currencies:
|
||||
print("Currencies up-to-date with API, skipping commit.")
|
||||
else:
|
||||
save_currencies(new_currencies, save_path)
|
||||
print("Currencies updated and saved.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -92,8 +92,8 @@ jobs:
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
COMMIT=${{ github.sha }}
|
||||
|
||||
4
.github/workflows/docker-publish.yaml
vendored
4
.github/workflows/docker-publish.yaml
vendored
@@ -89,8 +89,8 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
COMMIT=${{ github.sha }}
|
||||
|
||||
100
.github/workflows/update-currencies.yml
vendored
Normal file
100
.github/workflows/update-currencies.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
name: Update Currencies
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
update-currencies:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.8'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
- name: Run currency fetch script
|
||||
run: python .github/scripts/update_currencies.py
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if [[ $(git status --porcelain) ]]; then
|
||||
echo "Changes detected."
|
||||
echo "changes=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No changes detected."
|
||||
echo "changes=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Delete existing update-currencies branch
|
||||
run: |
|
||||
if git show-ref --verify --quiet refs/heads/update-currencies; then
|
||||
git branch -D update-currencies
|
||||
echo "Deleted existing update-currencies branch."
|
||||
else
|
||||
echo "No existing update-currencies branch to delete."
|
||||
fi
|
||||
|
||||
- name: Create new update-currencies branch
|
||||
if: env.changes == 'true'
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Create a new branch
|
||||
git checkout -b update-currencies
|
||||
git add backend/internal/core/currencies/currencies.json
|
||||
git commit -m "Update currencies.json"
|
||||
|
||||
# Fetch the latest changes from the remote
|
||||
git fetch origin
|
||||
|
||||
# Attempt to rebase with the latest changes
|
||||
if git show-ref --verify --quiet refs/remotes/origin/update-currencies; then
|
||||
if ! git rebase origin/update-currencies; then
|
||||
echo "Rebase conflicts occurred. Please resolve them manually."
|
||||
echo "To resolve conflicts, check out the 'update-currencies' branch locally."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "No existing remote branch 'update-currencies'. Skipping rebase."
|
||||
fi
|
||||
|
||||
# Push the new branch to the remote
|
||||
if ! git push --set-upstream origin update-currencies; then
|
||||
echo "Push failed, trying to fetch and rebase again."
|
||||
git fetch origin
|
||||
if git show-ref --verify --quiet refs/remotes/origin/update-currencies; then
|
||||
if ! git rebase origin/update-currencies; then
|
||||
echo "Second rebase failed. Please resolve manually."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "No existing remote branch 'update-currencies'. Skipping rebase."
|
||||
fi
|
||||
if ! git push --set-upstream origin update-currencies; then
|
||||
echo "Second push failed. Please resolve manually."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create a pull request
|
||||
curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-X POST \
|
||||
-d '{"title": "Update currencies", "head": "update-currencies", "base": "main"}' \
|
||||
https://api.github.com/repos/${{ github.repository }}/pulls
|
||||
|
||||
- name: Notify no changes
|
||||
if: env.changes == 'false'
|
||||
run: echo "Currencies up-to-date with API, skipping commit."
|
||||
19
Dockerfile
19
Dockerfile
@@ -1,13 +1,23 @@
|
||||
# Node dependencies
|
||||
FROM node:18-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
RUN npm install -g pnpm
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --shamefully-hoist
|
||||
|
||||
# Build Nuxt
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
RUN npm install -g pnpm
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --shamefully-hoist
|
||||
COPY frontend .
|
||||
COPY --from=frontend-dependencies /app/node_modules ./node_modules
|
||||
RUN pnpm build
|
||||
|
||||
FROM golang:alpine AS builder-dependencies
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
RUN go mod download
|
||||
|
||||
# Build API
|
||||
FROM golang:alpine AS builder
|
||||
ARG BUILD_TIME
|
||||
@@ -19,10 +29,11 @@ RUN apk update && \
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
RUN go get -d -v ./...
|
||||
RUN rm -rf ./app/api/public
|
||||
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
CGO_ENABLED=0 GOOS=linux go build \
|
||||
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
|
||||
-o /go/bin/api \
|
||||
-v ./app/api/*.go
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
# Node dependencies
|
||||
FROM node:18-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
RUN npm install -g pnpm
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --shamefully-hoist
|
||||
|
||||
# Build Nuxt
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
RUN npm install -g pnpm
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --shamefully-hoist
|
||||
COPY frontend .
|
||||
COPY --from=frontend-dependencies /app/node_modules ./node_modules
|
||||
RUN pnpm build
|
||||
|
||||
FROM golang:alpine AS builder-dependencies
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
RUN go mod download
|
||||
|
||||
# Build API
|
||||
FROM golang:alpine AS builder
|
||||
ARG BUILD_TIME
|
||||
ARG COMMIT
|
||||
ARG VERSION
|
||||
ARG BUSYBOX_VERSION=1.36.1-r31
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add --update git build-base gcc g++
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
RUN go get -d -v ./...
|
||||
RUN rm -rf ./app/api/public
|
||||
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
CGO_ENABLED=0 GOOS=linux go build \
|
||||
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
|
||||
-o /go/bin/api \
|
||||
-v ./app/api/*.go && \
|
||||
chmod +x /go/bin/api && \
|
||||
# create a directory so that we can copy it in the next stage
|
||||
mkdir /data
|
||||
-v ./app/api/*.go
|
||||
|
||||
FROM gcr.io/distroless/java:latest
|
||||
|
||||
|
||||
@@ -49,6 +49,11 @@ Contributions are what make the open source community such an amazing place to l
|
||||
|
||||
If you are not a coder, you can still contribute financially. Financial contributions help me prioritize working on this project over others and helps me know that there is a real demand for project development.
|
||||
|
||||
## Help us Translate
|
||||
We want to make sure that Homebox is available in as many languages as possible. If you are interested in helping us translate Homebox, please help us via our [Weblate instance](https://translate.sysadminsmedia.com/projects/homebox/).
|
||||
|
||||
[](http://translate.sysadminsmedia.com/engage/homebox/)
|
||||
|
||||
## Credits
|
||||
|
||||
- Original project by [@hay-kot](https://github.com/hay-kot)
|
||||
|
||||
@@ -57,6 +57,12 @@ func WithSecureCookies(secure bool) func(*V1Controller) {
|
||||
}
|
||||
}
|
||||
|
||||
func WithURL(url string) func(*V1Controller) {
|
||||
return func(ctrl *V1Controller) {
|
||||
ctrl.url = url
|
||||
}
|
||||
}
|
||||
|
||||
type V1Controller struct {
|
||||
cookieSecure bool
|
||||
repo *repo.AllRepos
|
||||
@@ -65,6 +71,7 @@ type V1Controller struct {
|
||||
isDemo bool
|
||||
allowRegistration bool
|
||||
bus *eventbus.EventBus
|
||||
url string
|
||||
}
|
||||
|
||||
type (
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -333,7 +334,7 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID)
|
||||
csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID, getHBURL(r.Header.Get("Referer"), ctrl.url))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to export items")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
@@ -347,3 +348,26 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
|
||||
return writer.WriteAll(csvData)
|
||||
}
|
||||
}
|
||||
|
||||
func getHBURL(refererHeader, fallback string) (hbURL string) {
|
||||
hbURL = refererHeader
|
||||
if hbURL == "" {
|
||||
hbURL = fallback
|
||||
}
|
||||
|
||||
return stripPathFromURL(hbURL)
|
||||
}
|
||||
|
||||
// stripPathFromURL removes the path from a URL.
|
||||
// ex. https://example.com/tools -> https://example.com
|
||||
func stripPathFromURL(rawURL string) string {
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse URL")
|
||||
return ""
|
||||
}
|
||||
|
||||
strippedURL := url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host}
|
||||
|
||||
return strippedURL.String()
|
||||
}
|
||||
|
||||
@@ -3,12 +3,7 @@ package main
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"fmt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware
|
||||
@@ -18,6 +13,11 @@ import (
|
||||
_ "github.com/sysadminsmedia/homebox/backend/app/api/static/docs"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authroles"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const prefix = "/api"
|
||||
@@ -54,6 +54,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
|
||||
v1.WithRegistration(a.conf.Options.AllowRegistration),
|
||||
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
|
||||
v1.WithURL(fmt.Sprintf("%s:%s", a.conf.Web.Host, a.conf.Web.Port)),
|
||||
)
|
||||
|
||||
r.Route(prefix+"/v1", func(r chi.Router) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ type ExportCSVRow struct {
|
||||
LabelStr LabelString `csv:"HB.labels"`
|
||||
AssetID repo.AssetID `csv:"HB.asset_id"`
|
||||
Archived bool `csv:"HB.archived"`
|
||||
URL string `csv:"HB.url"`
|
||||
|
||||
Name string `csv:"HB.name"`
|
||||
Quantity int `csv:"HB.quantity"`
|
||||
|
||||
@@ -153,7 +153,7 @@ func (s *IOSheet) Read(data io.Reader) error {
|
||||
}
|
||||
|
||||
// ReadItems writes the sheet to a writer.
|
||||
func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos) error {
|
||||
func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos, hbURL string) error {
|
||||
s.Rows = make([]ExportCSVRow, len(items))
|
||||
|
||||
extraHeaders := map[string]struct{}{}
|
||||
@@ -178,6 +178,8 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
|
||||
labelString[i] = l.Name
|
||||
}
|
||||
|
||||
url := generateItemURL(item, hbURL)
|
||||
|
||||
customFields := make([]ExportItemFields, len(item.Fields))
|
||||
|
||||
for i, f := range item.Fields {
|
||||
@@ -201,6 +203,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
|
||||
Description: item.Description,
|
||||
Insured: item.Insured,
|
||||
Archived: item.Archived,
|
||||
URL: url,
|
||||
|
||||
PurchasePrice: item.PurchasePrice,
|
||||
PurchaseFrom: item.PurchaseFrom,
|
||||
@@ -219,6 +222,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
|
||||
SoldPrice: item.SoldPrice,
|
||||
SoldNotes: item.SoldNotes,
|
||||
|
||||
Notes: item.Notes,
|
||||
Fields: customFields,
|
||||
}
|
||||
}
|
||||
@@ -252,6 +256,14 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateItemURL(item repo.ItemOut, d string) string {
|
||||
url := ""
|
||||
if item.ID != uuid.Nil {
|
||||
url = fmt.Sprintf("%s/item/%s", d, item.ID.String())
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
// CSV writes the current sheet to a 2d array, for compatibility with TSV/CSV files.
|
||||
func (s *IOSheet) CSV() ([][]string, error) {
|
||||
memcsv := make([][]string, len(s.Rows)+1)
|
||||
|
||||
@@ -329,7 +329,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
|
||||
return finished, nil
|
||||
}
|
||||
|
||||
func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID) ([][]string, error) {
|
||||
func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID, hbURL string) ([][]string, error) {
|
||||
items, err := svc.repo.Items.GetAll(ctx, GID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -337,7 +337,7 @@ func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID) ([][]strin
|
||||
|
||||
sheet := reporting.IOSheet{}
|
||||
|
||||
err = sheet.ReadItems(ctx, items, GID, svc.repo)
|
||||
err = sheet.ReadItems(ctx, items, GID, svc.repo, hbURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ func (tp *TemplateProps) Set(key, value string) {
|
||||
func DefaultTemplateData() TemplateProps {
|
||||
return TemplateProps{
|
||||
Defaults: TemplateDefaults{
|
||||
CompanyName: "Haybytes.com",
|
||||
CompanyName: "sysadminsmedia.com",
|
||||
CompanyAddress: "123 Main St, Anytown, CA 12345",
|
||||
CompanyURL: "https://haybytes.com",
|
||||
CompanyURL: "https://sysadminsmedia.com",
|
||||
ActivateAccountURL: "https://google.com",
|
||||
UnsubscribeURL: "https://google.com",
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
||||
description: "A simple home inventory management software",
|
||||
lastUpdated: true,
|
||||
sitemap: {
|
||||
hostname: 'https://homebox.sysadminsmedia.com',
|
||||
hostname: 'https://homebox.software',
|
||||
},
|
||||
|
||||
locales: {
|
||||
|
||||
@@ -1683,12 +1683,14 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "admin@admin.com",
|
||||
"description": "string",
|
||||
"name": "username",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "admin",
|
||||
"description": "string",
|
||||
"name": "password",
|
||||
"in": "formData"
|
||||
|
||||
@@ -47,24 +47,28 @@ type checking `task ui:check`
|
||||
|
||||
## Documentation
|
||||
We use [Vitepress](https://vitepress.dev/) for the web documentation of homebox. Anyone is welcome to contribute the documentation if they wish.
|
||||
Anyone is welcome to contribute the documentation if they wish. For documentation contributions, you only need Node.js and PNPM.
|
||||
For documentation contributions, you only need Node.js and PNPM.
|
||||
|
||||
::: info Notes
|
||||
- Languages are separated by folder (e.g `/en`, `/fr`, etc.)
|
||||
- The Sidebar must be updated on a per language basis
|
||||
+ The Sidebar must be updated on a per-language basis
|
||||
- Each languages files can be named independently (slugs can match the language)
|
||||
- Each language's files can be named independently (slugs can match the language)
|
||||
- The `public/_redirects` file is used to redirect the default to english
|
||||
- Redirects can also be configured per language by adding `Language=` after the redirect code
|
||||
:::
|
||||
|
||||
## Translations
|
||||
We use our own [Weblate instance](https://translate.sysadminsmedia.com/projects/homebox/) for translations. If you would like to help translate Homebox, please visit the Weblate instance and help us translate the project.
|
||||
|
||||
[](http://translate.sysadminsmedia.com/engage/homebox/)
|
||||
|
||||
## Branch Flow
|
||||
We use the `main` branch as the development branch. All PRs should be made to the `main` branch form a feature branch.
|
||||
We use the `main` branch as the development branch. All PRs should be made to the `main` branch from a feature branch.
|
||||
To create a pull request you can use the following steps:
|
||||
|
||||
1. Fork the repo and create a new branch from `main`
|
||||
2. If you added code that should be tested, add tests
|
||||
3. If you've changed APIs update the documentation
|
||||
3. If you've changed APIs, update the documentation
|
||||
4. Ensure that the test suite and linters pass
|
||||
5. Create your PR
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ Homebox provides the option to auto-set asset IDs, this is the default behavior.
|
||||
|
||||
Example ID: `000-001`
|
||||
|
||||
To search for an Asset ID: type `#` in the search bar followed by the ID you're searching for, e.g. `#000-001`.
|
||||
|
||||
Asset IDs are partially managed by Homebox, but have a flexible implementation to allow for unique use cases. IDs are non-unique at the database level, so there is nothing stopping a user from manually setting duplicate IDs for various items. There are two recommended approaches to manage Asset IDs:
|
||||
|
||||
### 1. Auto Incrementing IDs
|
||||
|
||||
1
frontend/.npmrc
Normal file
1
frontend/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
shamefully-hoist=true
|
||||
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<BaseModal v-model="dialog">
|
||||
<template #title> Import CSV File </template>
|
||||
<template #title> {{ $t("components.app.import_dialog.title") }} </template>
|
||||
<p>
|
||||
Import a CSV file containing your items, labels, and locations. See documentation for more information on the
|
||||
required format.
|
||||
{{ $t("components.app.import_dialog.description") }}
|
||||
</p>
|
||||
<div class="alert alert-warning shadow-lg mt-4">
|
||||
<div>
|
||||
@@ -21,8 +20,7 @@
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm">
|
||||
Behavior for imports with existing import_refs has changed. If an import_ref is present in the CSV file, the
|
||||
item will be updated with the values in the CSV file.
|
||||
{{ $t("components.app.import_dialog.change_warning") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +31,7 @@
|
||||
|
||||
<BaseButton type="button" @click="uploadCsv">
|
||||
<MdiUpload class="h-5 w-5 mr-2" />
|
||||
Upload
|
||||
{{ $t("components.app.import_dialog.upload") }}
|
||||
</BaseButton>
|
||||
<p class="text-center pt-4 -mb-5">
|
||||
{{ importCsv?.name }}
|
||||
@@ -41,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<BaseButton type="submit" :disabled="!importCsv"> Submit </BaseButton>
|
||||
<BaseButton type="submit" :disabled="!importCsv"> {{ $t("global.submit") }} </BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseModal>
|
||||
|
||||
@@ -9,23 +9,28 @@
|
||||
{{ name != "" ? itm[name] : itm }}
|
||||
</span>
|
||||
</div>
|
||||
<ul
|
||||
<div
|
||||
tabindex="0"
|
||||
style="display: inline"
|
||||
class="dropdown-content mb-1 menu shadow border border-gray-400 rounded bg-base-100 w-full z-[9999] max-h-60 overflow-y-scroll"
|
||||
class="dropdown-content mb-1 menu w-full z-[9999] shadow border border-gray-400 rounded bg-base-100"
|
||||
>
|
||||
<li
|
||||
v-for="(obj, idx) in items"
|
||||
:key="idx"
|
||||
:class="{
|
||||
bordered: selected[idx],
|
||||
}"
|
||||
>
|
||||
<button type="button" @click="toggle(idx)">
|
||||
{{ name != "" ? obj[name] : obj }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="m-2">
|
||||
<input v-model="search" placeholder="Search…" class="input input-sm input-bordered w-full" />
|
||||
</div>
|
||||
<ul class="overflow-y-scroll max-h-60">
|
||||
<li
|
||||
v-for="(obj, idx) in filteredItems"
|
||||
:key="idx"
|
||||
:class="{
|
||||
bordered: selected.includes(obj[props.uniqueField]),
|
||||
}"
|
||||
>
|
||||
<button type="button" @click="toggle(obj[props.uniqueField])">
|
||||
{{ name != "" ? obj[name] : obj }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -49,6 +54,10 @@
|
||||
type: String,
|
||||
default: "name",
|
||||
},
|
||||
uniqueField: {
|
||||
type: String,
|
||||
default: "id",
|
||||
},
|
||||
selectFirst: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -57,19 +66,26 @@
|
||||
|
||||
const value = useVModel(props, "modelValue", emit);
|
||||
|
||||
const selected = computed<Record<number, boolean>>(() => {
|
||||
const obj: Record<number, boolean> = {};
|
||||
value.value.forEach(itm => {
|
||||
const idx = props.items.findIndex(item => item[props.name] === itm.name);
|
||||
obj[idx] = true;
|
||||
const search = ref("");
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
if (!search.value) {
|
||||
return props.items;
|
||||
}
|
||||
|
||||
return props.items.filter(item => {
|
||||
return item[props.name].toLowerCase().includes(search.value.toLowerCase());
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
|
||||
function toggle(index: number) {
|
||||
const item = props.items[index];
|
||||
if (selected.value[index]) {
|
||||
value.value = value.value.filter(itm => itm.name !== item.name);
|
||||
const selected = computed<string[]>(() => {
|
||||
return value.value.map(itm => itm[props.uniqueField]);
|
||||
});
|
||||
|
||||
function toggle(uniqueField: string) {
|
||||
const item = props.items.find(itm => itm[props.uniqueField] === uniqueField);
|
||||
if (selected.value.includes(item[props.uniqueField])) {
|
||||
value.value = value.value.filter(itm => itm[props.uniqueField] !== item[props.uniqueField]);
|
||||
} else {
|
||||
value.value = [...value.value, item];
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
<template>
|
||||
<BaseModal v-model="modal">
|
||||
<template #title> Create Item </template>
|
||||
<template #title> {{ $t("components.item.create_modal.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 ?? []" />
|
||||
|
||||
<div class="modal-action">
|
||||
<div class="flex justify-center">
|
||||
<div>
|
||||
<label for="photo" class="btn">{{ $t("components.item.create_modal.photo_button") }}</label>
|
||||
<input id="photo" type="file" accept="image/*" style="visibility: hidden" @change="previewImage" />
|
||||
</div>
|
||||
<BaseButton class="rounded-r-none" :loading="loading" type="submit">
|
||||
<template #icon>
|
||||
<MdiPackageVariant class="swap-off h-5 w-5" />
|
||||
<MdiPackageVariantClosed class="swap-on h-5 w-5" />
|
||||
</template>
|
||||
Create
|
||||
{{ $t("global.create") }}
|
||||
</BaseButton>
|
||||
<div class="dropdown dropdown-top">
|
||||
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
|
||||
@@ -21,12 +26,24 @@
|
||||
</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("global.create_and_add") }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- photo preview area is AFTER the create button, to avoid pushing the button below the screen on small displays -->
|
||||
<div class="border-t border-gray-300 p-4">
|
||||
<template v-if="form.preview">
|
||||
<p class="mb-0">File name: {{ form.photo?.name }}</p>
|
||||
<img
|
||||
:src="form.preview"
|
||||
class="h-[100px] w-full object-cover rounded-t shadow-sm border-gray-300"
|
||||
alt="Uploaded Photo"
|
||||
/>
|
||||
</template>
|
||||
</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
|
||||
@@ -41,6 +58,7 @@
|
||||
import MdiPackageVariant from "~icons/mdi/package-variant";
|
||||
import MdiPackageVariantClosed from "~icons/mdi/package-variant-closed";
|
||||
import MdiChevronDown from "~icons/mdi/chevron-down";
|
||||
import { AttachmentTypes } from "~~/lib/api/types/non-generated";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -85,10 +103,25 @@
|
||||
description: "",
|
||||
color: "", // Future!
|
||||
labels: [] as LabelOut[],
|
||||
preview: null as string | null,
|
||||
photo: null as File | null,
|
||||
});
|
||||
|
||||
const { shift } = useMagicKeys();
|
||||
|
||||
function previewImage(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.files && input.files.length > 0) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
form.preview = e.target?.result as string;
|
||||
};
|
||||
const file = input.files[0];
|
||||
form.photo = file;
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
whenever(
|
||||
() => modal.value,
|
||||
() => {
|
||||
@@ -112,6 +145,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (loading.value) {
|
||||
toast.error("Already creating an item");
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
if (shift.value) {
|
||||
close = false;
|
||||
}
|
||||
@@ -127,16 +167,32 @@
|
||||
const { error, data } = await api.items.create(out);
|
||||
loading.value = false;
|
||||
if (error) {
|
||||
loading.value = false;
|
||||
toast.error("Couldn't create item");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Item created");
|
||||
|
||||
// if the photo was provided, upload it
|
||||
if (form.photo) {
|
||||
const { error } = await api.items.attachments.add(data.id, form.photo, form.photo.name, AttachmentTypes.Photo);
|
||||
|
||||
if (error) {
|
||||
loading.value = false;
|
||||
toast.error("Failed to upload Photo");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Photo uploaded");
|
||||
}
|
||||
|
||||
// Reset
|
||||
form.name = "";
|
||||
form.description = "";
|
||||
form.color = "";
|
||||
form.preview = null;
|
||||
form.photo = null;
|
||||
focused.value = false;
|
||||
loading.value = false;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<template>
|
||||
<section>
|
||||
<BaseSectionHeader class="mb-2 flex justify-between items-center">
|
||||
Items
|
||||
{{ $t("components.item.view.selectable.items") }}
|
||||
<template #description>
|
||||
<div v-if="!viewSet" class="dropdown dropdown-hover dropdown-left">
|
||||
<label tabindex="0" class="btn btn-ghost m-1">
|
||||
@@ -39,13 +39,13 @@
|
||||
<li>
|
||||
<button @click="setViewPreference('card')">
|
||||
<MdiCardTextOutline class="h-5 w-5" />
|
||||
Card
|
||||
{{ $t("components.item.view.selectable.card") }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button @click="setViewPreference('table')">
|
||||
<MdiTable class="h-5 w-5" />
|
||||
Table
|
||||
{{ $t("components.item.view.selectable.table") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -59,7 +59,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("components.item.view.selectable.no_items") }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseCard>
|
||||
<BaseCard class="overflow-clip">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -63,7 +63,14 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="hasPrev || hasNext" class="border-t p-3 justify-end flex">
|
||||
<div v-if="items.length > 10" class="border-t p-3 justify-end flex gap-3">
|
||||
<div class="flex items-center">Rows per page</div>
|
||||
<select v-model.number="pagination.rowsPerPage" class="select select-sm select-primary">
|
||||
<option :value="10">10</option>
|
||||
<option :value="25">25</option>
|
||||
<option :value="50">50</option>
|
||||
<option :value="100">100</option>
|
||||
</select>
|
||||
<div class="btn-group">
|
||||
<button :disabled="!hasPrev" class="btn btn-sm" @click="prev()">«</button>
|
||||
<button class="btn btn-sm">Page {{ pagination.page }}</button>
|
||||
@@ -97,13 +104,22 @@
|
||||
] as TableHeader[];
|
||||
});
|
||||
|
||||
const preferences = useViewPreferences();
|
||||
|
||||
const pagination = reactive({
|
||||
descending: false,
|
||||
page: 1,
|
||||
rowsPerPage: 10,
|
||||
rowsPerPage: preferences.value.itemsPerTablePage,
|
||||
rowsNumber: 0,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => pagination.rowsPerPage,
|
||||
newRowsPerPage => {
|
||||
preferences.value.itemsPerTablePage = newRowsPerPage;
|
||||
}
|
||||
);
|
||||
|
||||
const next = () => pagination.page++;
|
||||
const hasNext = computed<boolean>(() => {
|
||||
return pagination.page * pagination.rowsPerPage < props.items.length;
|
||||
@@ -189,4 +205,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
:where(.table *:first-child) :where(*:first-child) :where(th, td):first-child {
|
||||
border-top-left-radius: 0px;
|
||||
}
|
||||
|
||||
:where(.table *:first-child) :where(*:first-child) :where(th, td):last-child {
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
|
||||
:where(.table *:last-child) :where(*:last-child) :where(th, td):first-child {
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
|
||||
:where(.table *:last-child) :where(*:last-child) :where(th, td):last-child {
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<BaseModal v-model="modal">
|
||||
<template #title> Create Label </template>
|
||||
<template #title>{{ $t("components.label.create_modal.title") }}</template>
|
||||
<form @submit.prevent="create()">
|
||||
<FormTextField
|
||||
ref="locationNameRef"
|
||||
@@ -12,14 +12,14 @@
|
||||
<FormTextArea v-model="form.description" label="Label Description" />
|
||||
<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("global.create") }} </BaseButton>
|
||||
<div class="dropdown dropdown-top">
|
||||
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
|
||||
<MdiChevronDown class="h-5 w-5" />
|
||||
</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("global.create_and_add") }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -71,6 +71,12 @@
|
||||
const { shift } = useMagicKeys();
|
||||
|
||||
async function create(close = true) {
|
||||
if (loading.value) {
|
||||
toast.error("Already creating a label");
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
if (shift.value) {
|
||||
close = false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<BaseModal v-model="modal">
|
||||
<template #title> Create Location </template>
|
||||
<template #title>{{ $t("components.location.create_modal.title") }}</template>
|
||||
<form @submit.prevent="create()">
|
||||
<FormTextField
|
||||
ref="locationNameRef"
|
||||
@@ -13,14 +13,14 @@
|
||||
<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("global.create") }}</BaseButton>
|
||||
<div class="dropdown dropdown-top">
|
||||
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
|
||||
<MdiChevronDown class="h-5 w-5" />
|
||||
</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("global.create_and_add") }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -73,6 +73,10 @@
|
||||
const { shift } = useMagicKeys();
|
||||
|
||||
async function create(close = true) {
|
||||
if (loading.value) {
|
||||
toast.error("Already creating a location");
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
if (shift.value) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<BaseModal v-model="isRevealed" readonly @cancel="cancel(false)">
|
||||
<template #title> Confirm </template>
|
||||
<template #title> {{ $t("global.confirm") }} </template>
|
||||
<div>
|
||||
<p>{{ text }}</p>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<BaseButton type="submit" @click="confirm(true)"> Confirm </BaseButton>
|
||||
<BaseButton type="submit" @click="confirm(true)"> {{ $t("global.confirm") }} </BaseButton>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
options: any[];
|
||||
display?: string;
|
||||
modelValue: any[];
|
||||
uniqueField: string;
|
||||
};
|
||||
|
||||
const btn = ref<HTMLButtonElement>();
|
||||
@@ -75,6 +76,7 @@
|
||||
label: "",
|
||||
display: "name",
|
||||
modelValue: () => [],
|
||||
uniqueField: "id",
|
||||
});
|
||||
|
||||
const len = computed(() => {
|
||||
@@ -95,9 +97,9 @@
|
||||
const unselected = computed(() => {
|
||||
return props.options.filter(o => {
|
||||
if (searchFold.value.length > 0) {
|
||||
return o[props.display].toLowerCase().includes(searchFold.value) && !selected.value.includes(o);
|
||||
return o[props.display].toLowerCase().includes(searchFold.value) && selected.value.every(s => s[props.uniqueField] !== o[props.uniqueField]);
|
||||
}
|
||||
return !selected.value.includes(o);
|
||||
return selected.value.every(s => s[props.uniqueField] !== o[props.uniqueField]);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</slot>
|
||||
<div tabindex="0" class="card compact dropdown-content shadow-lg bg-base-100 rounded-box w-64">
|
||||
<div class="card-body">
|
||||
<h2 class="text-center">Page URL</h2>
|
||||
<h2 class="text-center">{{ $t("components.global.page_qr_code.page_url") }}</h2>
|
||||
<img :src="getQRCodeUrl()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="py-4">
|
||||
<p class="text-sm">Password Strength: {{ message }}</p>
|
||||
<p class="text-sm">{{ $t("components.global.password_score.password_strength") }}: {{ message }}</p>
|
||||
<progress
|
||||
class="progress w-full progress-bar"
|
||||
:value="score"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="stats bg-neutral shadow rounded-md">
|
||||
<div class="stat text-neutral-content text-center space-y-1 p-3">
|
||||
<div class="stat-title">{{ title }}</div>
|
||||
<div class="stat-title text-neutral-content">{{ title }}</div>
|
||||
<div class="stat-value text-2xl">
|
||||
<Currency v-if="type === 'currency'" :amount="value" />
|
||||
<template v-if="type === 'number'">{{ value }}</template>
|
||||
@@ -27,8 +27,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
[data-theme="homebox"] .stat-title {
|
||||
color: hsl(0 0% 90/0.6);
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -9,6 +9,7 @@ export type LocationViewPreferences = {
|
||||
editorAdvancedView: boolean;
|
||||
itemDisplayView: ViewType;
|
||||
theme: DaisyTheme;
|
||||
itemsPerTablePage: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ export function useViewPreferences(): Ref<LocationViewPreferences> {
|
||||
editorAdvancedView: false,
|
||||
itemDisplayView: "card",
|
||||
theme: "homebox",
|
||||
itemsPerTablePage: 10,
|
||||
},
|
||||
{ mergeDefaults: true }
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ export function useRouteQuery(q: string, def: any): WritableComputedRef<any> {
|
||||
case "string":
|
||||
return computed({
|
||||
get: () => {
|
||||
const qv = route.query[q];
|
||||
const qv = first.value;
|
||||
if (Array.isArray(qv)) {
|
||||
return qv[0];
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
<footer v-if="status" class="text-center w-full bottom-0 pb-4 bg-base-300 text-secondary-content">
|
||||
<p class="text-center text-sm">
|
||||
{{ $t("global.version", { version: status.build.version }) }} ~
|
||||
{{ $t("global.build", { build: status.build.commit }) }}
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
@@ -39,7 +45,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("global.welcome", { username: username }) }}</p>
|
||||
<NuxtLink class="avatar placeholder" to="/home">
|
||||
<div class="bg-base-300 text-neutral-content rounded-full w-24 p-4">
|
||||
<AppLogo />
|
||||
@@ -53,7 +59,7 @@
|
||||
<span>
|
||||
<MdiPlus class="mr-1 -ml-1" />
|
||||
</span>
|
||||
Create
|
||||
{{ $t("global.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">
|
||||
@@ -83,7 +89,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("global.sign_out") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,12 +109,17 @@
|
||||
import MdiMagnify from "~icons/mdi/magnify";
|
||||
import MdiAccount from "~icons/mdi/account";
|
||||
import MdiCog from "~icons/mdi/cog";
|
||||
|
||||
const username = computed(() => authCtx.user?.name || "User");
|
||||
|
||||
const pubApi = usePublicApi();
|
||||
const { data: status } = useAsyncData(async () => {
|
||||
const { data } = await pubApi.status();
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
// Preload currency format
|
||||
useFormatCurrency();
|
||||
|
||||
const modals = reactive({
|
||||
item: false,
|
||||
location: false,
|
||||
|
||||
80
frontend/locales/ca.json
Normal file
80
frontend/locales/ca.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"title": "Importa un fitxer CSV",
|
||||
"upload": "Puja",
|
||||
"description": "Importa un fitxer CSV que contingui els articles, etiquetes i ubicacions. \nConsulteu la documentació per a més informació sobre el format requerit.",
|
||||
"change_warning": "El comportament de les importacions amb import_refs existents ha canviat. Si hi ha un import_refs al fitxer CSV, \nl'article s'actualitzarà amb els valors del fitxer CSV."
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Crea un article",
|
||||
"photo_button": "Foto 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Targeta",
|
||||
"items": "Articles",
|
||||
"no_items": "No hi ha articles a mostrar",
|
||||
"table": "Taula"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Crea una etiqueta"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Crea una ubicació"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "URL de la pàgina"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Força de la contrasenya"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Construcció: { build }",
|
||||
"confirm": "Confirma",
|
||||
"create": "Crea",
|
||||
"create_and_add": "Crea i afegeix-ne un altre",
|
||||
"created": "Creat",
|
||||
"email": "Correu electrònic",
|
||||
"follow_dev": "Segueix al desenvolupador",
|
||||
"github": "Projecte de GitHub",
|
||||
"items": "Articles",
|
||||
"join_discord": "Uniu-vos a Discord",
|
||||
"locations": "Ubicacions",
|
||||
"name": "Nom",
|
||||
"read_docs": "Llegiu la documentació",
|
||||
"search": "Cerca",
|
||||
"sign_out": "Tanca la sessió",
|
||||
"submit": "Envia",
|
||||
"welcome": "Us donem la benvinguda, { username }",
|
||||
"labels": "Etiquetes",
|
||||
"password": "Contrasenya",
|
||||
"version": "Versió {version}"
|
||||
},
|
||||
"index": {
|
||||
"joining_group": "Us uniu a un grup existent!",
|
||||
"dont_join_group": "Voleu unir-vos al grup?",
|
||||
"login": "Inici de sessió",
|
||||
"disabled_registration": "El registre és desactivat",
|
||||
"register": "Registra-m'hi",
|
||||
"remember_me": "Recorda'm",
|
||||
"set_email": "Quin és el seu correu electrònic?",
|
||||
"set_name": "Com us dieu?",
|
||||
"set_password": "Definiu la contrasenya"
|
||||
},
|
||||
"items": {
|
||||
"negate_labels": "Nega les etiquetes seleccionades"
|
||||
}
|
||||
}
|
||||
128
frontend/locales/de.json
Normal file
128
frontend/locales/de.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Das Verhalten beim Importieren vorhandener import_refs hat sich geändert. Wenn ein import_ref in der CSV-Datei vorhanden ist, \nwird der Gegenstand mit den Werten in der CSV-Datei aktualisiert.",
|
||||
"description": "Importiere eine CSV-Datei, die deine Gegenstände, Etiketten und Standorte enthält. Schau in die Dokumentation für weitere Informationen\nzum erforderlichen Format.",
|
||||
"title": "CSV-Datei importieren",
|
||||
"upload": "Hochladen"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "Seiten-URL"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Passwortstärke"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Gegenstand erstellen",
|
||||
"photo_button": "Foto 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Karte",
|
||||
"items": "Gegenstände",
|
||||
"no_items": "Keine Gegenstände anzuzeigen",
|
||||
"table": "Tabelle"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Etikett erstellen"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Standort erstellen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Build: { build }",
|
||||
"confirm": "Bestätigen",
|
||||
"create": "Erstellen",
|
||||
"create_and_add": "Erstellen und weiteren hinzufügen",
|
||||
"created": "Erstellt",
|
||||
"email": "E-Mail",
|
||||
"follow_dev": "Dem Entwickler folgen",
|
||||
"github": "GitHub-Projekt",
|
||||
"items": "Gegenstände",
|
||||
"join_discord": "Discord beitreten",
|
||||
"labels": "Etiketten",
|
||||
"locations": "Lagerorte",
|
||||
"name": "Name",
|
||||
"password": "Passwort",
|
||||
"read_docs": "Dokumentation lesen",
|
||||
"search": "Suche",
|
||||
"sign_out": "Abmelden",
|
||||
"submit": "Einreichen",
|
||||
"version": "Version: { version }",
|
||||
"welcome": "Willkommen, { username }"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registrierung deaktiviert",
|
||||
"dont_join_group": "Möchtest du nicht einer Gruppe beitreten?",
|
||||
"joining_group": "Du trittst einer bereits bestehenden Gruppe bei!",
|
||||
"login": "Anmelden",
|
||||
"register": "Registrieren",
|
||||
"remember_me": "Angemeldet bleiben",
|
||||
"set_email": "Was ist deine E-Mail?",
|
||||
"set_name": "Wie heißt du?",
|
||||
"set_password": "Setze dein Passwort",
|
||||
"tagline": "Verfolgen, Organisieren und Verwalten deiner Sachen."
|
||||
},
|
||||
"items": {
|
||||
"add": "Hinzufügen",
|
||||
"created_at": "Erstellt am",
|
||||
"custom_fields": "Benutzerdefinierte Felder",
|
||||
"field_selector": "Feldauswahl",
|
||||
"field_value": "Feldwert",
|
||||
"first": "Erste",
|
||||
"include_archive": "Archivierte Elemente einschließen",
|
||||
"last": "Letzte",
|
||||
"negate_labels": "Ausgewählte Etiketten negieren",
|
||||
"next_page": "Nächste Seite",
|
||||
"no_results": "Keine Elemente gefunden",
|
||||
"options": "Optionen",
|
||||
"order_by": "Sortieren nach",
|
||||
"pages": "Seite { page } von { totalPages }",
|
||||
"prev_page": "Vorherige Seite",
|
||||
"query_id": "Abfrage Asset-ID-Nummer: { id }",
|
||||
"reset_search": "Suche zurücksetzen",
|
||||
"results": "{ total } Ergebnisse",
|
||||
"tip_1": "Standort- und Etikettenfilter verwenden die 'ODER'-Operation. Wenn mehr als eines ausgewählt ist, wird\n nur eines für eine Übereinstimmung benötigt.",
|
||||
"tip_2": "Suchen, die mit '#' beginnen, fragen nach einer Asset-ID (Beispiel '#000-001')",
|
||||
"tip_3": "Feldfilter verwenden die 'ODER'-Operation. Wenn mehr als eines ausgewählt ist, wird nur eines\n für eine Übereinstimmung benötigt.",
|
||||
"tips": "Tipps",
|
||||
"tips_sub": "Suchtipps",
|
||||
"updated_at": "Aktualisiert am"
|
||||
},
|
||||
"profile": {
|
||||
"active": "Aktiv",
|
||||
"change_password": "Passwort ändern",
|
||||
"currency_format": "Währungsformat",
|
||||
"current_password": "Aktuelles Passwort",
|
||||
"delete_account": "Konto löschen",
|
||||
"delete_account_sub": "Lösche dein Konto und alle zugehörigen Daten. Dies kann nicht rückgängig gemacht werden.",
|
||||
"enabled": "Aktiviert",
|
||||
"gen_invite": "Einladungslink generieren",
|
||||
"group_settings": "Gruppeneinstellungen",
|
||||
"group_settings_sub": "Geteilte Gruppeneinstellungen. Möglicherweise musst du die Seite neu laden, damit einige Einstellungen wirksam werden.",
|
||||
"inactive": "Inaktiv",
|
||||
"new_password": "Neues Passwort",
|
||||
"notifier_modal": "{ type, select, true {Bearbeiten} false {Erstellen} other {Andere}} Notifier",
|
||||
"notifiers": "Melder",
|
||||
"notifiers_sub": "Erhalte Benachrichtigungen über bevorstehende Wartungserinnerungen",
|
||||
"test": "Test",
|
||||
"theme_settings": "Themes",
|
||||
"theme_settings_sub": "Theme-Einstellungen werden im lokalen Speicher deines Browsers gespeichert. Du kannst das Theme jederzeit ändern. Wenn du Probleme hast, dein Theme einzustellen, versuche, die Seite neu zu laden.",
|
||||
"update_group": "Gruppe aktualisieren",
|
||||
"url": "URL",
|
||||
"user_profile": "Benutzerprofil",
|
||||
"user_profile_sub": "Lade Benutzer ein und verwalte dein Konto."
|
||||
}
|
||||
}
|
||||
128
frontend/locales/en.json
Normal file
128
frontend/locales/en.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Behavior for imports with existing import_refs has changed. If an import_ref is present in the CSV file, the \nitem will be updated with the values in the CSV file.",
|
||||
"description": "Import a CSV file containing your items, labels, and locations. See documentation for more information on the \nrequired format.",
|
||||
"title": "Import CSV File",
|
||||
"upload": "Upload"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "Page URL"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Password Strength"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Create Item",
|
||||
"photo_button": "Photo \uD83D\uDCF7"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Card",
|
||||
"items": "Items",
|
||||
"no_items": "No Items to Display",
|
||||
"table": "Table"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Create Label"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Create Location"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Build: { build }",
|
||||
"confirm": "Confirm",
|
||||
"create": "Create",
|
||||
"create_and_add": "Create and Add Another",
|
||||
"created": "Created",
|
||||
"email": "Email",
|
||||
"follow_dev": "Follow the Developer",
|
||||
"github": "GitHub Project",
|
||||
"items": "Items",
|
||||
"join_discord": "Join the Discord",
|
||||
"labels": "Labels",
|
||||
"locations": "Locations",
|
||||
"name": "Name",
|
||||
"password": "Password",
|
||||
"read_docs": "Read the Docs",
|
||||
"search": "Search",
|
||||
"sign_out": "Sign Out",
|
||||
"submit": "Submit",
|
||||
"version": "Version: { version }",
|
||||
"welcome": "Welcome, { username }"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registration Disabled",
|
||||
"dont_join_group": "Don't want to join a group?",
|
||||
"joining_group": "You're Joining an Existing Group!",
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"remember_me": "Remember Me",
|
||||
"set_email": "What's your email?",
|
||||
"set_name": "What's your name?",
|
||||
"set_password": "Set your password",
|
||||
"tagline": "Track, Organize, and Manage your Things."
|
||||
},
|
||||
"items": {
|
||||
"add": "Add",
|
||||
"created_at": "Created At",
|
||||
"custom_fields": "Custom Fields",
|
||||
"field_selector": "Field Selector",
|
||||
"field_value": "Field Value",
|
||||
"first": "First",
|
||||
"include_archive": "Include Archived Items",
|
||||
"last": "Last",
|
||||
"negate_labels": "Negate Selected Labels",
|
||||
"next_page": "Next Page",
|
||||
"no_results": "No Items Found",
|
||||
"options": "Options",
|
||||
"order_by": "Order By",
|
||||
"pages": "Page { page } of { totalPages }",
|
||||
"prev_page": "Previous Page",
|
||||
"query_id": "Querying Asset ID Number: { id }",
|
||||
"reset_search": "Reset Search",
|
||||
"results": "{ total } Results",
|
||||
"tip_1": "Location and label filters use the 'OR' operation. If more than one is selected only one will be\n required for a match.",
|
||||
"tip_2": "Searches prefixed with '#'' will query for a asset ID (example '#000-001')",
|
||||
"tip_3": "Field filters use the 'OR' operation. If more than one is selected only one will be required for a\n match.",
|
||||
"tips": "Tips",
|
||||
"tips_sub": "Search Tips",
|
||||
"updated_at": "Updated At"
|
||||
},
|
||||
"profile": {
|
||||
"active": "Active",
|
||||
"change_password": "Change Password",
|
||||
"currency_format": "Currency Format",
|
||||
"current_password": "Current Password",
|
||||
"delete_account": "Delete Account",
|
||||
"delete_account_sub": "Delete your account and all its associated data. This can not be undone.",
|
||||
"enabled": "Enabled",
|
||||
"gen_invite": "Generate Invite Link",
|
||||
"group_settings": "Group Settings",
|
||||
"group_settings_sub": "Shared Group Settings. You may need to refresh your browser for some settings to apply.",
|
||||
"inactive": "Inactive",
|
||||
"new_password": "New Password",
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notifier",
|
||||
"notifiers": "Notifiers",
|
||||
"notifiers_sub": "Get notifications for upcoming maintenance reminders",
|
||||
"test": "Test",
|
||||
"theme_settings": "Theme Settings",
|
||||
"theme_settings_sub": "Theme settings are stored in your browser's local storage. You can change the theme at any time. If you're\n having trouble setting your theme try refreshing your browser.",
|
||||
"update_group": "Update Group",
|
||||
"url": "URL",
|
||||
"user_profile": "User Profile",
|
||||
"user_profile_sub": "Invite users, and manage your account."
|
||||
}
|
||||
}
|
||||
128
frontend/locales/es.json
Normal file
128
frontend/locales/es.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Se ha modificado el comportamiento de las importaciones con import_refs existentes. Si existe una import_ref en el archivo CSV, el \nelemento se actualizará con los valores del archivo CSV.",
|
||||
"description": "Importa un archivo CSV que contenga tus elementos, etiquetas y ubicaciones. Consulta la documentación para obtener más información sobre el \nformato requerido.",
|
||||
"title": "Importar Archivo CSV",
|
||||
"upload": "Subir"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "URL de página"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Seguridad de la contraseña"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Crear Elemento",
|
||||
"photo_button": "Foto 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Tarjeta",
|
||||
"items": "Elementos",
|
||||
"no_items": "No hay elementos para mostrar",
|
||||
"table": "Tabla"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Crear Etiqueta"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Crear Ubicación"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Compilación: { build }",
|
||||
"confirm": "Confirmar",
|
||||
"create": "Crear",
|
||||
"created": "Creado",
|
||||
"email": "Email",
|
||||
"github": "Proyecto GitHub",
|
||||
"items": "Elementos",
|
||||
"join_discord": "Únete al Discord",
|
||||
"labels": "Etiquetas",
|
||||
"locations": "Ubicaciones",
|
||||
"name": "Nombre",
|
||||
"password": "Contraseña",
|
||||
"read_docs": "Lee la Documentación",
|
||||
"search": "Buscar",
|
||||
"sign_out": "Cerrar Sesión",
|
||||
"submit": "Enviar",
|
||||
"welcome": "Bienvenido/a, { username }",
|
||||
"create_and_add": "Crear y Añadir Otro",
|
||||
"follow_dev": "Seguir al Desarrollador",
|
||||
"version": "Versión: { version }"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registro Desactivado",
|
||||
"dont_join_group": "¿No quieres unirte a un grupo?",
|
||||
"login": "Iniciar sesión",
|
||||
"register": "Registrarse",
|
||||
"remember_me": "Recuérdame",
|
||||
"set_email": "¿Cuál es tu email?",
|
||||
"set_name": "¿Cómo te llamas?",
|
||||
"set_password": "Establece tu contraseña",
|
||||
"tagline": "Rastrea, Organiza y Gestiona tus Cosas.",
|
||||
"joining_group": "¡Te estás uniendo a un grupo existente!"
|
||||
},
|
||||
"items": {
|
||||
"add": "Añadir",
|
||||
"created_at": "Creado El",
|
||||
"custom_fields": "Campos Personalizados",
|
||||
"field_selector": "Selector de Campo",
|
||||
"field_value": "Valor del Campo",
|
||||
"first": "Primer",
|
||||
"include_archive": "Incluir Elementos Archivados",
|
||||
"last": "Último",
|
||||
"negate_labels": "Negar Etiquetas Seleccionadas",
|
||||
"next_page": "Siguiente Página",
|
||||
"no_results": "No se Encontraron Elementos",
|
||||
"options": "Opciones",
|
||||
"order_by": "Ordenar Por",
|
||||
"pages": "Página { page } de { totalPages }",
|
||||
"prev_page": "Anterior Página",
|
||||
"query_id": "Consultar Número ID del Activo: { id }",
|
||||
"reset_search": "Restablecer Búsqueda",
|
||||
"results": "{ total } Resultados",
|
||||
"tip_2": "Las búsquedas precedidas de \"#\" buscarán un ID de activo (por ejemplo, \"#000-001\")",
|
||||
"tip_3": "Los filtros de campo utilizan el operador \"OR\". Si se selecciona más de uno, sólo se requerirá uno para una\n coincidencia.",
|
||||
"tip_1": "Los filtros de ubicación y etiquetas utilizan el operador \"OR\". Si se selecciona más de uno, sólo uno será\n necesario para obtener una coincidencia.",
|
||||
"tips": "Sugerencias",
|
||||
"tips_sub": "Sugerencias de Búsqueda",
|
||||
"updated_at": "Actualizado El"
|
||||
},
|
||||
"profile": {
|
||||
"active": "Activo",
|
||||
"change_password": "Cambiar Contraseña",
|
||||
"currency_format": "Formato Divisa",
|
||||
"current_password": "Contraseña Actual",
|
||||
"delete_account": "Eliminar Cuenta",
|
||||
"enabled": "Habilitado",
|
||||
"gen_invite": "Generar Enlace de Invitación",
|
||||
"group_settings": "Ajustes de Grupo",
|
||||
"group_settings_sub": "Configuración de Grupo Compartido. Es posible que tengas que actualizar tu navegador para que se apliquen algunos ajustes.",
|
||||
"inactive": "Inactivo",
|
||||
"new_password": "Nueva Contraseña",
|
||||
"notifiers": "Notificaciones",
|
||||
"notifiers_sub": "Recibe notificaciones de los próximos recordatorios de mantenimiento",
|
||||
"test": "Probar",
|
||||
"theme_settings": "Ajustes de Tema",
|
||||
"update_group": "Actualizar Grupo",
|
||||
"url": "URL",
|
||||
"user_profile": "Perfil de Usuario",
|
||||
"user_profile_sub": "Invita a usuarios y gestiona tu cuenta.",
|
||||
"delete_account_sub": "Elimina tu cuenta y todos sus datos asociados. Esto no se puede deshacer.",
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notificación",
|
||||
"theme_settings_sub": "La configuración del tema se guarda en el almacenamiento local de tu navegador. Puedes cambiar el tema en cualquier momento. Si tienes\n problemas para configurar el tema, prueba a actualizar el navegador."
|
||||
}
|
||||
}
|
||||
128
frontend/locales/fr.json
Normal file
128
frontend/locales/fr.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"global": {
|
||||
"email": "Courriel",
|
||||
"read_docs": "Lire la documentation",
|
||||
"password": "Mot de passe",
|
||||
"github": "Projet GitHub",
|
||||
"join_discord": "Rejoindre le Discord",
|
||||
"follow_dev": "Suivre le développeur",
|
||||
"version": "Version : { version }",
|
||||
"build": "Assemblage : { build }",
|
||||
"create": "Créer",
|
||||
"submit": "Soumettre",
|
||||
"create_and_add": "Créer et en ajouter un autre",
|
||||
"confirm": "Confirmer",
|
||||
"sign_out": "Déconnexion",
|
||||
"welcome": "Bienvenue, { username }",
|
||||
"created": "Créé",
|
||||
"labels": "Étiquettes",
|
||||
"locations": "Emplacements",
|
||||
"name": "Nom",
|
||||
"search": "Rechercher",
|
||||
"items": "Articles"
|
||||
},
|
||||
"index": {
|
||||
"set_email": "Quel est vôtre courriel ?",
|
||||
"set_name": "Quel est votre nom ?",
|
||||
"login": "Se connecter",
|
||||
"register": "S'enregistrer",
|
||||
"remember_me": "Se souvenir de moi",
|
||||
"set_password": "Définir votre mot de passe",
|
||||
"dont_join_group": "Vous ne voulez pas joindre un groupe ?",
|
||||
"tagline": "Répertoriez, organisez et gérez vos affaires.",
|
||||
"disabled_registration": "Les inscriptions sont désactivées",
|
||||
"joining_group": "Vous rejoignez un groupe existant !"
|
||||
},
|
||||
"components": {
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Créer une étiquette"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"view": {
|
||||
"selectable": {
|
||||
"items": "Articles",
|
||||
"no_items": "Nb. d’articles à afficher",
|
||||
"table": "Tableau",
|
||||
"card": "Carte"
|
||||
}
|
||||
},
|
||||
"create_modal": {
|
||||
"title": "Créer un article",
|
||||
"photo_button": "Photo 📷"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Créer un emplacement"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"password_score": {
|
||||
"password_strength": "Solidité du mot de passe"
|
||||
},
|
||||
"page_qr_code": {
|
||||
"page_url": "URL de la page"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"title": "Importer un fichier CSV",
|
||||
"upload": "Téléverser",
|
||||
"description": "Importer un fichier CSV contenant vos articles, étiquettes, et emplacements. Voir la documentation pour plus d’informations sur le format requis.",
|
||||
"change_warning": "Le comportement lors d’importations avec des import_ref existants a changé. Si une valeur pour import_ref est présente dans le fichier CSV, alors l’élément sera mis à jour avec celle-ci."
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"notifiers_sub": "Recevez des notifications pour vous prévenir des prochaines maintenances.",
|
||||
"active": "Actif",
|
||||
"enabled": "Activé",
|
||||
"test": "Test",
|
||||
"current_password": "Mot de passe actuel",
|
||||
"new_password": "Nouveau mot de passe",
|
||||
"change_password": "Changer de mot de passe",
|
||||
"inactive": "Inactif",
|
||||
"notifiers": "Notifications",
|
||||
"gen_invite": "Générer un lien d’invitation",
|
||||
"user_profile": "Profil utilisateur",
|
||||
"user_profile_sub": "Gérez votre compte et invitez des utilisateurs.",
|
||||
"url": "URL",
|
||||
"delete_account": "Effacer le compte",
|
||||
"delete_account_sub": "Supprimer le compte et toutes ses données. Aucune récupération possible.",
|
||||
"group_settings": "Paramètres du groupe",
|
||||
"group_settings_sub": "Paramètres du groupe partagé. Il peut être nécessaire de recharger la page pour qu’ils deviennent effectifs.",
|
||||
"currency_format": "Format de la devise",
|
||||
"update_group": "Mettre à jour le groupe",
|
||||
"theme_settings": "Paramètres du thème",
|
||||
"theme_settings_sub": "Les paramètres du thème sont stockés dans le navigateur. Vous pouvez les changer à tout moment. Si vous\nrencontrez des problèmes, il est conseillé de rafraichir la page.",
|
||||
"notifier_modal": "Notifications { type, select, true {Edit} false {Create} other {Other}}"
|
||||
},
|
||||
"items": {
|
||||
"add": "Ajouter",
|
||||
"created_at": "Créé à",
|
||||
"custom_fields": "Champs personnalisés",
|
||||
"field_selector": "Sélecteur de champ",
|
||||
"field_value": "Valeur du champ",
|
||||
"first": "Premier",
|
||||
"include_archive": "Inclure les éléments archivés",
|
||||
"last": "Dernier",
|
||||
"negate_labels": "Négliger les étiquettes sélectionnées",
|
||||
"next_page": "Page suivante",
|
||||
"no_results": "Aucun élément trouvé",
|
||||
"options": "Options",
|
||||
"order_by": "Trier par",
|
||||
"pages": "Page { page } sur { totalPages }",
|
||||
"prev_page": "Page précédente",
|
||||
"query_id": "Interrogation du numéro d'identification de l'actif : { id }",
|
||||
"reset_search": "Réinitialiser la recherche",
|
||||
"tip_1": "Les filtres d’emplacement et d’étiquette utilisent l’opérateur « OU ».\nUn seul des filtres n’a besoin de correspondre pour retourner un résultat.",
|
||||
"tip_2": "Les recherches préfixées par '#'' rechercheront un ID d'actif (exemple '#000-001')",
|
||||
"tip_3": "Les filtres de champ utilisent l’opérateur « OU ».\nUn seul des filtres n’a besoin de correspondre pour retourner un résultat.",
|
||||
"tips": "Conseils",
|
||||
"tips_sub": "Conseils pour la recherche",
|
||||
"updated_at": "Mis à jour le",
|
||||
"results": "{ total } résultats"
|
||||
}
|
||||
}
|
||||
127
frontend/locales/it.json
Normal file
127
frontend/locales/it.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Il comportamento per le importazioni con import_ref esistenti è cambiato. Se un import_ref è presente nel file CSV,\n l'elemento verrà aggiornato con i valori presenti nel file CSV.",
|
||||
"description": "Importa un file CSV contenente gli oggetti, le etichette e le posizioni. Vedi la documentazione per ulteriori informazioni sul \nformato richiesto.",
|
||||
"title": "Importa File CSV",
|
||||
"upload": "Carica"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "URL della Pagina"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Complessità della Password"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Crea Oggetto"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Scheda",
|
||||
"items": "Oggetti",
|
||||
"no_items": "Nessun Oggetto da Visualizzare",
|
||||
"table": "Tabella"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Crea Etichetta"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Crea Posizione"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Build: { build }",
|
||||
"confirm": "Conferma",
|
||||
"create": "Crea",
|
||||
"create_and_add": "Crea e aggiungi un altro",
|
||||
"created": "Creato",
|
||||
"email": "Email",
|
||||
"follow_dev": "Segui lo Sviluppatore",
|
||||
"github": "Progetto GitHub",
|
||||
"items": "Articoli",
|
||||
"join_discord": "Unisciti a Discord",
|
||||
"labels": "Etichette",
|
||||
"locations": "Posizioni",
|
||||
"name": "Nome",
|
||||
"password": "Password",
|
||||
"read_docs": "Leggi la Documentazione",
|
||||
"search": "Cerca",
|
||||
"sign_out": "Disconnetti",
|
||||
"submit": "Invia",
|
||||
"version": "Versione: { version }",
|
||||
"welcome": "Benvenuto, { username }"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registrazione Disabilitata",
|
||||
"dont_join_group": "Non vuoi unirti a un gruppo?",
|
||||
"joining_group": "Stai unendoti a un gruppo esistente!",
|
||||
"login": "Accedi",
|
||||
"register": "Registrati",
|
||||
"remember_me": "Ricordami",
|
||||
"set_email": "Qual è la tua email?",
|
||||
"set_name": "Come ti chiami?",
|
||||
"set_password": "Imposta la tua password",
|
||||
"tagline": "Tieni traccia, Organizza e Gestisci le tue Cose."
|
||||
},
|
||||
"items": {
|
||||
"add": "Aggiungi",
|
||||
"created_at": "Creato Il",
|
||||
"custom_fields": "Campi Personalizzati",
|
||||
"field_selector": "Selettore di Campo",
|
||||
"field_value": "Valore del Campo",
|
||||
"first": "Primo",
|
||||
"include_archive": "Includi Articoli Archiviati",
|
||||
"last": "Ultimo",
|
||||
"negate_labels": "Negare Etichette Selezionate",
|
||||
"next_page": "Pagina Successiva",
|
||||
"no_results": "Nessun Articolo Trovato",
|
||||
"options": "Opzioni",
|
||||
"order_by": "Ordina Per",
|
||||
"pages": "Pagina { page } di { totalPages }",
|
||||
"prev_page": "Pagina Precedente",
|
||||
"query_id": "ID dell'Asset in Ricerca: { id }",
|
||||
"reset_search": "Reimposta Ricerca",
|
||||
"results": "{ total } Risultati",
|
||||
"tip_1": "I filtri di posizione ed etichetta utilizzano l'operazione 'OR'. Se ne viene selezionato più\n di uno, ne sarà richiesto solo uno per una corrispondenza.",
|
||||
"tip_2": "Le ricerche con prefisso '#' cercheranno un ID asset (esempio '#000-001')",
|
||||
"tip_3": "I filtri di campo utilizzano l'operazione 'OR'. Se ne viene selezionato più di uno, ne sarà\n richiesto solo uno per una corrispondenza.",
|
||||
"tips": "Suggerimenti",
|
||||
"tips_sub": "Suggerimenti per la Ricerca",
|
||||
"updated_at": "Aggiornato Il"
|
||||
},
|
||||
"profile": {
|
||||
"active": "Attivo",
|
||||
"change_password": "Cambia Password",
|
||||
"currency_format": "Formato Valuta",
|
||||
"current_password": "Password Corrente",
|
||||
"delete_account": "Elimina Account",
|
||||
"delete_account_sub": "Elimina il tuo account e tutti i dati associati. Questa operazione non può essere annullata.",
|
||||
"enabled": "Abilitato",
|
||||
"gen_invite": "Genera Link di Invito",
|
||||
"group_settings": "Impostazioni Gruppo",
|
||||
"group_settings_sub": "Impostazioni Gruppo Condivise. Potrebbe essere necessario aggiornare il browser affinché alcune impostazioni vengano applicate.",
|
||||
"inactive": "Inattivo",
|
||||
"new_password": "Nuova Password",
|
||||
"notifier_modal": "{ type, select, true {Modifica} false {Crea} other {Altro}} Notifier",
|
||||
"notifiers": "Notifiche",
|
||||
"notifiers_sub": "Ricevi notifiche per i prossimi promemoria di manutenzione",
|
||||
"test": "Test",
|
||||
"theme_settings": "Impostazioni Tema",
|
||||
"theme_settings_sub": "Le impostazioni del tema sono memorizzate nella memoria locale del tuo browser. Puoi cambiare il tema \nin qualsiasi momento. Se hai problemi a impostare il tuo tema, prova a ricaricare la pagina.",
|
||||
"update_group": "Aggiorna Gruppo",
|
||||
"url": "URL",
|
||||
"user_profile": "Profilo Utente",
|
||||
"user_profile_sub": "Invita utenti e gestisci il tuo account."
|
||||
}
|
||||
}
|
||||
128
frontend/locales/nl.json
Normal file
128
frontend/locales/nl.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"global": {
|
||||
"version": "Versie: { version }",
|
||||
"github": "GitHub Project",
|
||||
"join_discord": "Sluit je aan bij de Discord",
|
||||
"follow_dev": "Volg de ontwikkelaar",
|
||||
"read_docs": "Lees de documentatie",
|
||||
"email": "E-mail",
|
||||
"submit": "Indienen",
|
||||
"confirm": "Bevestigen",
|
||||
"create": "Maken",
|
||||
"create_and_add": "Maak en voeg nog een toe",
|
||||
"password": "Wachtwoord",
|
||||
"build": "Bouw: { build }",
|
||||
"created": "Gemaakt",
|
||||
"welcome": "Welkom, { username }",
|
||||
"sign_out": "Log uit",
|
||||
"labels": "etiketten",
|
||||
"locations": "Locaties",
|
||||
"name": "Naam",
|
||||
"items": "artikelen",
|
||||
"search": "Zoeken"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registratie uitgeschakeld",
|
||||
"login": "Log in",
|
||||
"register": "Registreer",
|
||||
"remember_me": "Onthoud mij",
|
||||
"set_email": "Wat is je mailadres?",
|
||||
"set_password": "Stel je wachtwoord in",
|
||||
"set_name": "Wat is je naam?",
|
||||
"joining_group": "Je neemt deel aan een bestaande groep!",
|
||||
"tagline": "Volg, Organiseer en beheer je dingen.",
|
||||
"dont_join_group": "Wil je niet aan een groep deelnemen?"
|
||||
},
|
||||
"components": {
|
||||
"global": {
|
||||
"password_score": {
|
||||
"password_strength": "Wachtwoord sterkte"
|
||||
},
|
||||
"page_qr_code": {
|
||||
"page_url": "Pagina URL"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"title": "Importeer CSV bestand",
|
||||
"change_warning": "Gedrag voor importeren met bestaande import_refs is veranderd. Als een import_refs reeds bestaat in het CSV bestand, het\nobject zal worden aangepast met de waardes van het CSV bestand.",
|
||||
"upload": "Upload",
|
||||
"description": "Importeer een CSV bestand met je objecten, labels en locaties. Zie documentatie voor meer informatie m.b.t.\n vereist format."
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Maak object",
|
||||
"photo_button": "Foto 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"items": "Objecten",
|
||||
"card": "Kaart",
|
||||
"table": "Tabel",
|
||||
"no_items": "Geen objecten om te tonen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Maak label"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Maak locatie"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"gen_invite": "Genereer Uitnodigingslink",
|
||||
"notifier_modal": "{ type, select, true {Bewerk} false {Creëer} other {overig}} Notifier",
|
||||
"change_password": "Verander Wachtwoord",
|
||||
"current_password": "Huidig Wachtwoord",
|
||||
"new_password": "Nieuw Wachtwoord",
|
||||
"url": "URL",
|
||||
"enabled": "ingeschakeld",
|
||||
"test": "Test",
|
||||
"user_profile": "Gebruikers Profiel",
|
||||
"user_profile_sub": "Nodig gebruikers uit, en beheer je account.",
|
||||
"active": "Actief",
|
||||
"inactive": "Inactief",
|
||||
"notifiers_sub": "Krijg notificaties voor opkomende onderhouds herinneringen",
|
||||
"group_settings_sub": "Gedeelde groepsinstellingen",
|
||||
"group_settings": "Groeps Instellingen",
|
||||
"notifiers": "Notificatie",
|
||||
"currency_format": "Valutanotatie",
|
||||
"update_group": "Groep bijwerken",
|
||||
"theme_settings": "Theme instellingen",
|
||||
"theme_settings_sub": "Thema-instellingen worden opgeslagen in de lokale opslag van uw browser. Je kan deze wijzigen op elk moment. \nAls je problemen hebt met de instellingen kun je je browser verversen.",
|
||||
"delete_account": "Verwijder account",
|
||||
"delete_account_sub": "Verwijder je account en alle geassocieerde data. Deze actie kan niet ongedaan worden."
|
||||
},
|
||||
"items": {
|
||||
"tip_1": "Locatie- en labelfilters gebruiken de 'OF' -werking. Als er meer dan een is geselecteerd,\nis er maar een nodig voor een overeenkomst.",
|
||||
"tip_2": "Zoekopdrachten voorafgegaan door '#'' zullen om een object-ID vragen (bijvoorbeeld '#000-001')",
|
||||
"tip_3": "Veldfilters gebruiken de 'OF' -bewerking. Indien meer dan 1 is geselecteerd\nzal er maar 1 nodig zijn voor een match.",
|
||||
"tips_sub": "Zoektips",
|
||||
"updated_at": "Bijgewerkt op",
|
||||
"pages": "Pagina { page } van { totalPages }",
|
||||
"prev_page": "Vorige pagina",
|
||||
"query_id": "ID-nummer van object opvragen: { id }",
|
||||
"reset_search": "Reset Zoeken",
|
||||
"tips": "Tips",
|
||||
"no_results": "Geen Items Gevonden",
|
||||
"options": "Opties",
|
||||
"order_by": "Sorteren op",
|
||||
"results": "{ total } Resultaten",
|
||||
"add": "Toevoegen",
|
||||
"created_at": "Aangemaakt op",
|
||||
"custom_fields": "Aangepaste velden",
|
||||
"field_selector": "Veld selectie",
|
||||
"field_value": "Veldwaarde",
|
||||
"first": "Eerst",
|
||||
"include_archive": "Inclusief gearchiveerde items",
|
||||
"last": "Achternaam",
|
||||
"negate_labels": "Negeer Geselecteerde Etiketten",
|
||||
"next_page": "Volgende pagina"
|
||||
}
|
||||
}
|
||||
118
frontend/locales/pl.json
Normal file
118
frontend/locales/pl.json
Normal file
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"description": "Zaimportuj plik CSV zawierający Twoje przedmioty, etykiety i lokalizacje. Zobacz dokumentację, aby uzyskać \nwięcej informacji na temat wymaganego formatu.",
|
||||
"title": "Zaimportuj plik CSV",
|
||||
"upload": "Prześlij"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "Adres URL strony"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Siła hasła"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Utwórz przedmiot",
|
||||
"photo_button": "Zdjęcie 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Karta",
|
||||
"items": "Przedmioty",
|
||||
"no_items": "Brak przedmiotów do wyświetlenia",
|
||||
"table": "Tabela"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Stwórz nową etykietę"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Utwórz lokalizację"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Kompilacja: {build}",
|
||||
"follow_dev": "Śledź dewelopera",
|
||||
"github": "Projekt na GitHubie",
|
||||
"items": "Przedmioty",
|
||||
"version": "Wersja:{version}",
|
||||
"welcome": "Witaj, {username}",
|
||||
"confirm": "Potwierdź",
|
||||
"create": "Utwórz",
|
||||
"create_and_add": "Utwórz i dodaj kolejny",
|
||||
"created": "Utworzone",
|
||||
"email": "E-mail",
|
||||
"join_discord": "Dołącz do Discorda",
|
||||
"labels": "Etykiety",
|
||||
"locations": "Lokalizacje",
|
||||
"name": "Nazwa",
|
||||
"password": "Hasło",
|
||||
"read_docs": "Przeczytaj dokumentację",
|
||||
"search": "Wyszukaj",
|
||||
"sign_out": "Wyloguj się",
|
||||
"submit": "Wyślij"
|
||||
},
|
||||
"index": {
|
||||
"set_password": "Ustaw swoje hasło",
|
||||
"dont_join_group": "Nie chcesz dołączyć do grupy?",
|
||||
"disabled_registration": "Rejestracja jest wyłączona",
|
||||
"joining_group": "Dołączasz do istniejącej grupy!",
|
||||
"login": "Zaloguj się",
|
||||
"register": "Zarejestruj się",
|
||||
"remember_me": "Zapamiętaj mnie",
|
||||
"set_email": "Jaki jest Twój adres e-mail?",
|
||||
"set_name": "Jak się nazywasz?",
|
||||
"tagline": "Śledź, organizuj i zarządzaj swoimi rzeczami."
|
||||
},
|
||||
"items": {
|
||||
"created_at": "Data utworzenia",
|
||||
"field_selector": "Selektor pól",
|
||||
"field_value": "Wartość pola",
|
||||
"first": "Pierwszy",
|
||||
"include_archive": "Uwzględnij zarchiwizowane przedmioty",
|
||||
"negate_labels": "Neguj wybrane etykiety",
|
||||
"no_results": "Nie znaleziono przedmiotów",
|
||||
"query_id": "Zapytanie o numer identyfikacyjny zasobu: { id }",
|
||||
"results": "{ total } wyniki",
|
||||
"tip_3": "Filtry pól używają operacji 'LUB'. Jeśli wybrano więcej niż jeden, wystarczy jeden, \naby uzyskać dopasowanie.",
|
||||
"updated_at": "Zaktualizowano",
|
||||
"tip_1": "Filtry lokalizacji i etykiet używają operacji 'LUB'. Jeśli wybrano więcej niż jeden, wystarczy jeden, \naby uzyskać dopasowanie.",
|
||||
"pages": "Strona {page} z {totalPages}",
|
||||
"add": "Dodaj",
|
||||
"custom_fields": "Pola niestandardowe",
|
||||
"last": "Ostatni",
|
||||
"next_page": "Następna strona",
|
||||
"options": "Opcje",
|
||||
"prev_page": "Poprzednia strona",
|
||||
"reset_search": "Zresetuj wyszukiwanie",
|
||||
"tip_2": "Wyszukiwania poprzedzone prefiksem \"#\" będą wysyłać zapytanie o identyfikator zasobu (na przykład \"#000-001\")",
|
||||
"tips": "Wskazówki",
|
||||
"tips_sub": "Wskazówki wyszukiwania",
|
||||
"order_by": "Ułóż według"
|
||||
},
|
||||
"profile": {
|
||||
"active": "Aktywny",
|
||||
"change_password": "Zmiana hasła",
|
||||
"currency_format": "Format waluty",
|
||||
"delete_account": "Usuń konto",
|
||||
"delete_account_sub": "Usuń swoje konto oraz wszystkie powiązane z nim dane. Tego nie można cofnąć.",
|
||||
"current_password": "Bieżące hasło",
|
||||
"group_settings_sub": "Ustawienia grupy udostępnione. Możesz potrzebować odświeżyć przeglądarkę, aby niektóre ustawienia zostały zastosowane.",
|
||||
"inactive": "Nieaktywny",
|
||||
"new_password": "Nowe hasło",
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Powiadomiony",
|
||||
"enabled": "Włączone",
|
||||
"gen_invite": "Wygeneruj link z zaproszeniem",
|
||||
"group_settings": "Ustawienia grupy"
|
||||
}
|
||||
}
|
||||
128
frontend/locales/ru.json
Normal file
128
frontend/locales/ru.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"title": "Импорт CSV файла",
|
||||
"change_warning": "Изменено поведение для импортов с существующими import_ref. Если в CSV-файле присутствует import_ref, \nто элемент будет обновлен значениями из CSV-файла.",
|
||||
"description": "Импортируйте CSV-файл, содержащий ваши предметы, ярлыки и местоположения. Для получения информации о требуемом формате \nсм. документацию.",
|
||||
"upload": "Загрузить"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "URL-адрес страницы"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Сложность пароля"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Создать элемент",
|
||||
"photo_button": "Фото 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"items": "Элементы",
|
||||
"no_items": "Нет элементов для отображения",
|
||||
"table": "Таблица",
|
||||
"card": "Карта"
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Создать локацию"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Создать ярлык"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"email": "Email",
|
||||
"items": "Элементы",
|
||||
"build": "Сборка: { build }",
|
||||
"confirm": "Подтвердить",
|
||||
"create": "Создать",
|
||||
"create_and_add": "Создать и добавить еще",
|
||||
"created": "Создано",
|
||||
"follow_dev": "Следить за разработчиком",
|
||||
"github": "Проект Github",
|
||||
"labels": "Ярлыки",
|
||||
"join_discord": "Присоединяйтесь к Discord",
|
||||
"locations": "Локации",
|
||||
"name": "Имя",
|
||||
"password": "Пароль",
|
||||
"read_docs": "Прочитать документацию",
|
||||
"search": "Поиск",
|
||||
"sign_out": "Выйти",
|
||||
"submit": "Отправить",
|
||||
"version": "Версия: { version }",
|
||||
"welcome": "Добро пожаловать, { username }"
|
||||
},
|
||||
"index": {
|
||||
"login": "Войти",
|
||||
"register": "Зарегистрироваться",
|
||||
"set_email": "Какой у вас адрес электронной почты?",
|
||||
"set_name": "Как вас зовут?",
|
||||
"set_password": "Установите пароль",
|
||||
"tagline": "Отслеживайте, упорядочивайте и управляйте своими вещами.",
|
||||
"disabled_registration": "Регистрация отключена",
|
||||
"dont_join_group": "Не хотите ли вступить в группу?",
|
||||
"remember_me": "Запомнить меня",
|
||||
"joining_group": "Вы присоединяетесь к уже существующей группе!"
|
||||
},
|
||||
"items": {
|
||||
"add": "Добавить",
|
||||
"created_at": "Создано в",
|
||||
"custom_fields": "Настраиваемые поля",
|
||||
"first": "Первый",
|
||||
"pages": "Страница {page} из {totalPages}",
|
||||
"prev_page": "Предыдущая страница",
|
||||
"reset_search": "Сбросить Поиск",
|
||||
"field_selector": "Поле выбора",
|
||||
"field_value": "Значение поля",
|
||||
"last": "Последний",
|
||||
"negate_labels": "Снять выбранные ярлыки",
|
||||
"next_page": "Следующая страница",
|
||||
"no_results": "Элементы не найдены",
|
||||
"options": "Параметры",
|
||||
"results": "{ total } Результатов",
|
||||
"tips": "Подсказки",
|
||||
"tips_sub": "Поисковые подсказки",
|
||||
"updated_at": "Обновлено в",
|
||||
"tip_3": "Фильтры по полю используют операцию «ИЛИ». Если выбрано несколько фильтров, для совпадения\n требуется только один.",
|
||||
"tip_2": "Поисковые запросы с префиксом \"#\" должны включать в себя ID актива (прим. '#000-001')",
|
||||
"include_archive": "Включить архивированные элементы",
|
||||
"order_by": "Сортировка по",
|
||||
"query_id": "Запрос идентификационного номера актива: { id }",
|
||||
"tip_1": "При фильтрации по локации и по ярлыкам используется логический оператор «ИЛИ». Если выбрано несколько фильтров, то для срабатывания\n требуется лишь одно совпадение."
|
||||
},
|
||||
"profile": {
|
||||
"new_password": "Новый пароль",
|
||||
"update_group": "Обновить группу",
|
||||
"active": "Активный",
|
||||
"change_password": "Изменить пароль",
|
||||
"currency_format": "Формат валюты",
|
||||
"current_password": "Текущий пароль",
|
||||
"delete_account": "Удалить аккаунт",
|
||||
"group_settings": "Настройки группы",
|
||||
"inactive": "Неактивный",
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Уведомитель",
|
||||
"notifiers": "Уведомители",
|
||||
"notifiers_sub": "Получить уведомление о предстоящем обслуживании",
|
||||
"test": "Тест",
|
||||
"theme_settings": "Настройки темы",
|
||||
"url": "URL",
|
||||
"user_profile": "Профиль пользователя",
|
||||
"user_profile_sub": "Приглашайте пользователей и управляйте своим аккаунтом.",
|
||||
"enabled": "Активен",
|
||||
"theme_settings_sub": "Настройки темы хранятся в локальном хранилище браузера. Вы можете изменить тему в любое время. Если у вас\n не удается установить тему, попробуйте перезапустить браузер.",
|
||||
"delete_account_sub": "Удалить свой аккаунт и все связанные с ним данные. Это действие невозможно отменить.",
|
||||
"gen_invite": "Сгенерировать ссылку-приглашение",
|
||||
"group_settings_sub": "Настройки общей группы. Для применения изменений возможно потребуется перезагрузить страницу."
|
||||
}
|
||||
}
|
||||
128
frontend/locales/sl.json
Normal file
128
frontend/locales/sl.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"global": {
|
||||
"follow_dev": "Sledi razvijjalcu",
|
||||
"build": "Gradnja: [ build ]",
|
||||
"confirm": "Potrdi",
|
||||
"create": "Ustvari",
|
||||
"create_and_add": "Ustvari in dodaj še enega",
|
||||
"created": "Ustvarjeno",
|
||||
"email": "E-pošta",
|
||||
"github": "GitHub projekt",
|
||||
"items": "Predmeti",
|
||||
"join_discord": "Pridruži se na Discord",
|
||||
"labels": "Oznake",
|
||||
"locations": "Lokacije",
|
||||
"name": "Naziv",
|
||||
"password": "Geslo",
|
||||
"read_docs": "Preberite dokumentacijo",
|
||||
"search": "Iskanje",
|
||||
"sign_out": "Odjava",
|
||||
"submit": "Pošlji",
|
||||
"version": "Verzija: { version }",
|
||||
"welcome": "Dobrodošel, { username }"
|
||||
},
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Vedenje pri uvozih z obstoječimi import_refs se je spremenilo. Če je import_ref prisoten v datoteki CSV, bo\nelement posodobljen z vrednostmi v datoteki CSV.",
|
||||
"description": "Uvozite datoteko CSV, ki vsebuje vaše predmete, oznake in lokacije. Poglejte si dokumentacijo za več informacij o\nzahtevani obliki.",
|
||||
"title": "Uvozi CSV datoteko",
|
||||
"upload": "Naloži"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "URL strani"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Moč gesla"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Ustvari predmet",
|
||||
"photo_button": "Fotografija 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Kartica",
|
||||
"items": "Predmeti",
|
||||
"no_items": "Ni predmetov za prikaz",
|
||||
"table": "Tabela"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Ustvari oznako"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Ustvari lokacijo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registracija je onemogočena",
|
||||
"dont_join_group": "Se ne želite pridružiti skupini?",
|
||||
"joining_group": "Pridružujete se obstoječi skupini!",
|
||||
"login": "Prijava",
|
||||
"register": "Registriraj se",
|
||||
"remember_me": "Zapomni si me",
|
||||
"set_email": "Kakšen je vaš e-poštni naslov?",
|
||||
"set_password": "Nastavite geslo",
|
||||
"tagline": "Sledite, organizirajte in upravljajte svoje stvari.",
|
||||
"set_name": "Kako vam je ime?"
|
||||
},
|
||||
"items": {
|
||||
"created_at": "Ustvarjeno ob",
|
||||
"include_archive": "Vključi arhivirane predmete",
|
||||
"last": "Zadnji",
|
||||
"negate_labels": "Negiraj izbrane oznake",
|
||||
"no_results": "Ni najdenih predmetov",
|
||||
"options": "Možnosti",
|
||||
"pages": "Stran { page } od { totalPages }",
|
||||
"tip_2": "Iskanja s predpono '#' bodo iskala ID sredstva (primer '#000-001')",
|
||||
"tip_3": "Filtri polj uporabljajo operacijo 'ALI'. Če je izbranih več kot eno, bo za ujemanje dovolj samo\n eno.",
|
||||
"tips": "Nasveti",
|
||||
"updated_at": "Posodobljeno ob",
|
||||
"custom_fields": "Polje po meri",
|
||||
"results": "Rezultatov: { total }",
|
||||
"tip_1": "Filtri lokacij in oznak uporabljajo operacijo 'ALI'. Če je izbranih več kot ena, bo izbrana samo ena\n potrebna za ujemanje.",
|
||||
"add": "Dodaj",
|
||||
"field_selector": "Izbirnik polj",
|
||||
"field_value": "Vrednost polja",
|
||||
"first": "Prvi",
|
||||
"next_page": "Naslednja stran",
|
||||
"prev_page": "Prejšnja stran",
|
||||
"query_id": "Poizvedovanje po identifikacijski številki sredstva: { id }",
|
||||
"reset_search": "Ponastavi iskanje",
|
||||
"tips_sub": "Nasveti za iskanje",
|
||||
"order_by": "Razvrsti po"
|
||||
},
|
||||
"profile": {
|
||||
"gen_invite": "Ustvari povezavo povabila",
|
||||
"group_settings_sub": "Nastavitve skupine v skupni rabi. Morda boste morali osvežiti brskalnik, da bodo nekatere nastavitve veljale.",
|
||||
"notifiers": "Obveščevalci",
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Obveščevalec",
|
||||
"notifiers_sub": "Prejemajte obvestila za prihajajoče opomnike o vzdrževanju",
|
||||
"theme_settings_sub": "Nastavitve teme so shranjene v lokalni shrambi vašega brskalnika. Temo lahko kadar koli spremenite. Če\n imate težave z nastavitvijo teme, poskusite osvežiti brskalnik.",
|
||||
"update_group": "Posodobi skupino",
|
||||
"url": "URL",
|
||||
"delete_account_sub": "Izbrišite svoj račun in vse z njim povezane podatke. Tega ni mogoče razveljaviti.",
|
||||
"active": "Aktivno",
|
||||
"change_password": "Sprememba gesla",
|
||||
"currency_format": "Oblika valute",
|
||||
"current_password": "Trenutno geslo",
|
||||
"delete_account": "Izbriši račun",
|
||||
"enabled": "Omogočeno",
|
||||
"group_settings": "Nastavitve skupine",
|
||||
"inactive": "Neaktivno",
|
||||
"new_password": "Novo geslo",
|
||||
"test": "Preizkus",
|
||||
"theme_settings": "Nastavitve teme",
|
||||
"user_profile": "Profil uporabnika",
|
||||
"user_profile_sub": "Povabite uporabnike in upravljajte svoj račun."
|
||||
}
|
||||
}
|
||||
128
frontend/locales/sv.json
Normal file
128
frontend/locales/sv.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"profile": {
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Anmälare",
|
||||
"change_password": "Ändra Lösenord",
|
||||
"current_password": "Nuvarande lösenord",
|
||||
"new_password": "Nytt lösenord",
|
||||
"notifiers_sub": "Få aviseringar om kommande underhållspåminnelser",
|
||||
"url": "URL",
|
||||
"test": "Test",
|
||||
"gen_invite": "Skapa inbjudningslänk",
|
||||
"user_profile": "Användarprofil",
|
||||
"user_profile_sub": "Bjud in användare och hantera ditt konto.",
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"notifiers": "Notiser",
|
||||
"enabled": "Aktiverad",
|
||||
"currency_format": "Valuta format",
|
||||
"delete_account": "Radera konto",
|
||||
"delete_account_sub": "Ta bort ditt konto och alla tillhörande data. Detta kan inte ångras.",
|
||||
"group_settings": "Grupp inställningar",
|
||||
"group_settings_sub": "Inställningar för delad grupp. Du kan behöva uppdatera din webbläsare för att vissa inställningar ska gälla.",
|
||||
"theme_settings": "Temainställningar",
|
||||
"theme_settings_sub": "Temainställningar sparas i din webbläsares lokala lagring. Du kan ändra tema när du vill. Om du\nhar problem att ställa in tema, pröva att ladda om din webbläsare.",
|
||||
"update_group": "Uppdatera grupp"
|
||||
},
|
||||
"index": {
|
||||
"set_name": "Vad heter du?",
|
||||
"joining_group": "Du går med i en befintlig grupp!",
|
||||
"dont_join_group": "Vill du inte gå med i en grupp?",
|
||||
"tagline": "Spåra, organisera och hantera dina saker.",
|
||||
"disabled_registration": "Registrering avaktiverad",
|
||||
"login": "Logga in",
|
||||
"register": "Registrera",
|
||||
"remember_me": "Kom ihåg mig",
|
||||
"set_email": "Vad är din e-post?",
|
||||
"set_password": "Ställ in ditt lösenord"
|
||||
},
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"upload": "Ladda upp",
|
||||
"title": "Importera CSV fil",
|
||||
"description": "Importera en CSV-fil som innehåller dina föremål, etiketter och platser. Se dokumentationen för mer information om \nönskat format.",
|
||||
"change_warning": "Beteendet för importer med befintliga import_refs har ändrats. Om en import_ref finns i CSV-filen, \nobjektet kommer att uppdateras med värdena i CSV-filen."
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Kort",
|
||||
"items": "Föremål",
|
||||
"table": "Tabell",
|
||||
"no_items": "Inga föremål att visa"
|
||||
}
|
||||
},
|
||||
"create_modal": {
|
||||
"title": "Skapa föremål",
|
||||
"photo_button": "Foto 📷"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Skapa etikett"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Skapa plats"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"password_score": {
|
||||
"password_strength": "Lösenordsstyrka"
|
||||
},
|
||||
"page_qr_code": {
|
||||
"page_url": "Sidans URL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Byggd: { build }",
|
||||
"github": "GitHub Projekt",
|
||||
"join_discord": "Gå med i Discord",
|
||||
"follow_dev": "Följ utvecklaren",
|
||||
"read_docs": "Läs dokumenten",
|
||||
"password": "Lösenord",
|
||||
"email": "Epost",
|
||||
"submit": "Skicka",
|
||||
"confirm": "Godkänn",
|
||||
"create": "Skapa",
|
||||
"created": "Skapad",
|
||||
"welcome": "Välkommen, { username }",
|
||||
"sign_out": "Logga ut",
|
||||
"create_and_add": "Skapa och lägg till en annan",
|
||||
"version": "Version: { version }",
|
||||
"items": "Föremål",
|
||||
"labels": "Etiketter",
|
||||
"locations": "Platser",
|
||||
"name": "Namn",
|
||||
"search": "Sök"
|
||||
},
|
||||
"items": {
|
||||
"add": "Lägg till",
|
||||
"created_at": "Skapat",
|
||||
"custom_fields": "Egna fält",
|
||||
"field_selector": "Fält alternativ",
|
||||
"field_value": "Fält värde",
|
||||
"first": "Första",
|
||||
"include_archive": "Inkludera arkiverade föremål",
|
||||
"last": "Sista",
|
||||
"negate_labels": "Negera valda etiketter",
|
||||
"next_page": "Nästa sida",
|
||||
"no_results": "Inga föremål hittades",
|
||||
"options": "Alternativ",
|
||||
"order_by": "Ordning via",
|
||||
"pages": "Sida { page } av { totalPages }",
|
||||
"prev_page": "Föregående sida",
|
||||
"query_id": "Fråga efter tillgångs-ID-nummer: { id }",
|
||||
"reset_search": "Återställ sökning",
|
||||
"tip_1": "Platser och etiketter filter använder 'OR' funktionen. Om fler än en är valda, endast en kommer\nkrävas för en träff.",
|
||||
"tip_2": "Sökningar med prefixet '#'' kommer att fråga efter ett tillgångs-ID (exempel '#000-001')",
|
||||
"tip_3": "Fältfilter använder 'OR' funktion. Om fler än en är valda endast en kommer att bli krävande för en\nträff.",
|
||||
"tips": "Tips",
|
||||
"tips_sub": "Sök Tips",
|
||||
"updated_at": "Uppdaterad",
|
||||
"results": "{ total } Resultat"
|
||||
}
|
||||
}
|
||||
128
frontend/locales/tr.json
Normal file
128
frontend/locales/tr.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"global": {
|
||||
"version": "Versiyon:{ version }",
|
||||
"password": "Şifre",
|
||||
"create": "Oluştur",
|
||||
"github": "GitHub projesi",
|
||||
"join_discord": "Discord'a Katılın",
|
||||
"follow_dev": "Geliştiriciyi takip edin",
|
||||
"read_docs": "Dokümanları okuyun",
|
||||
"email": "Elektronik posta",
|
||||
"submit": "Gönder",
|
||||
"confirm": "Onaylayın",
|
||||
"build": "Sürüm: { build }",
|
||||
"create_and_add": "Oluştur ve Bir Tane Daha Ekle",
|
||||
"created": "Oluşturuldu",
|
||||
"items": "Öğeler",
|
||||
"labels": "Etiketler",
|
||||
"locations": "Konumlar",
|
||||
"name": "İsim",
|
||||
"search": "Ara",
|
||||
"sign_out": "Oturumu kapat",
|
||||
"welcome": "Hoşgeldib, { username }"
|
||||
},
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Mevcut import_refs ile içe aktarmaların davranışı değişti. CSV dosyasında bir import_ref varsa, \nöğe CSV dosyasındaki değerlerle güncellenecektir.",
|
||||
"title": "CSV dosyasını içeri aktar",
|
||||
"upload": "Yükle",
|
||||
"description": "Öğelerinizi, etiketlerinizi ve konumlarınızı içeren bir CSV dosyasını içe aktarın. Daha fazla\nbilgi için dökümanları okuyun."
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"password_score": {
|
||||
"password_strength": "Şifre güvenlik seviyesi"
|
||||
},
|
||||
"page_qr_code": {
|
||||
"page_url": "Sayfa URL'si"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Eşya Oluştur",
|
||||
"photo_button": "Fotoğraf 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"items": "Öğeler",
|
||||
"card": "Kart",
|
||||
"table": "Tablo",
|
||||
"no_items": "Görüntülecek Öge Yok"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Etiket oluştur"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Konum oluştur"
|
||||
}
|
||||
}
|
||||
},
|
||||
"index": {
|
||||
"remember_me": "Beni Hatırla",
|
||||
"tagline": "Eşyalarınızı Takip Edin, Düzenleyin ve Yönetin.",
|
||||
"disabled_registration": "Kayıt olma devre dışı",
|
||||
"login": "Oturum Aç",
|
||||
"register": "Kaydolun",
|
||||
"set_email": "E-posta adresiniz nedir?",
|
||||
"set_password": "Şifrenizi belirleyin",
|
||||
"set_name": "Adın ne?",
|
||||
"joining_group": "Mevcut bir gruba katılıyorsunuz!",
|
||||
"dont_join_group": "Bir gruba katılmak istemiyor musunuz?"
|
||||
},
|
||||
"items": {
|
||||
"add": "Ekle",
|
||||
"created_at": "Oluşturulma",
|
||||
"custom_fields": "Özel Alanlar",
|
||||
"field_selector": "alan seçici",
|
||||
"field_value": "Alan Değeri",
|
||||
"first": "Birinci",
|
||||
"include_archive": "Arşivlenen Öğeleri Dahil Et",
|
||||
"last": "Son",
|
||||
"negate_labels": "Seçili Etiketleri Yoksay",
|
||||
"next_page": "Sonraki Sayfa",
|
||||
"no_results": "Öğe Bulunamadı",
|
||||
"options": "Seçenekler",
|
||||
"order_by": "Sıralama ölçütü",
|
||||
"pages": "Sayfa { page }/{ totalPages }",
|
||||
"prev_page": "Önceki Sayfa",
|
||||
"query_id": "Varlık Kimlik Numarası Sorgulanıyor: { id }",
|
||||
"tip_1": "Konum ve etiket filtreleri 'veya' işlemini kullanır. Eğer birden fazla seçilirse sadece biri \neşleştirme için kullanılacaktır.",
|
||||
"reset_search": "Aramayı Sıfırla",
|
||||
"tips": "İpuçları",
|
||||
"results": "{ total } Sonuç",
|
||||
"tip_2": "'#' ile başlayan aramalar bir varlık kimliğini sorgular (örneğin '#000-001')",
|
||||
"tip_3": "Alan filtreleri 'VEYA' işlemini kullanır. Birden fazla seçenek seçilirse, eşleşme için yalnızca birinin \nkarşılanması yeterlidir.",
|
||||
"tips_sub": "Arama İpuçları",
|
||||
"updated_at": "Güncellendiği Zaman"
|
||||
},
|
||||
"profile": {
|
||||
"url": "URL",
|
||||
"user_profile": "Kullanıcı Profili",
|
||||
"user_profile_sub": "Kullanıcıları davet edin ve hesabınızı yönetin.",
|
||||
"active": "Aktif",
|
||||
"change_password": "Şifre Değiştir",
|
||||
"currency_format": "Para Birimi Biçimi",
|
||||
"current_password": "Mevcut Şifre",
|
||||
"delete_account": "Hesabı Sil",
|
||||
"delete_account_sub": "Hesabınızı ve ona bağlı tüm verileri silin. Bu işlem geri alınamaz.",
|
||||
"enabled": "Etkinleştirildi",
|
||||
"gen_invite": "Davet Bağlantısı Oluştur",
|
||||
"group_settings": "Grup Ayarları",
|
||||
"group_settings_sub": "Paylaşılan Grup Ayarları. Bazı ayarların uygulanabilmesi için tarayıcınızı yenilemeniz gerekebilir.",
|
||||
"inactive": "Etkin Değil",
|
||||
"new_password": "Yeni Şifre",
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Bildirici",
|
||||
"notifiers": "Bildirimde Bulunanlar",
|
||||
"test": "Test",
|
||||
"theme_settings": "Tema Ayarları",
|
||||
"theme_settings_sub": "Tema ayarları tarayıcınızın yerel depolama alanında saklanır. Temayı istediğiniz zaman değiştirebilirsiniz. \nTemanızı ayarlamakta sorun yaşıyorsanız, tarayıcınızı yenilemeyi deneyin.",
|
||||
"update_group": "Grubu Güncelle",
|
||||
"notifiers_sub": "Yaklaşan bakım hatırlatmaları için bildirimler alın"
|
||||
}
|
||||
}
|
||||
128
frontend/locales/zh_Hans.json
Normal file
128
frontend/locales/zh_Hans.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"title": "导入 CSV 文件",
|
||||
"upload": "上传",
|
||||
"description": "导入包含物品、标签和位置的CSV文件。更多有关信息,\n请参阅文档查看所需格式。",
|
||||
"change_warning": "导入行为会导致现有的 import_refs 的字段被覆盖。\n如果 CSV 文件中存在 import_ref,则将使用 CSV 文件中的值更新该项。"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "创建物品",
|
||||
"photo_button": "照片 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "卡片模式",
|
||||
"items": "物品",
|
||||
"no_items": "没有物品可用展示",
|
||||
"table": "表格模式"
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "创建位置"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "页面URL"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "密码强度"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "创建标签"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"create_and_add": "保存并继续创建",
|
||||
"email": "邮箱",
|
||||
"items": "物品",
|
||||
"build": "编译:{build}",
|
||||
"confirm": "确认",
|
||||
"create": "创建",
|
||||
"created": "已创建",
|
||||
"follow_dev": "关注开发者",
|
||||
"github": "Github项目",
|
||||
"join_discord": "加入Discord讨论",
|
||||
"labels": "标签",
|
||||
"sign_out": "注销",
|
||||
"version": "版本:{version}",
|
||||
"welcome": "欢迎,{username}",
|
||||
"name": "名称",
|
||||
"password": "密码",
|
||||
"read_docs": "查阅文档",
|
||||
"search": "搜索",
|
||||
"submit": "提交",
|
||||
"locations": "位置"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "已禁用注册",
|
||||
"dont_join_group": "不想加入群组?",
|
||||
"joining_group": "您正在加入现有群组!",
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"set_name": "你的名称叫什么?",
|
||||
"remember_me": "记住我",
|
||||
"set_email": "您的电子邮箱是什么?",
|
||||
"tagline": "跟踪、整理和管理您的物品。",
|
||||
"set_password": "设置密码"
|
||||
},
|
||||
"items": {
|
||||
"last": "最后一页",
|
||||
"negate_labels": "取消选中的标签",
|
||||
"next_page": "下一页",
|
||||
"no_results": "没有可显示的物品",
|
||||
"options": "选项",
|
||||
"prev_page": "上一页",
|
||||
"add": "添加",
|
||||
"field_selector": "字段选择",
|
||||
"first": "第一页",
|
||||
"order_by": "排序方式",
|
||||
"pages": "第{page}页,共{totalPages}页",
|
||||
"query_id": "查询资产ID: {id}",
|
||||
"created_at": "创建于",
|
||||
"custom_fields": "自定义字段",
|
||||
"field_value": "字段值",
|
||||
"include_archive": "包括已存档的项目",
|
||||
"reset_search": "重置搜索",
|
||||
"results": "{ total } 条结果",
|
||||
"tip_1": "位置和标签过滤器使用“或”操作。如果选择了多个位置或标签,\n则只需要任意一个匹配上即可。",
|
||||
"tip_2": "以“#”为前缀的搜索将变成查询资产ID(例如“#000-001” )",
|
||||
"tip_3": "字段过滤器使用“或”操作。如果选择了多个位置或标签,\n则只需要任意一个匹配上即可。",
|
||||
"tips": "建议",
|
||||
"tips_sub": "搜索提示",
|
||||
"updated_at": "更新于"
|
||||
},
|
||||
"profile": {
|
||||
"group_settings_sub": "共享组设置。您可能需要刷新浏览器来让某些设置生效。",
|
||||
"notifier_modal": "{ type, select, true {编辑} false {创建} other {Other}} 通知器",
|
||||
"active": "活跃",
|
||||
"delete_account_sub": "删除您的帐户及其所有相关数据。这是无法撤消的。",
|
||||
"inactive": "非活跃",
|
||||
"theme_settings_sub": "主题设置存储在浏览器的本地存储中。您可以随时更改主题。\n如果您在设置主题时遇到问题,请尝试刷新浏览器。",
|
||||
"update_group": "更新组",
|
||||
"change_password": "更改密码",
|
||||
"currency_format": "货币格式",
|
||||
"current_password": "原密码",
|
||||
"delete_account": "删除帐户",
|
||||
"enabled": "已启用",
|
||||
"gen_invite": "生成邀请链接",
|
||||
"group_settings": "组设置",
|
||||
"new_password": "新密码",
|
||||
"notifiers": "通知器",
|
||||
"notifiers_sub": "获取即将到来的维护提醒通知",
|
||||
"test": "测试",
|
||||
"theme_settings": "主题设置",
|
||||
"url": "网址",
|
||||
"user_profile": "用户资料",
|
||||
"user_profile_sub": "邀请用户共同管理您的资产。"
|
||||
}
|
||||
}
|
||||
128
frontend/locales/zh_Hant.json
Normal file
128
frontend/locales/zh_Hant.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"items": {
|
||||
"include_archive": "包括存檔項目",
|
||||
"tip_1": "位置和標籤過濾器使用“OR”運算。如果選擇了多個,則一次配對\n只會使用一個。",
|
||||
"pages": "第{ page } 頁(共{ totalPages })",
|
||||
"tip_3": "字段過濾器使用“OR”運算。如果選擇了多個,則一次配對只需要選擇\n一個。",
|
||||
"field_value": "欄位值",
|
||||
"field_selector": "欄位選擇器",
|
||||
"custom_fields": "自訂欄位",
|
||||
"first": "第一項",
|
||||
"add": "添加",
|
||||
"created_at": "創建於",
|
||||
"last": "最後一項",
|
||||
"negate_labels": "取消選定的標籤",
|
||||
"next_page": "下一頁",
|
||||
"no_results": "沒有找到項目",
|
||||
"options": "選項",
|
||||
"order_by": "排序",
|
||||
"prev_page": "上一頁",
|
||||
"query_id": "查詢資產ID號碼:{ id }",
|
||||
"reset_search": "重置搜尋",
|
||||
"results": "{ total }結果",
|
||||
"tips": "提示",
|
||||
"tips_sub": "搜尋技巧",
|
||||
"updated_at": "更新於",
|
||||
"tip_2": "以「#」為前綴的搜尋將查詢資產 ID(例如「#000-001」)"
|
||||
},
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"description": "匯入包含您的商品、標籤和位置的 CSV 檔案。有關所需格式的更多信息\n,請參閱文件。",
|
||||
"title": "匯入CSV檔案",
|
||||
"upload": "上傳",
|
||||
"change_warning": "現有 import_refs 的導入行為已變更。如果 CSV 檔案中存在 import_ref,則\n項目將使用 CSV 檔案中的值進行更新。"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "頁面網址"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "密碼強度"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "新增項目",
|
||||
"photo_button": "相片 📷"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "卡牌風",
|
||||
"items": "項目",
|
||||
"no_items": "沒有可顯示的項目",
|
||||
"table": "表格"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "新增標籤"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "新增地點"
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "建立{ build }",
|
||||
"confirm": "確認",
|
||||
"create": "新增",
|
||||
"create_and_add": "創建並添加另一個",
|
||||
"created": "已建立",
|
||||
"email": "電子信箱",
|
||||
"github": "GitHub項目",
|
||||
"items": "項目",
|
||||
"join_discord": "加入Discord",
|
||||
"labels": "標籤",
|
||||
"locations": "地點",
|
||||
"name": "名字",
|
||||
"password": "密碼",
|
||||
"read_docs": "閱讀文件",
|
||||
"search": "搜尋",
|
||||
"sign_out": "登出",
|
||||
"submit": "提交",
|
||||
"version": "版本:{ version }",
|
||||
"follow_dev": "關注開發者",
|
||||
"welcome": "歡迎,{ username }"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "已禁用註冊",
|
||||
"dont_join_group": "不想加入群組?",
|
||||
"joining_group": "您正在加入現有群組!",
|
||||
"login": "登入",
|
||||
"register": "註冊",
|
||||
"remember_me": "記住帳號",
|
||||
"set_email": "你的電子郵件是什麼?",
|
||||
"set_name": "你叫什麼名字?",
|
||||
"tagline": "追蹤、組織和管理您的物品。",
|
||||
"set_password": "設定您的密碼"
|
||||
},
|
||||
"profile": {
|
||||
"change_password": "變更密碼",
|
||||
"currency_format": "貨幣格式",
|
||||
"current_password": "當前密碼",
|
||||
"delete_account": "刪除帳戶",
|
||||
"enabled": "啟用",
|
||||
"gen_invite": "產生邀請連結",
|
||||
"group_settings": "群組設定",
|
||||
"group_settings_sub": "共享群組設定。您可能需要刷新瀏覽器才能套用設定。",
|
||||
"inactive": "不活躍",
|
||||
"new_password": "新密碼",
|
||||
"active": "活躍",
|
||||
"delete_account_sub": "刪除您的帳戶及其所有相關資料。此操作無法撤回。",
|
||||
"notifiers": "通知者",
|
||||
"notifiers_sub": "獲取即將到來的維護提醒的通知",
|
||||
"test": "測試",
|
||||
"theme_settings": "主題設定",
|
||||
"update_group": "更新群組",
|
||||
"url": "網址",
|
||||
"user_profile": "使用者資料",
|
||||
"user_profile_sub": "邀請使用者並管理您的帳戶。",
|
||||
"theme_settings_sub": "主題設定儲存在瀏覽器的本機儲存中。您可以隨時變更主題。如果你是\n 設定主題時遇到問題,請嘗試刷新瀏覽器。",
|
||||
"notifier_modal": "通知 { type, select, true {Edit} false {Create} other {Other}}"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import { defineNuxtConfig } from "nuxt/config";
|
||||
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
build: {
|
||||
transpile: ["vue-i18n"],
|
||||
},
|
||||
modules: [
|
||||
"@nuxtjs/tailwindcss",
|
||||
"@pinia/nuxt",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.0",
|
||||
"@iconify-json/mdi": "^1.1.64",
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
||||
"@types/dompurify": "^3.0.0",
|
||||
"@types/markdown-it": "^13.0.0",
|
||||
@@ -27,13 +28,15 @@
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"h3": "^1.7.1",
|
||||
"intl-messageformat": "^10.5.14",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"nuxt": "3.6.5",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.0.0",
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vitest": "^1.0.0"
|
||||
"vitest": "^1.0.0",
|
||||
"vue-i18n": "^9.13.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.9",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import MdiGithub from "~icons/mdi/github";
|
||||
import MdiTwitter from "~icons/mdi/twitter";
|
||||
import MdiDiscord from "~icons/mdi/discord";
|
||||
import MdiFolder from "~icons/mdi/folder";
|
||||
import MdiAccount from "~icons/mdi/account";
|
||||
@@ -8,6 +7,7 @@
|
||||
import MdiLogin from "~icons/mdi/login";
|
||||
import MdiArrowRight from "~icons/mdi/arrow-right";
|
||||
import MdiLock from "~icons/mdi/lock";
|
||||
import MdiMastodon from '~icons/mdi/mastodon';
|
||||
|
||||
useHead({
|
||||
title: "Homebox | Organize and Tag Your Stuff",
|
||||
@@ -77,6 +77,15 @@
|
||||
|
||||
async function registerUser() {
|
||||
loading.value = true;
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
if (!emailRegex.test(email.value)) {
|
||||
toast.error("Invalid email address");
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await api.register({
|
||||
name: username.value,
|
||||
email: email.value,
|
||||
@@ -149,19 +158,19 @@
|
||||
<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.tagline") }}</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/sysadminsmedia/homebox" target="_blank">
|
||||
<a class="tooltip" :data-tip="$t('global.github')" href="https://github.com/sysadminsmedia/homebox" target="_blank">
|
||||
<MdiGithub class="h-8 w-8" />
|
||||
</a>
|
||||
<a href="https://twitter.com/haybytes" class="tooltip" data-tip="Follow The Developer" target="_blank">
|
||||
<MdiTwitter class="h-8 w-8" />
|
||||
<a href="https://noc.social/@sysadminsmedia" class="tooltip" :data-tip="$t('global.follow_dev')" target="_blank">
|
||||
<MdiMastodon class="h-8 w-8" />
|
||||
</a>
|
||||
<a href="https://discord.gg/aY4DCkpNA9" class="tooltip" data-tip="Join The Discord" target="_blank">
|
||||
<a href="https://discord.gg/aY4DCkpNA9" class="tooltip" :data-tip="$t('global.join_discord')" target="_blank">
|
||||
<MdiDiscord class="h-8 w-8" />
|
||||
</a>
|
||||
<a href="https://homebox.sysadminsmedia.com/en/" class="tooltip" data-tip="Read The Docs" target="_blank">
|
||||
<a href="https://homebox.sysadminsmedia.com/en/" class="tooltip" :data-tip="$t('global.read_docs')" target="_blank">
|
||||
<MdiFolder class="h-8 w-8" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -174,17 +183,17 @@
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl align-center">
|
||||
<MdiAccount 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.set_email')" />
|
||||
<FormTextField v-model="username" :label="$t('index.set_name')" />
|
||||
<div v-if="!(groupToken == '')" class="pt-4 pb-1 text-center">
|
||||
<p>You're Joining an Existing Group!</p>
|
||||
<p>{{ $t("index.joining_group") }}</p>
|
||||
<button type="button" class="text-xs underline" @click="groupToken = ''">
|
||||
Don't Want To Join a Group?
|
||||
{{ $t("index.dont_join_group") }}
|
||||
</button>
|
||||
</div>
|
||||
<FormPassword v-model="password" label="Set your password" />
|
||||
<FormPassword v-model="password" :label="$t('index.set_password')" />
|
||||
<PasswordScore v-model:valid="canRegister" :password="password" />
|
||||
<div class="card-actions justify-end">
|
||||
<button
|
||||
@@ -193,7 +202,7 @@
|
||||
:class="loading ? 'loading' : ''"
|
||||
:disabled="loading || !canRegister"
|
||||
>
|
||||
Register
|
||||
{{ $t("index.register") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,17 +213,17 @@
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl align-center">
|
||||
<MdiAccount 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>
|
||||
<p class="text-xs text-center"><b>{{ $t("global.email") }}</b> demo@example.com</p>
|
||||
<p class="text-xs text-center"><b>{{ $t("global.password") }}</b> demo</p>
|
||||
</template>
|
||||
<FormTextField v-model="email" label="Email" />
|
||||
<FormPassword v-model="loginPassword" label="Password" />
|
||||
<FormTextField v-model="email" :label="$t('global.email')" />
|
||||
<FormPassword v-model="loginPassword" :label="$t('global.password')" />
|
||||
<div class="max-w-[140px]">
|
||||
<FormCheckbox v-model="remember" label="Remember Me" />
|
||||
<FormCheckbox v-model="remember" :label="$t('index.remember_me')" />
|
||||
</div>
|
||||
<div class="card-actions justify-end">
|
||||
<button
|
||||
@@ -223,7 +232,7 @@
|
||||
:class="loading ? 'loading' : ''"
|
||||
:disabled="loading"
|
||||
>
|
||||
Login
|
||||
{{ $t("index.login") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,18 +250,21 @@
|
||||
<MdiLogin v-else class="w-5 h-5 swap-off" />
|
||||
<MdiArrowRight 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">
|
||||
<MdiLock class="w-4 h-4 inline-block" />
|
||||
Registration Disabled
|
||||
{{ $t("disabled_registration") }}
|
||||
</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("global.version", { version: status.build.version }) }} ~
|
||||
{{ $t("global.build", { build: status.build.commit }) }}
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
const includeArchived = useRouteQuery("archived", false);
|
||||
const fieldSelector = useRouteQuery("fieldSelector", false);
|
||||
const negateLabels = useRouteQuery("negateLabels", false);
|
||||
const orderBy = useRouteQuery("orderBy", "name");
|
||||
|
||||
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
|
||||
const hasNext = computed(() => page.value * pageSize.value < total.value);
|
||||
@@ -169,6 +170,12 @@
|
||||
}
|
||||
});
|
||||
|
||||
watch(orderBy, (newV, oldV) => {
|
||||
if (newV !== oldV) {
|
||||
search();
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchValues(field: string): Promise<string[]> {
|
||||
if (fieldValuesCache.value[field]) {
|
||||
return fieldValuesCache.value[field];
|
||||
@@ -201,6 +208,7 @@
|
||||
pageSize: pageSize.value,
|
||||
includeArchived: includeArchived.value ? "true" : "false",
|
||||
negateLabels: negateLabels.value ? "true" : "false",
|
||||
orderBy: orderBy.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -231,6 +239,7 @@
|
||||
includeArchived: includeArchived.value,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
orderBy: orderBy.value,
|
||||
fields,
|
||||
});
|
||||
|
||||
@@ -278,6 +287,7 @@
|
||||
archived: includeArchived.value ? "true" : "false",
|
||||
fieldSelector: fieldSelector.value ? "true" : "false",
|
||||
negateLabels: negateLabels.value ? "true" : "false",
|
||||
orderBy: orderBy.value,
|
||||
pageSize: pageSize.value,
|
||||
page: page.value,
|
||||
q: query.value,
|
||||
@@ -311,6 +321,7 @@
|
||||
fieldSelector: "false",
|
||||
pageSize: 10,
|
||||
page: 1,
|
||||
orderBy: "name",
|
||||
q: "",
|
||||
loc: [],
|
||||
lab: [],
|
||||
@@ -327,9 +338,9 @@
|
||||
<div v-if="locations && labels">
|
||||
<div class="flex flex-wrap md:flex-nowrap gap-4 items-end">
|
||||
<div class="w-full">
|
||||
<FormTextField v-model="query" placeholder="Search" />
|
||||
<FormTextField v-model="query" :placeholder="$t('global.search')" />
|
||||
<div v-if="byAssetId" class="text-sm pl-2 pt-2">
|
||||
<p>Querying Asset ID Number: {{ parsedAssetId }}</p>
|
||||
<p>{{ $t("items.query_id", { id: parsedAssetId }) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<BaseButton class="btn-block md:w-auto" @click.prevent="submit">
|
||||
@@ -337,12 +348,12 @@
|
||||
<MdiLoading v-if="loading" class="animate-spin" />
|
||||
<MdiMagnify v-else />
|
||||
</template>
|
||||
Search
|
||||
{{ $t("global.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('global.locations')" :options="locationFlatTree">
|
||||
<template #display="{ item }">
|
||||
<div>
|
||||
<div class="flex w-full">
|
||||
@@ -354,52 +365,60 @@
|
||||
</div>
|
||||
</template>
|
||||
</SearchFilter>
|
||||
<SearchFilter v-model="selectedLabels" label="Labels" :options="labels" />
|
||||
<SearchFilter v-model="selectedLabels" :label="$t('global.labels')" :options="labels" />
|
||||
<div class="dropdown">
|
||||
<label tabindex="0" class="btn btn-xs">Options</label>
|
||||
<label tabindex="0" class="btn btn-xs">{{ $t("items.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.include_archive") }} </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.field_selector") }} </span>
|
||||
</label>
|
||||
<label class="label cursor-pointer mr-auto">
|
||||
<input v-model="negateLabels" type="checkbox" class="toggle toggle-sm toggle-primary" />
|
||||
<span class="label-text ml-4"> Negate selected labels </span>
|
||||
<span class="label-text ml-4"> {{ $t("items.negate_labels") }} </span>
|
||||
</label>
|
||||
<label class="label cursor-pointer mr-auto">
|
||||
<select v-model="orderBy" class="select select-bordered select-sm">
|
||||
<option value="name" selected>{{ $t("global.name") }}</option>
|
||||
<option value="createdAt">{{ $t("items.created_at") }}</option>
|
||||
<option value="updatedAt">{{ $t("items.updated_at") }}</option>
|
||||
</select>
|
||||
<span class="label-text ml-4"> {{ $t("items.order_by") }} </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.reset_search") }} </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_sub") }}</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.tip_1") }}
|
||||
</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.tip_2") }}
|
||||
</li>
|
||||
<li>
|
||||
{{ $t("items.tip_3") }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="fieldSelector" class="py-4 space-y-2">
|
||||
<p>Custom Fields</p>
|
||||
<p>{{ $t("items.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">
|
||||
@@ -416,7 +435,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.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>
|
||||
@@ -430,38 +449,42 @@
|
||||
<MdiDelete 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.add") }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="mt-10">
|
||||
<BaseSectionHeader ref="itemsTitle"> Items </BaseSectionHeader>
|
||||
<BaseSectionHeader ref="itemsTitle"> {{ $t("global.items") }} </BaseSectionHeader>
|
||||
<p class="text-base font-medium flex items-center">
|
||||
{{ total }} Results
|
||||
<span class="text-base ml-auto"> Page {{ page }} of {{ totalPages }}</span>
|
||||
{{ $t("items.results", { total: total }) }}
|
||||
<span class="text-base ml-auto"> {{ $t("items.pages", { page: page, totalPages: 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.no_results") }}</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">
|
||||
<MdiChevronLeft class="mr-1 h-6 w-6" name="mdi-chevron-left" />
|
||||
Prev
|
||||
{{ $t("items.prev_page") }}
|
||||
</button>
|
||||
<button v-if="hasPrev" class="btn text-no-transform" @click="page = 1">{{ $t("items.first") }}</button>
|
||||
<button v-if="hasNext" class="btn text-no-transform" @click="page = totalPages">
|
||||
{{ $t("items.last") }}
|
||||
</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 :disabled="!hasNext" class="btn text-no-transform" @click="next">
|
||||
Next
|
||||
{{ $t("items.next_page") }}
|
||||
<MdiChevronRight 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.pages", { page: page, totalPages: totalPages }) }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</BaseContainer>
|
||||
|
||||
@@ -89,13 +89,6 @@
|
||||
notify.success("Group updated");
|
||||
}
|
||||
|
||||
const pubApi = usePublicApi();
|
||||
const { data: status } = useAsyncData(async () => {
|
||||
const { data } = await pubApi.status();
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const auth = useAuthContext();
|
||||
@@ -305,11 +298,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<BaseModal v-model="passwordChange.dialog">
|
||||
<template #title> Change Password </template>
|
||||
<template #title> {{ $t("profile.change_password") }} </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.current_password')" placeholder="" />
|
||||
<FormPassword v-model="passwordChange.new" :label="$t('profile.new_password')" placeholder="" />
|
||||
<PasswordScore v-model:valid="passwordChange.isValid" :password="passwordChange.new" />
|
||||
|
||||
<div class="flex">
|
||||
@@ -319,26 +312,26 @@
|
||||
:disabled="!passwordChange.isValid"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
{{ $t("global.submit") }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseModal>
|
||||
|
||||
<BaseModal v-model="notifierDialog">
|
||||
<template #title> {{ notifier ? "Edit" : "Create" }} Notifier </template>
|
||||
<template #title> {{ $t("profile.notifier_modal", {type: (notifier != null)}) }} </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('global.name')" />
|
||||
<FormTextField v-model="notifier.url" :label="$t('profile.url')" />
|
||||
<div class="max-w-[100px]">
|
||||
<FormCheckbox v-model="notifier.isActive" label="Enabled" />
|
||||
<FormCheckbox v-model="notifier.isActive" :label="$t('profile.enabled')" />
|
||||
</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.test") }} </BaseButton>
|
||||
<BaseButton type="submit"> {{ $t("global.submit") }} </BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseModal>
|
||||
@@ -348,8 +341,8 @@
|
||||
<template #title>
|
||||
<BaseSectionHeader>
|
||||
<MdiAccount 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_profile") }} </span>
|
||||
<template #description> {{ $t("profile.user_profile_sub") }} </template>
|
||||
</BaseSectionHeader>
|
||||
</template>
|
||||
|
||||
@@ -357,8 +350,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.change_password") }} </BaseButton>
|
||||
<BaseButton size="sm" @click="generateToken"> {{ $t("profile.gen_invite") }} </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" />
|
||||
@@ -375,8 +368,8 @@
|
||||
<template #title>
|
||||
<BaseSectionHeader>
|
||||
<MdiMegaphone 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.notifiers") }} </span>
|
||||
<template #description> {{ $t("profile.notifiers_sub") }} </template>
|
||||
</BaseSectionHeader>
|
||||
</template>
|
||||
|
||||
@@ -399,11 +392,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.active") }} </span>
|
||||
<span v-else class="badge badge-error"> {{ $t("profile.inactive") }} </span>
|
||||
</p>
|
||||
<p>
|
||||
Created
|
||||
{{ $t("global.created") }}
|
||||
<DateTime format="relative" datetime-type="time" :date="n.createdAt" />
|
||||
</p>
|
||||
</div>
|
||||
@@ -411,7 +404,7 @@
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<BaseButton size="sm" @click="openNotifierDialog"> Create </BaseButton>
|
||||
<BaseButton size="sm" @click="openNotifierDialog"> {{ $t("global.create") }} </BaseButton>
|
||||
</div>
|
||||
</BaseCard>
|
||||
|
||||
@@ -419,19 +412,19 @@
|
||||
<template #title>
|
||||
<BaseSectionHeader class="pb-0">
|
||||
<MdiAccountMultiple class="mr-2 -mt-1 text-base-600" />
|
||||
<span class="text-base-600"> Group Settings </span>
|
||||
<span class="text-base-600"> {{ $t("profile.group_settings") }} </span>
|
||||
<template #description>
|
||||
Shared Group Settings. You may need to refresh your browser for some settings to apply.
|
||||
{{ $t("profile.group_settings_sub") }}
|
||||
</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" />
|
||||
<FormSelect v-model="currency" :label="$t('profile.currency_format')" :items="currencies" />
|
||||
<p class="m-2 text-sm">Example: {{ currencyExample }}</p>
|
||||
|
||||
<div class="mt-4">
|
||||
<BaseButton size="sm" @click="updateGroup"> Update Group </BaseButton>
|
||||
<BaseButton size="sm" @click="updateGroup"> {{ $t("profile.update_group") }} </BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</BaseCard>
|
||||
@@ -440,10 +433,9 @@
|
||||
<template #title>
|
||||
<BaseSectionHeader>
|
||||
<MdiFill class="mr-2 text-base-600" />
|
||||
<span class="text-base-600"> Theme Settings </span>
|
||||
<span class="text-base-600"> {{ $t("profile.theme_settings") }} </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_settings_sub") }}
|
||||
</template>
|
||||
</BaseSectionHeader>
|
||||
</template>
|
||||
@@ -491,18 +483,15 @@
|
||||
<template #title>
|
||||
<BaseSectionHeader>
|
||||
<MdiDelete 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.delete_account") }} </span>
|
||||
<template #description> {{ $t("profile.delete_account_sub") }} </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.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>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@
|
||||
</p>
|
||||
<p>
|
||||
This feature is in early development stages and may change in future releases, if you have feedback please
|
||||
provide it in the <a href="https://github.com/sysadminsmedia/homebox/discussions/273">GitHub Discussion</a>
|
||||
provide it in the <a href="https://github.com/sysadminsmedia/homebox/discussions/53">GitHub Discussion</a>
|
||||
</p>
|
||||
<h2>Tips</h2>
|
||||
<ul>
|
||||
|
||||
@@ -42,8 +42,9 @@
|
||||
<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.
|
||||
Imports the standard CSV format for Homebox. Without an <code>HB.import_ref</code> column, this will
|
||||
<b>not</b> overwrite any existing items in your inventory, only add new items. Rows with an
|
||||
<code>HB.import_ref</code> column are merged into existing items with the same import_ref, if one exists.
|
||||
</DetailAction>
|
||||
<DetailAction @action="getExportCSV()">
|
||||
<template #title>Export Inventory</template>
|
||||
@@ -104,7 +105,7 @@
|
||||
middleware: ["auth"],
|
||||
});
|
||||
useHead({
|
||||
title: "Homebox | Profile",
|
||||
title: "Homebox | Tools",
|
||||
});
|
||||
|
||||
const modals = ref({
|
||||
|
||||
63
frontend/plugins/i18n.ts
Normal file
63
frontend/plugins/i18n.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { CompileError, MessageCompiler, MessageContext } from "vue-i18n";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import { IntlMessageFormat } from "intl-messageformat";
|
||||
|
||||
export default defineNuxtPlugin(({ vueApp }) => {
|
||||
function checkDefaultLanguage() {
|
||||
let matched = null;
|
||||
const languages = Object.getOwnPropertyNames(messages())
|
||||
languages.forEach(lang => {
|
||||
if (lang === navigator.language.replace('-', '_')) {
|
||||
matched = lang;
|
||||
}
|
||||
});
|
||||
if (!matched) {
|
||||
languages.forEach(lang => {
|
||||
const languagePartials = navigator.language.split('-')[0]
|
||||
if (lang === languagePartials) {
|
||||
matched = lang;
|
||||
}
|
||||
});
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
locale: checkDefaultLanguage() || "en",
|
||||
fallbackLocale: "en",
|
||||
messageCompiler,
|
||||
messages: messages(),
|
||||
});
|
||||
vueApp.use(i18n);
|
||||
});
|
||||
|
||||
export const messageCompiler: MessageCompiler = (message, { locale, key, onError }) => {
|
||||
if (typeof message === "string") {
|
||||
/**
|
||||
* You can tune your message compiler performance more with your cache strategy or also memoization at here
|
||||
*/
|
||||
const formatter = new IntlMessageFormat(message, locale);
|
||||
return (ctx: MessageContext) => {
|
||||
return formatter.format(ctx.values);
|
||||
};
|
||||
} else {
|
||||
/**
|
||||
* for AST.
|
||||
* If you would like to support it,
|
||||
* You need to transform locale messages such as `json`, `yaml`, etc. with the bundle plugin.
|
||||
*/
|
||||
onError && onError(new Error("not support for AST") as CompileError);
|
||||
return () => key;
|
||||
}
|
||||
};
|
||||
|
||||
export const messages: Object = () => {
|
||||
let messages = {};
|
||||
const modules = import.meta.glob('~//locales/**.json', { eager: true });
|
||||
for (const path in modules) {
|
||||
const key = path.slice(9, -5);
|
||||
messages[key] = modules[path];
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
12656
frontend/pnpm-lock.yaml
generated
12656
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user