mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 13:23:14 +01:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
3
.github/workflows/binaries-publish.yaml
vendored
3
.github/workflows/binaries-publish.yaml
vendored
@@ -16,6 +16,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.23"
|
||||||
|
cache-dependency-path: backend/go.mod
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.github/workflows/e2e-partial.yaml
vendored
3
.github/workflows/e2e-partial.yaml
vendored
@@ -27,7 +27,8 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: "1.23"
|
||||||
|
cache-dependency-path: backend/go.mod
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.github/workflows/partial-backend.yaml
vendored
3
.github/workflows/partial-backend.yaml
vendored
@@ -12,7 +12,8 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: "1.23"
|
||||||
|
cache-dependency-path: backend/go.mod
|
||||||
|
|
||||||
- name: Install Task
|
- name: Install Task
|
||||||
uses: arduino/setup-task@v1
|
uses: arduino/setup-task@v1
|
||||||
|
|||||||
6
.github/workflows/partial-frontend.yaml
vendored
6
.github/workflows/partial-frontend.yaml
vendored
@@ -60,7 +60,8 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: "1.23"
|
||||||
|
cache-dependency-path: backend/go.mod
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -110,7 +111,8 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: "1.23"
|
||||||
|
cache-dependency-path: backend/go.mod
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ tasks:
|
|||||||
desc: Runs all go tests using gotestsum - supports passing gotestsum args
|
desc: Runs all go tests using gotestsum - supports passing gotestsum args
|
||||||
dir: backend
|
dir: backend
|
||||||
cmds:
|
cmds:
|
||||||
- gotestsum {{ .CLI_ARGS }} ./...
|
- go test {{ .CLI_ARGS }} ./...
|
||||||
|
|
||||||
go:coverage:
|
go:coverage:
|
||||||
desc: Runs all go tests with -race flag and generates a coverage report
|
desc: Runs all go tests with -race flag and generates a coverage report
|
||||||
|
|||||||
@@ -190,10 +190,23 @@ func (svc *UserService) Login(ctx context.Context, username, password string, ex
|
|||||||
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasher.CheckPasswordHash(password, usr.PasswordHash) {
|
check, rehash := hasher.CheckPasswordHash(password, usr.PasswordHash)
|
||||||
|
|
||||||
|
if !check {
|
||||||
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rehash {
|
||||||
|
hash, err := hasher.HashPassword(password)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Failed to hash password")
|
||||||
|
return UserAuthTokenDetail{}, err
|
||||||
|
}
|
||||||
|
err = svc.repos.Users.ChangePassword(ctx, usr.ID, hash)
|
||||||
|
if err != nil {
|
||||||
|
return UserAuthTokenDetail{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return svc.createSessionToken(ctx, usr.ID, extendedSession)
|
return svc.createSessionToken(ctx, usr.ID, extendedSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +240,8 @@ func (svc *UserService) ChangePassword(ctx Context, current string, new string)
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasher.CheckPasswordHash(current, usr.PasswordHash) {
|
match, _ := hasher.CheckPasswordHash(current, usr.PasswordHash)
|
||||||
|
if !match {
|
||||||
log.Err(errors.New("current password is incorrect")).Msg("Failed to change password")
|
log.Err(errors.New("current password is incorrect")).Msg("Failed to change password")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
package hasher
|
package hasher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var enabled = true
|
var enabled = true
|
||||||
|
|
||||||
|
type params struct {
|
||||||
|
memory uint32
|
||||||
|
iterations uint32
|
||||||
|
parallelism uint8
|
||||||
|
saltLength uint32
|
||||||
|
keyLength uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = ¶ms{
|
||||||
|
memory: 64 * 1024,
|
||||||
|
iterations: 3,
|
||||||
|
parallelism: 2,
|
||||||
|
saltLength: 16,
|
||||||
|
keyLength: 32,
|
||||||
|
}
|
||||||
|
|
||||||
func init() { // nolint: gochecknoinits
|
func init() { // nolint: gochecknoinits
|
||||||
disableHas := os.Getenv("UNSAFE_DISABLE_PASSWORD_PROJECTION") == "yes_i_am_sure"
|
disableHas := os.Getenv("UNSAFE_DISABLE_PASSWORD_PROJECTION") == "yes_i_am_sure"
|
||||||
|
|
||||||
@@ -18,20 +39,108 @@ func init() { // nolint: gochecknoinits
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateRandomBytes(n uint32) ([]byte, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
func HashPassword(password string) (string, error) {
|
func HashPassword(password string) (string, error) {
|
||||||
if !enabled {
|
if !enabled {
|
||||||
return password, nil
|
return password, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
salt, err := GenerateRandomBytes(p.saltLength)
|
||||||
return string(bytes), err
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
hash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
|
||||||
|
|
||||||
|
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
||||||
|
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
||||||
|
|
||||||
|
encodedHash := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, 64*1024, 3, 2, b64Salt, b64Hash)
|
||||||
|
return encodedHash, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckPasswordHash(password, hash string) bool {
|
// CheckPasswordHash checks if the provided password matches the hash.
|
||||||
|
// Additionally, it returns a boolean indicating whether the password should be rehashed.
|
||||||
|
func CheckPasswordHash(password, hash string) (bool, bool) {
|
||||||
if !enabled {
|
if !enabled {
|
||||||
return password == hash
|
return password == hash, false
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
// Compare Argon2id hash first
|
||||||
return err == nil
|
match, err := comparePasswordAndHash(password, hash)
|
||||||
|
if err != nil || !match {
|
||||||
|
// If argon2id hash fails or doesn't match, try bcrypt
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
|
if err == nil {
|
||||||
|
// If bcrypt hash matches, return true and indicate rehashing
|
||||||
|
return true, true
|
||||||
|
} else {
|
||||||
|
// If both fail, return false and indicate no rehashing
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePasswordAndHash(password, encodedHash string) (match bool, err error) {
|
||||||
|
// Extract the parameters, salt and derived key from the encoded password
|
||||||
|
// hash.
|
||||||
|
p, salt, hash, err := decodeHash(encodedHash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the key from the other password using the same parameters.
|
||||||
|
otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
|
||||||
|
|
||||||
|
// Check that the contents of the hashed passwords are identical. Note
|
||||||
|
// that we are using the subtle.ConstantTimeCompare() function for this
|
||||||
|
// to help prevent timing attacks.
|
||||||
|
if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHash(encodedHash string) (p *params, salt, hash []byte, err error) {
|
||||||
|
vals := strings.Split(encodedHash, "$")
|
||||||
|
if len(vals) != 6 {
|
||||||
|
return nil, nil, nil, fmt.Errorf("invalid hash format")
|
||||||
|
}
|
||||||
|
|
||||||
|
var version int
|
||||||
|
_, err = fmt.Sscanf(vals[2], "v=%d", &version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
if version != argon2.Version {
|
||||||
|
return nil, nil, nil, fmt.Errorf("unsupported argon2 version: %d", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
p = ¶ms{}
|
||||||
|
_, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
p.saltLength = uint32(len(salt))
|
||||||
|
|
||||||
|
hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
p.keyLength = uint32(len(hash))
|
||||||
|
|
||||||
|
return p, salt, hash, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package hasher
|
package hasher
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestHashPassword(t *testing.T) {
|
func TestHashPassword(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
type args struct {
|
type args struct {
|
||||||
password string
|
password string
|
||||||
|
invalidInputs []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -15,13 +18,29 @@ func TestHashPassword(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "letters_and_numbers",
|
name: "letters_and_numbers",
|
||||||
args: args{
|
args: args{
|
||||||
password: "password123456788",
|
password: "password123456788",
|
||||||
|
invalidInputs: []string{"testPassword", "AnotherBadPassword", "ThisShouldNeverWork", "1234567890"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "letters_number_and_special",
|
name: "letters_number_and_special",
|
||||||
args: args{
|
args: args{
|
||||||
password: "!2afj3214pofajip3142j;fa",
|
password: "!2afj3214pofajip3142j;fa",
|
||||||
|
invalidInputs: []string{"testPassword", "AnotherBadPassword", "ThisShouldNeverWork", "1234567890"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extra_long_password",
|
||||||
|
args: args{
|
||||||
|
password: "this_is_a_very_long_password_that_should_be_hashed_properly_and_still_work_with_the_check_function",
|
||||||
|
invalidInputs: []string{"testPassword", "AnotherBadPassword", "ThisShouldNeverWork", "1234567890"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_password",
|
||||||
|
args: args{
|
||||||
|
password: "",
|
||||||
|
invalidInputs: []string{"testPassword", "AnotherBadPassword", "ThisShouldNeverWork", "1234567890"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -32,9 +51,17 @@ func TestHashPassword(t *testing.T) {
|
|||||||
t.Errorf("HashPassword() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("HashPassword() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !CheckPasswordHash(tt.args.password, got) {
|
check, _ := CheckPasswordHash(tt.args.password, got)
|
||||||
|
if !check {
|
||||||
t.Errorf("CheckPasswordHash() failed to validate password=%v against hash=%v", tt.args.password, got)
|
t.Errorf("CheckPasswordHash() failed to validate password=%v against hash=%v", tt.args.password, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, invalid := range tt.args.invalidInputs {
|
||||||
|
check, _ := CheckPasswordHash(invalid, got)
|
||||||
|
if check {
|
||||||
|
t.Errorf("CheckPasswordHash() improperly validated password=%v against hash=%v", invalid, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
class="absolute left-0 top-0 size-full cursor-pointer opacity-0"
|
class="absolute left-0 top-0 size-full cursor-pointer opacity-0"
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/png,image/jpeg,image/gif,image/avif,image/webp;capture=camera"
|
accept="image/png,image/jpeg,image/gif,image/avif,image/webp;capture=camera"
|
||||||
|
capture="environment"
|
||||||
multiple
|
multiple
|
||||||
@change="previewImage"
|
@change="previewImage"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -185,6 +185,7 @@
|
|||||||
const preferences = useViewPreferences();
|
const preferences = useViewPreferences();
|
||||||
|
|
||||||
const defaultHeaders = [
|
const defaultHeaders = [
|
||||||
|
{ text: "items.asset_id", value: "assetId", enabled: false },
|
||||||
{
|
{
|
||||||
text: "items.name",
|
text: "items.name",
|
||||||
value: "name",
|
value: "name",
|
||||||
|
|||||||
@@ -2,32 +2,39 @@
|
|||||||
"components": {
|
"components": {
|
||||||
"app": {
|
"app": {
|
||||||
"create_modal": {
|
"create_modal": {
|
||||||
"createAndAddAnother": "Usa {shiftKey} + {enterKey} para criar e adicionar outro.",
|
"createAndAddAnother": "Use {shiftKey} + {enterKey} para criar e adicionar outro.",
|
||||||
"enter": "Enter",
|
"enter": "Enter",
|
||||||
"shift": "Shift"
|
"shift": "Shift"
|
||||||
},
|
},
|
||||||
"import_dialog": {
|
"import_dialog": {
|
||||||
"change_warning": "O comportamento para ficheiros importados com import_refs foi alterado. Se um import_ref está presente no ficheiro CSV, \no item vai ser atualizado com os valores no ficheiro CSV.",
|
"change_warning": "O comportamento para importações com import_refs existente foi alterado. Se um import_ref estiver presente \nno ficheiro CSV, o item será atualizado com os valores do ficheiro CSV.",
|
||||||
"description": "Importe um ficheiro CSV com artigos, rótulos e localizações. Consulte a documentação para mais informações\nacerca do formato necessário.",
|
"description": "Importe um ficheiro CSV contendo os seus itens, etiquetas e localizações. Consulte a documentação para mais \ninformações sobre o formato necessário.",
|
||||||
"title": "Importar ficheiro CSV",
|
"title": "Importar Ficheiro CSV",
|
||||||
"toast": {
|
"toast": {
|
||||||
"import_success": "Importação com sucesso!"
|
"import_failed": "Falha na importação. Tente novamente mais tarde.",
|
||||||
|
"import_success": "Importação bem-sucedida!",
|
||||||
|
"please_select_file": "Selecione um ficheiro para importar."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outdated": {
|
"outdated": {
|
||||||
"current_version": "Versão Atual",
|
"current_version": "Versão Atual",
|
||||||
"dismiss": "Dispensar",
|
"dismiss": "Dispensar",
|
||||||
"latest_version": "Versão mais recente",
|
"latest_version": "Última Versão",
|
||||||
"new_version_available": "Nova Versão Disponível",
|
"new_version_available": "Nova Versão Disponível",
|
||||||
"new_version_available_link": "Carregue aqui para ver as notas desta versão"
|
"new_version_available_link": "Clique aqui para ver as notas da versão"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"password": {
|
||||||
|
"toggle_show": "Alternar Visibilidade da Palavra-passe"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"global": {
|
"global": {
|
||||||
"copy_text": {
|
"copy_text": {
|
||||||
"documentation": "Documentação",
|
"documentation": "documentação",
|
||||||
"failed_to_copy": "Falha ao copiar o texto para a área de transferência",
|
"failed_to_copy": "Falha ao copiar o texto para a área de transferência",
|
||||||
"https_required": "porque é necessário HTTPS",
|
"https_required": "porque é necessário HTTPS",
|
||||||
"learn_more": "Saiba mais na nossa"
|
"learn_more": "Saiba mais em"
|
||||||
},
|
},
|
||||||
"date_time": {
|
"date_time": {
|
||||||
"ago": "{0} atrás",
|
"ago": "{0} atrás",
|
||||||
@@ -35,7 +42,7 @@
|
|||||||
"hour": "hora",
|
"hour": "hora",
|
||||||
"hours": "horas",
|
"hours": "horas",
|
||||||
"in": "em {0}",
|
"in": "em {0}",
|
||||||
"just-now": "neste momento",
|
"just-now": "agora mesmo",
|
||||||
"last-month": "mês passado",
|
"last-month": "mês passado",
|
||||||
"last-week": "semana passada",
|
"last-week": "semana passada",
|
||||||
"last-year": "ano passado",
|
"last-year": "ano passado",
|
||||||
@@ -54,44 +61,75 @@
|
|||||||
"yesterday": "ontem"
|
"yesterday": "ontem"
|
||||||
},
|
},
|
||||||
"label_maker": {
|
"label_maker": {
|
||||||
"browser_print": "Imprimir a partir do navegador",
|
"browser_print": "Imprimir a partir do Navegador",
|
||||||
"confirm_description": "Tem a certeza que quer imprimir esta etiqueta?",
|
"confirm_description": "Tem a certeza de que deseja imprimir esta etiqueta?",
|
||||||
"download": "Transferir Etiqueta",
|
"download": "Transferir Etiqueta",
|
||||||
"print": "Imprimir etiqueta",
|
"print": "Imprimir etiqueta",
|
||||||
"server_print": "Imprimir no Servidor",
|
"server_print": "Imprimir no Servidor",
|
||||||
"titles": "Etiquetas",
|
"titles": "Etiquetas",
|
||||||
"toast": {
|
"toast": {
|
||||||
"load_status_failed": "Falha a carregar o estado",
|
"load_status_failed": "Falha ao carregar o estado",
|
||||||
"print_failed": "Falha a criar a etiqueta",
|
"print_failed": "Falha ao imprimir etiqueta",
|
||||||
"print_success": "Etiqueta impressa"
|
"print_success": "Etiqueta impressa"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"page_qr_code": {
|
"page_qr_code": {
|
||||||
"page_url": "Endereço da página",
|
"page_url": "URL da Página",
|
||||||
"qr_tooltip": "Mostrar o QR Code"
|
"qr_tooltip": "Mostrar Código QR"
|
||||||
},
|
},
|
||||||
"password_score": {
|
"password_score": {
|
||||||
"password_strength": "Força da Senha"
|
"password_strength": "Robustez da Palavra-passe"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"item": {
|
"item": {
|
||||||
|
"attachments_list": {
|
||||||
|
"download": "Transferir",
|
||||||
|
"open_new_tab": "Abrir em novo separador"
|
||||||
|
},
|
||||||
"create_modal": {
|
"create_modal": {
|
||||||
|
"delete_photo": "Eliminar foto",
|
||||||
"item_description": "Descrição do Item",
|
"item_description": "Descrição do Item",
|
||||||
"item_name": "Nome do Item",
|
"item_name": "Nome do Item",
|
||||||
"item_photo": "Imagem do Item📷",
|
"item_photo": "Foto do Item 📷",
|
||||||
|
"item_quantity": "Quantidade do Item",
|
||||||
|
"parent_item": "Item Principal",
|
||||||
|
"rotate_photo": "Rodar foto",
|
||||||
|
"set_as_primary_photo": "Definir como foto { isPrimary, select, true {não-} false {} other {}}principal",
|
||||||
"title": "Criar Item",
|
"title": "Criar Item",
|
||||||
"upload_photos": "Upload de Imagens"
|
"toast": {
|
||||||
|
"already_creating": "Já está a criar um item",
|
||||||
|
"create_failed": "Não foi possível criar o item",
|
||||||
|
"create_success": "Item criado",
|
||||||
|
"failed_load_parent": "Falha ao carregar item principal - por favor, selecione manualmente",
|
||||||
|
"no_canvas_support": "O seu navegador não suporta operações de canvas",
|
||||||
|
"please_select_location": "Selecione uma localização.",
|
||||||
|
"rotate_failed": "Falha ao rodar imagem: { error }",
|
||||||
|
"rotate_process_failed": "Falha ao processar imagem rodada",
|
||||||
|
"some_photos_failed": "{count, plural, =0 {Sem fotos para carregar.} =1 {1 foto falhou ao carregar.} other {Algumas fotos falharam ao carregar.}}",
|
||||||
|
"upload_failed": "Falha ao carregar foto: { photoName }",
|
||||||
|
"upload_success": "{count, plural, =0 {Sem fotos carregadas.} =1 {Foto carregada com sucesso.} other {Todas as fotos foram carregadas com sucesso.}}",
|
||||||
|
"uploading_photos": "{count, plural, =0 {Sem fotos para carregar} =1 {A carregar 1 foto...} other {A carregar {count} fotos...}}"
|
||||||
|
},
|
||||||
|
"upload_photos": "Carregar Fotos",
|
||||||
|
"uploaded": "Foto Carregada"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"no_results": "Nenhum Resultado Encontrado",
|
||||||
|
"placeholder": "Selecionar...",
|
||||||
|
"search_placeholder": "Escreva para pesquisar..."
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"selectable": {
|
"selectable": {
|
||||||
"card": "Cartão",
|
"card": "Cartão",
|
||||||
"items": "Items",
|
"items": "Itens",
|
||||||
"no_items": "Nenhum item para mostrar",
|
"no_items": "Sem Itens para Exibir",
|
||||||
"table": "Tabela"
|
"table": "Tabela"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
|
"headers": "Cabeçalhos",
|
||||||
"page": "Página",
|
"page": "Página",
|
||||||
"rows_per_page": "Linhas por página"
|
"rows_per_page": "Linhas por página",
|
||||||
|
"table_settings": "Definições da Tabela"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -99,100 +137,154 @@
|
|||||||
"create_modal": {
|
"create_modal": {
|
||||||
"label_description": "Descrição da Etiqueta",
|
"label_description": "Descrição da Etiqueta",
|
||||||
"label_name": "Nome da Etiqueta",
|
"label_name": "Nome da Etiqueta",
|
||||||
"title": "Criar etiqueta"
|
"title": "Criar Etiqueta",
|
||||||
|
"toast": {
|
||||||
|
"already_creating": "Já está a criar uma etiqueta",
|
||||||
|
"create_failed": "Não foi possível criar etiqueta",
|
||||||
|
"create_success": "Etiqueta criada",
|
||||||
|
"label_name_too_long": "O nome da etiqueta não pode ter mais de 50 caracteres"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"select_labels": "Escolher Etiquetas"
|
"select_labels": "Selecionar Etiquetas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"create_modal": {
|
"create_modal": {
|
||||||
"location_description": "Descrição do Local",
|
"location_description": "Descrição da Localização",
|
||||||
"location_name": "Nome do Local",
|
"location_name": "Nome da Localização",
|
||||||
"title": "Criar Localização"
|
"title": "Criar Localização",
|
||||||
|
"toast": {
|
||||||
|
"already_creating": "Já está a criar uma localização",
|
||||||
|
"create_failed": "Não foi possível criar localização",
|
||||||
|
"create_success": "Localização criada"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"no_location_found": "Nenhum Local Encontrado",
|
"no_location_found": "Nenhuma localização encontrada",
|
||||||
"parent_location": "Localização ascendente",
|
"parent_location": "Localização principal",
|
||||||
"search_location": "Pesquisar Localizações",
|
"search_location": "Pesquisar Localizações",
|
||||||
"select_location": "Selecione uma localização"
|
"select_location": "Selecionar uma Localização"
|
||||||
|
},
|
||||||
|
"tree": {
|
||||||
|
"no_locations": "Sem localizações disponíveis. Adicione novas localizações através do botão \n`<`span class=\"link-primary\"`>`Criar`<`/span`>` na barra de navegação."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quick_menu": {
|
"quick_menu": {
|
||||||
"no_results": "Nenhum resultado encontrado.",
|
"no_results": "Nenhum resultado encontrado.",
|
||||||
"shortcut_hint": "Utilize as teclas numéricas para selecionar rapidamente uma ação."
|
"shortcut_hint": "Use as teclas numéricas para selecionar rapidamente uma ação."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"global": {
|
"global": {
|
||||||
"add": "Adicionar",
|
"add": "Adicionar",
|
||||||
"build": "Build: { build }",
|
"archived": "Arquivado",
|
||||||
|
"build": "Compilação: { build }",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"confirm": "Confirmar",
|
"confirm": "Confirmar",
|
||||||
"create": "Criar",
|
"create": "Criar",
|
||||||
"create_and_add": "Criar e adicionar outro",
|
"create_and_add": "Criar e Adicionar Outro",
|
||||||
|
"create_subitem": "Criar Subitem",
|
||||||
"created": "Criado",
|
"created": "Criado",
|
||||||
"delete": "Apagar",
|
"delete": "Eliminar",
|
||||||
|
"delete_confirm": "Tem a certeza de que deseja eliminar este item? ",
|
||||||
|
"demo_instance": "Esta é uma instância de demonstração",
|
||||||
"details": "Detalhes",
|
"details": "Detalhes",
|
||||||
"duplicate": "Duplicar",
|
"duplicate": "Duplicar",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"follow_dev": "Siga o programador",
|
"follow_dev": "Siga o Desenvolvedor",
|
||||||
"github": "Código Fonte no Github",
|
"footer": {
|
||||||
"items": "Items",
|
"api_link": "'<a href=\"https://homebox.software/en/api/\" target=\"_blank\">'API'</a>'",
|
||||||
"join_discord": "Juntar-se ao Discord",
|
"version_link": "<a href=\"https://github.com/sysadminsmedia/homebox/releases/tag/'{ version }\" target=\"_blank\"> Versão: { version } Compilação: { build } '</a>'"
|
||||||
"labels": "Marcadores",
|
},
|
||||||
|
"github": "Projeto GitHub",
|
||||||
|
"insured": "Com seguro",
|
||||||
|
"items": "Itens",
|
||||||
|
"join_discord": "Junte-se ao Discord",
|
||||||
|
"labels": "Etiquetas",
|
||||||
|
"loading": "A Carregar...",
|
||||||
"locations": "Localizações",
|
"locations": "Localizações",
|
||||||
"maintenance": "Manutenção",
|
"maintenance": "Manutenção",
|
||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"navigate": "Navegar",
|
"navigate": "Navegar",
|
||||||
"password": "Senha",
|
"password": "Palavra-passe",
|
||||||
"read_docs": "Ler os Documentos",
|
"quantity": "Quantidade",
|
||||||
|
"read_docs": "Ler a Documentação",
|
||||||
|
"return_home": "Voltar à Página Inicial",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"search": "Pesquisar",
|
"search": "Pesquisar",
|
||||||
"sign_out": "Terminar sessão",
|
"sign_out": "Terminar Sessão",
|
||||||
"submit": "Enviar",
|
"submit": "Submeter",
|
||||||
|
"unknown": "Desconhecido",
|
||||||
"update": "Atualizar",
|
"update": "Atualizar",
|
||||||
|
"updating": "A Atualizar",
|
||||||
"value": "Valor",
|
"value": "Valor",
|
||||||
"version": "Versão: { version }",
|
"version": "Versão: { version }",
|
||||||
"welcome": "Bem-vindo, {username}"
|
"welcome": "Bem-vindo, { username }"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"labels": "Etiquetas",
|
"labels": "Etiquetas",
|
||||||
"quick_statistics": "Estatísticas rápidas",
|
"quick_statistics": "Estatísticas Rápidas",
|
||||||
"recently_added": "Adicionados Recentemente",
|
"recently_added": "Recentemente Adicionados",
|
||||||
"storage_locations": "Locais de Armazenamento",
|
"storage_locations": "Localizações de Armazenamento",
|
||||||
"total_items": "Total de Items",
|
"total_items": "Total de Itens",
|
||||||
"total_labels": "Total de Etiquetas",
|
"total_labels": "Total de Etiquetas",
|
||||||
"total_locations": "Total de Locais",
|
"total_locations": "Total de Localizações",
|
||||||
"total_value": "Valores Totais"
|
"total_value": "Valor Total"
|
||||||
},
|
},
|
||||||
"index": {
|
"index": {
|
||||||
"disabled_registration": "Registo Desativado",
|
"disabled_registration": "Registo Desativado",
|
||||||
"dont_join_group": "Não quer juntar-se a um grupo?",
|
"dont_join_group": "Não quer juntar-se a um grupo?",
|
||||||
"joining_group": "Está a juntar-se a um grupo existente!",
|
"joining_group": "Está a Juntar-se a um Grupo Existente!",
|
||||||
"login": "Iniciar sessão",
|
"login": "Iniciar Sessão",
|
||||||
"register": "Registar",
|
"register": "Registar",
|
||||||
"remember_me": "Lembrar-me",
|
"remember_me": "Lembrar-me",
|
||||||
"set_email": "Qual é o seu email?",
|
"set_email": "Qual é o seu email?",
|
||||||
"set_name": "Como se chama?",
|
"set_name": "Qual é o seu nome?",
|
||||||
"set_password": "Defina a sua senha",
|
"set_password": "Defina a sua palavra-passe",
|
||||||
"tagline": "Acompanhe, organize e faça a gestão das suas coisas."
|
"tagline": "Acompanhe, organize e faça a gestão das suas coisas.",
|
||||||
|
"title": "Organize e Etiquete os Seus Objetos",
|
||||||
|
"toast": {
|
||||||
|
"invalid_email": "Endereço de email inválido",
|
||||||
|
"invalid_email_password": "Email ou palavra-passe inválidos",
|
||||||
|
"login_success": "Sessão iniciada com sucesso",
|
||||||
|
"problem_registering": "Problema ao registar utilizador",
|
||||||
|
"user_registered": "Utilizador registado"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"add": "Adicionar",
|
"add": "Adicionar",
|
||||||
"advanced": "Avançado",
|
"advanced": "Avançado",
|
||||||
"archived": "Arquivado",
|
"archived": "Arquivado",
|
||||||
"asset_id": "Número de Património",
|
"asset_id": "ID do Ativo",
|
||||||
"created_at": "Criado em",
|
"associated_with_multiple": "Este ID de Ativo está associado a vários itens",
|
||||||
"custom_fields": "Campos personalizados",
|
"attachment": "Anexo",
|
||||||
|
"attachments": "Anexos",
|
||||||
|
"changes_persisted_immediately": "Alterações nos anexos serão guardadas imediatamente",
|
||||||
|
"created_at": "Criado Em",
|
||||||
|
"custom_fields": "Campos Personalizados",
|
||||||
|
"delete_attachment_confirm": "Tem a certeza de que deseja eliminar este anexo?",
|
||||||
|
"delete_item_confirm": "Tem a certeza de que deseja eliminar este item?",
|
||||||
"description": "Descrição",
|
"description": "Descrição",
|
||||||
"details": "Detalhes",
|
"details": "Detalhes",
|
||||||
|
"drag_and_drop": "Arraste e largue ficheiros aqui ou clique para selecionar ficheiros",
|
||||||
|
"edit": {
|
||||||
|
"edit_attachment_dialog": {
|
||||||
|
"attachment_title": "Título do Anexo",
|
||||||
|
"attachment_type": "Tipo de Anexo",
|
||||||
|
"primary_photo": "Foto Principal",
|
||||||
|
"primary_photo_sub": "Esta opção só está disponível para fotos. Apenas uma foto pode ser principal. Ao selecionar esta, a atual será desmarcada.",
|
||||||
|
"select_type": "Selecionar um tipo",
|
||||||
|
"title": "Editar Anexo"
|
||||||
|
}
|
||||||
|
},
|
||||||
"edit_details": "Editar Detalhes",
|
"edit_details": "Editar Detalhes",
|
||||||
"field_selector": "Selector de Campos",
|
"field_selector": "Seletor de Campo",
|
||||||
"field_value": "Valor do Campo",
|
"field_value": "Valor do Campo",
|
||||||
"first": "Primeiro",
|
"first": "Primeiro",
|
||||||
"include_archive": "Incluir Items Arquivados",
|
"include_archive": "Incluir Itens Arquivados",
|
||||||
"insured": "Assegurado",
|
"insured": "Com seguro",
|
||||||
|
"invalid_asset_id": "ID de Ativo inválido",
|
||||||
"last": "Último",
|
"last": "Último",
|
||||||
"lifetime_warranty": "Garantia Vitalícia",
|
"lifetime_warranty": "Garantia Vitalícia",
|
||||||
"location": "Localização",
|
"location": "Localização",
|
||||||
@@ -201,46 +293,84 @@
|
|||||||
"manufacturer": "Fabricante",
|
"manufacturer": "Fabricante",
|
||||||
"model_number": "Número do Modelo",
|
"model_number": "Número do Modelo",
|
||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"negate_labels": "Negar etiquetas selecionadas",
|
"negate_labels": "Anular Etiquetas Selecionadas",
|
||||||
"next_page": "Página seguinte",
|
"next_page": "Próxima Página",
|
||||||
"no_results": "Nenhum item encontrado",
|
"no_attachments": "Sem anexos encontrados",
|
||||||
|
"no_results": "Nenhum Item Encontrado",
|
||||||
"notes": "Notas",
|
"notes": "Notas",
|
||||||
"only_with_photo": "Apenas artigos com fotografia",
|
"only_with_photo": "Apenas itens com foto",
|
||||||
"only_without_photo": "Apenas artigos sem fotografia",
|
"only_without_photo": "Apenas itens sem foto",
|
||||||
"options": "Opções",
|
"options": "Opções",
|
||||||
"order_by": "Ordenar por",
|
"order_by": "Ordenar Por",
|
||||||
"pages": "Página { page } de { totalPages }",
|
"pages": "Página { page } de { totalPages }",
|
||||||
|
"parent_item": "Item Principal",
|
||||||
"photo": "Foto",
|
"photo": "Foto",
|
||||||
"photos": "Fotos",
|
"photos": "Fotos",
|
||||||
"prev_page": "Página Anterior",
|
"prev_page": "Página Anterior",
|
||||||
"purchase_date": "Data de Compra",
|
"purchase_date": "Data de Compra",
|
||||||
"purchase_details": "Detalhes da Compra",
|
"purchase_details": "Detalhes da Compra",
|
||||||
"purchase_price": "Preço de Compra",
|
"purchase_price": "Preço de Compra",
|
||||||
"purchased_from": "Comprado em",
|
"purchased_from": "Comprado Em",
|
||||||
"quantity": "Quantidade",
|
"quantity": "Quantidade",
|
||||||
"query_id": "A consultar o número de identificação do ativo: { id }",
|
"query_id": "A Consultar Número de ID de Ativo: { id }",
|
||||||
"receipt": "Recibo",
|
"receipt": "Recibo",
|
||||||
"receipts": "Recibos",
|
"receipts": "Recibos",
|
||||||
"reset_search": "Fazer Reset à Pesquisa",
|
"reset_search": "Fazer Reset à Pesquisa",
|
||||||
"results": "{ total } Resultados",
|
"results": "{ total } Resultados",
|
||||||
|
"select_field": "Selecionar um campo",
|
||||||
"serial_number": "Número de Série",
|
"serial_number": "Número de Série",
|
||||||
"show_advanced_view_options": "Opções Avançadas",
|
"show_advanced_view_options": "Mostrar Opções Avançadas de Visualização",
|
||||||
"sold_at": "Vendido em",
|
"sold_at": "Vendido Em",
|
||||||
"sold_details": "Detalhes da Venda",
|
"sold_details": "Detalhes da Venda",
|
||||||
"sold_price": "Preço de Venda",
|
"sold_price": "Preço de Venda",
|
||||||
"sold_to": "Vendido a",
|
"sold_to": "Vendido a",
|
||||||
"tip_1": "Os filtros de localização e etiqueta usam a operação 'OU'. Se forem selecionados vários, apenas\n um será utilizado para a pesquisa.",
|
"sync_child_locations": "Sincronizar localizações dos subitens",
|
||||||
"tip_2": "As pesquisas prefixadas com '#' ' consultarão um ID de ativo (exemplo '#000-001')",
|
"tip_1": "Os filtros de localização e etiqueta usam a operação 'OU'. Se mais do que um for selecionado, \nbasta que um corresponda.",
|
||||||
"tip_3": "Os filtros dos campos usal a operação 'OU'. Se mais do que um for seleccionado apenas um será necessário\npara fazer match.",
|
"tip_2": "Pesquisas com o prefixo '#' irão procurar um ID de ativo (exemplo '#000-001')",
|
||||||
|
"tip_3": "Os filtros de campo usam a operação 'OU'. Se mais do que um for selecionado, \nbasta que um corresponda.",
|
||||||
"tips": "Dicas",
|
"tips": "Dicas",
|
||||||
"tips_sub": "Dicas de pesquisa",
|
"tips_sub": "Dicas de Pesquisa",
|
||||||
"updated_at": "Atualizado em",
|
"toast": {
|
||||||
|
"asset_not_found": "Ativo não encontrado",
|
||||||
|
"attachment_deleted": "Anexo eliminado",
|
||||||
|
"attachment_updated": "Anexo atualizado",
|
||||||
|
"attachment_uploaded": "Anexo carregado",
|
||||||
|
"child_items_location_no_longer_synced": "As localizações dos subitens deixarão de estar sincronizadas com este item.",
|
||||||
|
"child_items_location_synced": "As localizações dos subitens foram sincronizadas com este item",
|
||||||
|
"child_location_desync": "Ao mudar a localização, a sincronização com a localização principal será desfeita",
|
||||||
|
"error_loading_parent_data": "Ocorreu um erro ao carregar os dados principais",
|
||||||
|
"failed_adjust_quantity": "Falha ao ajustar a quantidade",
|
||||||
|
"failed_delete_attachment": "Falha ao eliminar anexo",
|
||||||
|
"failed_delete_item": "Falha ao eliminar item",
|
||||||
|
"failed_duplicate_item": "Falha ao duplicar item",
|
||||||
|
"failed_load_asset": "Falha ao carregar ativo",
|
||||||
|
"failed_load_item": "Falha ao carregar item",
|
||||||
|
"failed_load_items": "Falha ao carregar itens",
|
||||||
|
"failed_save": "Falha ao guardar item",
|
||||||
|
"failed_save_no_location": "Falha ao guardar item: nenhuma localização selecionada",
|
||||||
|
"failed_search_items": "Falha ao pesquisar itens",
|
||||||
|
"failed_update_attachment": "Falha ao atualizar anexo",
|
||||||
|
"failed_upload_attachment": "Falha ao carregar anexo",
|
||||||
|
"item_deleted": "Item eliminado",
|
||||||
|
"item_saved": "Item guardado",
|
||||||
|
"quantity_cannot_negative": "A quantidade não pode ser negativa",
|
||||||
|
"sync_child_location": "O item principal selecionado sincroniza as localizações dos sub-itens com a sua. A localização foi atualizada."
|
||||||
|
},
|
||||||
|
"updated_at": "Atualizado Em",
|
||||||
"warranty": "Garantia",
|
"warranty": "Garantia",
|
||||||
"warranty_details": "Detalhes de Garantia",
|
"warranty_details": "Detalhes da Garantia",
|
||||||
"warranty_expires": "Garantia expira a"
|
"warranty_expires": "Garantia expira a"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"no_results": "Nenhuma etiqueta encontrada",
|
"label_delete_confirm": "Tem a certeza de que deseja eliminar esta etiqueta? Esta ação não pode ser desfeita.",
|
||||||
|
"no_results": "Nenhuma Etiqueta Encontrada",
|
||||||
|
"toast": {
|
||||||
|
"failed_delete_label": "Falha ao eliminar etiqueta",
|
||||||
|
"failed_load_label": "Falha ao carregar etiqueta",
|
||||||
|
"failed_update_label": "Falha ao atualizar etiqueta",
|
||||||
|
"label_deleted": "Etiqueta eliminada",
|
||||||
|
"label_updated": "Etiqueta atualizada"
|
||||||
|
},
|
||||||
"update_label": "Atualizar Etiqueta"
|
"update_label": "Atualizar Etiqueta"
|
||||||
},
|
},
|
||||||
"languages": {
|
"languages": {
|
||||||
@@ -253,91 +383,228 @@
|
|||||||
"fi-FI": "Finlandês",
|
"fi-FI": "Finlandês",
|
||||||
"fr": "Francês",
|
"fr": "Francês",
|
||||||
"hu": "Húngaro",
|
"hu": "Húngaro",
|
||||||
|
"id-ID": "Indonésio",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
|
"ja-JP": "Japonês",
|
||||||
"ko-KR": "Coreano",
|
"ko-KR": "Coreano",
|
||||||
"nb-NO": "Bonkal Norueguês",
|
"lb-LU": "Luxemburguês (Luxemburgo)",
|
||||||
|
"lt-LT": "Lituano (Lituânia)",
|
||||||
|
"nb-NO": "Norueguês Bokmål",
|
||||||
"nl": "Holandês",
|
"nl": "Holandês",
|
||||||
"pl": "Polaco",
|
"pl": "Polaco",
|
||||||
"pt-BR": "Português do Brasil",
|
"pt-BR": "Português (Brasil)",
|
||||||
"pt-PT": "Português (Portugal)",
|
"pt-PT": "Português (Portugal)",
|
||||||
"ro-RO": "Romeno",
|
"ro-RO": "Romeno",
|
||||||
"ru": "Russo",
|
"ru": "Russo",
|
||||||
"sk-SK": "Eslováquio",
|
"sk-SK": "Eslovaco",
|
||||||
"sl": "Esloveno",
|
"sl": "Esloveno",
|
||||||
|
"sq-AL": "Albanês",
|
||||||
"sv": "Sueco",
|
"sv": "Sueco",
|
||||||
"ta-IN": "Tamil",
|
"ta-IN": "Tâmil",
|
||||||
"th-TH": "Tailandês",
|
"th-TH": "Tailandês",
|
||||||
"tr": "Turco",
|
"tr": "Turco",
|
||||||
|
"uk-UA": "Ucraniano",
|
||||||
"zh-CN": "Chinês (Simplificado)",
|
"zh-CN": "Chinês (Simplificado)",
|
||||||
"zh-HK": "Chinês (Hong Kong)",
|
"zh-HK": "Chinês (Hong Kong)",
|
||||||
"zh-MO": "Chinês (Macau)",
|
"zh-MO": "Chinês (Macau)",
|
||||||
"zh-TW": "Chinês (Tradicional)"
|
"zh-TW": "Chinês (Tradicional)"
|
||||||
},
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"no_results": "Nenhuma localização encontrada"
|
"child_locations": "Localizações Filho",
|
||||||
|
"collapse_tree": "Recolher Árvore",
|
||||||
|
"expand_tree": "Expandir Árvore",
|
||||||
|
"location_items_delete_confirm": "Tem a certeza de que deseja eliminar esta localização e todos os seus itens? Esta ação não pode ser desfeita.",
|
||||||
|
"no_results": "Nenhuma Localização Encontrada",
|
||||||
|
"toast": {
|
||||||
|
"failed_delete_location": "Falha ao eliminar localização",
|
||||||
|
"failed_load_location": "Falha ao carregar localização",
|
||||||
|
"failed_update_location": "Falha ao atualizar localização",
|
||||||
|
"location_deleted": "Localização eliminada",
|
||||||
|
"location_updated": "Localização atualizada"
|
||||||
|
},
|
||||||
|
"update_location": "Atualizar Localização"
|
||||||
|
},
|
||||||
|
"maintenance": {
|
||||||
|
"filter": {
|
||||||
|
"both": "Ambos",
|
||||||
|
"completed": "Concluído",
|
||||||
|
"scheduled": "Agendado"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"complete": "Concluir",
|
||||||
|
"create_first": "Criar a Sua Primeira Entrada",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"duplicate": "Duplicar",
|
||||||
|
"edit": "Editar",
|
||||||
|
"new": "Novo"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"completed_date": "Data de Conclusão",
|
||||||
|
"cost": "Custo",
|
||||||
|
"delete_confirmation": "Tem a certeza de que deseja eliminar esta entrada?",
|
||||||
|
"edit_action": "Atualizar",
|
||||||
|
"edit_title": "Editar Entrada",
|
||||||
|
"entry_name": "Nome da Entrada",
|
||||||
|
"new_action": "Criar",
|
||||||
|
"new_title": "Nova Entrada",
|
||||||
|
"notes": "Notas",
|
||||||
|
"scheduled_date": "Data Agendada"
|
||||||
|
},
|
||||||
|
"monthly_average": "Média Mensal",
|
||||||
|
"toast": {
|
||||||
|
"failed_to_create": "Falha ao criar entrada",
|
||||||
|
"failed_to_delete": "Falha ao eliminar entrada",
|
||||||
|
"failed_to_update": "Falha ao atualizar entrada"
|
||||||
|
},
|
||||||
|
"total_cost": "Custo Total",
|
||||||
|
"total_entries": "Total de Entradas"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"create_item": "Item / Ativo",
|
||||||
|
"create_label": "Etiqueta",
|
||||||
|
"create_location": "Localização",
|
||||||
|
"home": "Início",
|
||||||
|
"locations": "Localizações",
|
||||||
|
"maintenance": "Manutenção",
|
||||||
|
"profile": "Perfil",
|
||||||
|
"scanner": "Leitor",
|
||||||
|
"search": "Pesquisar",
|
||||||
|
"tools": "Ferramentas"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"active": "Ativo",
|
"active": "Ativo",
|
||||||
"change_password": "Alterar Senha",
|
"change_password": "Alterar Palavra-passe",
|
||||||
"currency_format": "Moeda",
|
"currency_format": "Formato de Moeda",
|
||||||
"current_password": "Senha Atual",
|
"current_password": "Palavra-passe Atual",
|
||||||
"delete_account": "Eliminar Conta",
|
"delete_account": "Eliminar Conta",
|
||||||
"delete_account_sub": "Eliminar a sua conta e todos os dados associados. Esta ação não pode ser revertida.",
|
"delete_account_confirm": "Tem a certeza de que deseja eliminar a sua conta? Se for o último membro do grupo, todos os dados serão eliminados. Esta ação não pode ser desfeita.",
|
||||||
|
"delete_account_sub": "Eliminar a sua conta e todos os dados associados. Esta ação não pode ser desfeita.",
|
||||||
|
"delete_notifier_confirm": "Tem a certeza de que deseja eliminar este notificador?",
|
||||||
|
"display_legacy_header": "{ currentValue, select, true {Desativar Cabeçalho Legacy} false {Ativar Cabeçalho Legacy} other {Sem Resultado} }",
|
||||||
"enabled": "Ativado",
|
"enabled": "Ativado",
|
||||||
"gen_invite": "Gerar link de convite",
|
"example": "Exemplo",
|
||||||
"group_settings": "Definições de Grupo",
|
"gen_invite": "Gerar Link de Convite",
|
||||||
"group_settings_sub": "Definições de Grupo Partilhadas. Pode ter de atualizar a página para algumas definições serem aplicadas.",
|
"group_settings": "Definições do Grupo",
|
||||||
|
"group_settings_sub": "Definições Partilhadas do Grupo. Pode ser necessário atualizar a página para aplicar algumas definições.",
|
||||||
"inactive": "Inativo",
|
"inactive": "Inativo",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"new_password": "Nova Senha",
|
"new_password": "Nova Palavra-passe",
|
||||||
"no_notifiers": "Nenhum notificador configurado",
|
"no_notifiers": "Nenhum notificador configurado",
|
||||||
"notifier_modal": "{ type, select, true {Editar} false {Criar} other {Outro}} Notificador",
|
"no_override": "Sem substituição",
|
||||||
|
"notifier_modal": "{ type, select, true {Editar} false {Criar} other {Outro} } Notificador",
|
||||||
"notifiers": "Notificadores",
|
"notifiers": "Notificadores",
|
||||||
"notifiers_sub": "Receba notificações para os próximos lembretes de manutenção",
|
"notifiers_sub": "Receba notificações para os próximos lembretes de manutenção",
|
||||||
|
"override_locale": "Substituir Data e Idioma da Moeda",
|
||||||
"test": "Testar",
|
"test": "Testar",
|
||||||
"theme_settings": "Definições do tema",
|
"theme_settings": "Definições do Tema",
|
||||||
"theme_settings_sub": "As configurações do tema são guardadas no armazenamento local do seu navegador. Pode alterar o tema em qualquer altura.\nSe encontrar algum problema com a alteração do tema, tente refrescar o browser.",
|
"theme_settings_sub": "As definições do tema são guardadas no armazenamento local do seu navegador. Pode alterá-lo a qualquer\n momento. Se tiver problemas ao definir o tema, tente atualizar a página.",
|
||||||
|
"toast": {
|
||||||
|
"account_deleted": "A sua conta foi eliminada.",
|
||||||
|
"failed_change_password": "Falha ao alterar palavra-passe.",
|
||||||
|
"failed_create_notifier": "Falha ao criar notificador.",
|
||||||
|
"failed_delete_account": "Falha ao eliminar a sua conta.",
|
||||||
|
"failed_delete_notifier": "Falha ao eliminar notificador.",
|
||||||
|
"failed_get_currencies": "Falha ao obter moedas",
|
||||||
|
"failed_test_notifier": "Falha ao testar notificador.",
|
||||||
|
"failed_update_group": "Falha ao atualizar grupo",
|
||||||
|
"failed_update_notifier": "Falha ao atualizar notificador.",
|
||||||
|
"group_updated": "Grupo atualizado",
|
||||||
|
"notifier_test_success": "Teste do notificador bem-sucedido.",
|
||||||
|
"password_changed": "Palavra-passe alterada com sucesso."
|
||||||
|
},
|
||||||
"update_group": "Atualizar Grupo",
|
"update_group": "Atualizar Grupo",
|
||||||
"update_language": "Atualizar Idioma",
|
"update_language": "Atualizar Idioma",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"user_profile": "Perfil de Utilizador",
|
"user_profile": "Perfil de Utilizador",
|
||||||
"user_profile_sub": "Convide utilizadores e faça a gestão da sua conta."
|
"user_profile_sub": "Convide utilizadores e faça a gestão da sua conta."
|
||||||
},
|
},
|
||||||
|
"reports": {
|
||||||
|
"label_generator": {
|
||||||
|
"asset_end": "Fim do Ativo",
|
||||||
|
"asset_start": "Início do Ativo",
|
||||||
|
"base_url": "URL Base",
|
||||||
|
"bordered_labels": "Etiquetas com Contorno",
|
||||||
|
"generate_page": "Gerar Página",
|
||||||
|
"input_placeholder": "Escreva aqui",
|
||||||
|
"instruction_1": "O Gerador de Etiquetas Homebox é uma ferramenta para ajudar a imprimir etiquetas para o seu inventário Homebox.\n Estas etiquetas são preparadas antecipadamente para poder imprimi-las em quantidade e aplicá-las depois",
|
||||||
|
"instruction_2": "Assim, estas etiquetas funcionam imprimindo um código QR com o URL e informação do AssetID na etiqueta. \n Se desativou os AssetIDs nas definições do Homebox, ainda pode usar esta ferramenta, mas os AssetIDs não irão referenciar nenhum item",
|
||||||
|
"instruction_3": "Esta funcionalidade está em fase inicial de desenvolvimento e pode mudar em futuras versões. \nSe tiver sugestões, envie através da '<a href=\"https://github.com/sysadminsmedia/homebox/discussions/53\">'Discussão no GitHub'</a>'",
|
||||||
|
"label_height": "Altura da Etiqueta",
|
||||||
|
"label_width": "Largura da Etiqueta",
|
||||||
|
"measure_type": "Tipo de Medida",
|
||||||
|
"page_bottom_padding": "Margem Inferior da Página",
|
||||||
|
"page_height": "Altura da Página",
|
||||||
|
"page_left_padding": "Margem Esquerda da Página",
|
||||||
|
"page_right_padding": "Margem Direita da Página",
|
||||||
|
"page_top_padding": "Margem Superior da Página",
|
||||||
|
"page_width": "Largura da Página",
|
||||||
|
"qr_code_example": "Exemplo de Código QR",
|
||||||
|
"tip_1": "As predefinições aqui são configuradas para as \n '<a href=\"https://www.avery.com/templates/5260\">'folhas de etiquetas Avery 5260'</a>'. \n Se estiver a usar outro tipo de folha, terá de ajustar as definições para se ajustarem às suas folhas.",
|
||||||
|
"tip_2": "Se estiver a personalizar a folha, as dimensões estão em polegadas. Ao criar a folha 5260, percebi que as dimensões\n usadas nos seus modelos não correspondiam ao necessário para imprimir corretamente dentro das caixas.\n '<b>'Prepare-se para alguma tentativa e erro.'</b>'",
|
||||||
|
"tip_3": "Ao imprimir, certifique-se de que: \n'<ol><li>'Defina as margens como 0 ou Nenhuma'</li><li>'Define o escalamento para 100%'</li><li>'Desativa a impressão frente e verso'</li><li>'Imprime uma página de teste antes de imprimir várias'</li></ol>'",
|
||||||
|
"tips": "Dicas",
|
||||||
|
"title": "Gerador de Etiquetas",
|
||||||
|
"toast": {
|
||||||
|
"page_too_small_card": "O tamanho da página é demasiado pequeno para o tamanho do cartão"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scanner": {
|
||||||
|
"error": "Ocorreu um erro ao digitalizar",
|
||||||
|
"invalid_url": "URL de código de barras inválido",
|
||||||
|
"no_sources": "Sem fontes de vídeo disponíveis",
|
||||||
|
"permission_denied": "Permissão da câmara negada. Permita o acesso à câmara nas definições do navegador",
|
||||||
|
"select_video_source": "Escolha uma fonte de vídeo",
|
||||||
|
"title": "Leitor",
|
||||||
|
"unsupported": "A API Media Stream não é suportada sem HTTPS"
|
||||||
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
"actions": "Ações de Inventário",
|
"actions": "Ações de Inventário",
|
||||||
"actions_set": {
|
"actions_set": {
|
||||||
"ensure_ids": "Garantir IDs de Ativos",
|
"ensure_ids": "Garantir IDs dos Ativos",
|
||||||
"ensure_ids_button": "Garantir IDs de Ativos",
|
"ensure_ids_button": "Garantir IDs dos Ativos",
|
||||||
"ensure_ids_sub": "Garante que todos os items no inventário tenham um campo asset_id válido. Para tal, procuramos o valor mais alto do asset_id na base de dados e aplicamos o próximo valor a cada item que não tem um asset_id definido, ordenando pelo campo created_at.",
|
"ensure_ids_confirm": "Tem a certeza de que deseja garantir que todos os ativos têm um ID? Isto pode demorar e não pode ser desfeito.",
|
||||||
"ensure_import_refs": "Garantir Refs de Importação",
|
"ensure_ids_sub": "Garante que todos os itens do inventário têm um campo asset_id válido. É feito encontrando o maior asset_id atual na base de dados e atribuindo o próximo valor a cada item sem asset_id definido. A ordem é baseada no campo created_at.",
|
||||||
"ensure_import_refs_button": "Garantir Refs de Importação",
|
"ensure_import_refs": "Garantir Referências de Importação",
|
||||||
"ensure_import_refs_sub": "Garante que todos os itens do seu inventário tenham um campo import_ref válido",
|
"ensure_import_refs_button": "Garantir Referências de Importação",
|
||||||
"set_primary_photo": "Definir foto principal",
|
"ensure_import_refs_sub": "Garante que todos os itens do inventário têm um campo import_ref válido. É feito gerando aleatoriamente uma sequência de 8 caracteres para cada item sem import_ref definido.",
|
||||||
"set_primary_photo_button": "Definir foto principal",
|
"set_primary_photo": "Definir Foto Principal",
|
||||||
"set_primary_photo_sub": "Na versão v0.10.0 do Homebox, o campo de imagem principal foi adicionado aos anexos do tipo foto. Esta acção vai definir a imagem principal para a primeira imagem na lista de anexos na base de dados, se ainda não estiver definido. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'GitHub PR #576'</a>'",
|
"set_primary_photo_button": "Definir Foto Principal",
|
||||||
"zero_datetimes": "Zero Item Data e Hora",
|
"set_primary_photo_confirm": "Tem a certeza de que deseja definir fotos principais? Isto pode demorar e não pode ser desfeito.",
|
||||||
"zero_datetimes_button": "Zero Data Horas do Item"
|
"set_primary_photo_sub": "Na versão v0.10.0 do Homebox foi adicionado o campo de imagem principal aos anexos do tipo foto. Esta ação define a imagem principal como a primeira imagem no array de anexos da base de dados, caso ainda não esteja definida. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'Ver PR #576 no GitHub'</a>'",
|
||||||
|
"zero_datetimes": "Zerar Datas e Horas dos Itens",
|
||||||
|
"zero_datetimes_button": "Zerar Datas e Horas dos Itens",
|
||||||
|
"zero_datetimes_confirm": "Tem a certeza de que deseja repor todos os valores de data e hora? Isto pode demorar e não pode ser desfeito.",
|
||||||
|
"zero_datetimes_sub": "Repõe o valor da hora de todos os campos de data/hora do inventário para o início do dia. Isto corrige um erro inicial no site que causava problemas na apresentação correta das datas. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/issues/236\" target=\"_blank\">'Ver Issue #236 no GitHub para mais detalhes.'</a>'"
|
||||||
},
|
},
|
||||||
"actions_sub": "Aplicar Ações ao seu inventário em massa. Estas acções são irreversíveis. '<b>'Cuidado.'</b>'",
|
"actions_sub": "Aplicar Ações em Massa no inventário. Estas ações são irreversíveis. '<b>'Tenha cuidado.'</b>'",
|
||||||
"import_export": "Importar/Exportar",
|
"import_export": "Importar/Exportar",
|
||||||
"import_export_set": {
|
"import_export_set": {
|
||||||
"export": "Exportar Inventário",
|
"export": "Exportar Inventário",
|
||||||
"export_button": "Exportar Inventário",
|
"export_button": "Exportar Inventário",
|
||||||
"export_sub": "Exporta o formato CSV padrão para o Homebox. Isto vai exportar todos os items do inventário.",
|
"export_sub": "Exporta o formato CSV padrão para o Homebox. Isto vai exportar todos os items do inventário.",
|
||||||
"import": "Importar inventário",
|
"import": "Importar Inventário",
|
||||||
"import_button": "Importar inventário",
|
"import_button": "Importar Inventário",
|
||||||
|
"import_ref_confirm": "Tem a certeza de que deseja garantir que todos os ativos têm um import_ref? Isto pode demorar e não pode ser desfeito.",
|
||||||
"import_sub": "Importa o formato standard do CSV para o Homebox. Sem uma coluna '<code>'HB.import_ref'</code>' , isto '<b>'não '</b>' vai escrever por cima de items existentes no inventário, apenas adiciona novos. As linhas com uma coluna '<code>'HB.import_ref'</code>' vão ser fundidas com os items que tenham o mesmo import_ref, se existirem."
|
"import_sub": "Importa o formato standard do CSV para o Homebox. Sem uma coluna '<code>'HB.import_ref'</code>' , isto '<b>'não '</b>' vai escrever por cima de items existentes no inventário, apenas adiciona novos. As linhas com uma coluna '<code>'HB.import_ref'</code>' vão ser fundidas com os items que tenham o mesmo import_ref, se existirem."
|
||||||
},
|
},
|
||||||
"import_export_sub": "Importe e exporte o seu inventário de e para um ficheiro CSV. Isto é útil para migrar o inventário para uma nova instância do Homebox.",
|
"import_export_sub": "Importar e exportar o seu inventário de/para um ficheiro CSV. Útil para migração entre instâncias do Homebox.",
|
||||||
"reports": "Relatórios",
|
"reports": "Relatórios",
|
||||||
"reports_set": {
|
"reports_set": {
|
||||||
"asset_labels": "Etiquetas de ID do Ativo",
|
"asset_labels": "Etiquetas de ID do Ativo",
|
||||||
"asset_labels_button": "Gerador de Rótulos",
|
"asset_labels_button": "Gerador de Etiquetas",
|
||||||
"asset_labels_sub": "Gera um PDF imprimível de etiquetas para um um conjunto de Activos. Estas não são específicas do seu inventário por isso pode imprimi-las com antecedência e aplicar as mesmas ao inventário quando o receber.",
|
"asset_labels_sub": "Gera um PDF imprimível de etiquetas para um conjunto de IDs de Activos. Estas não são específicas do seu inventário por isso pode imprimi-las com antecedência e aplicar as mesmas ao inventário quando o receber.",
|
||||||
"bill_of_materials": "Lista de materiais",
|
"bill_of_materials": "Lista de Materiais",
|
||||||
"bill_of_materials_button": "Gerar BOM",
|
"bill_of_materials_button": "Gerar Lista de Materiais",
|
||||||
"bill_of_materials_sub": "Gera um ficheiro CSV (Valores Separados por Vírgulas) que pode ser importado para uma folha de cálculo. Isto é um resumo do seu inventário com a informação do item e do preço."
|
"bill_of_materials_sub": "Gera um ficheiro CSV (Valores Separados por Vírgulas) que pode ser importado para uma folha de cálculo. Isto é um resumo do seu inventário com a informação do item e do preço."
|
||||||
},
|
},
|
||||||
"reports_sub": "Gere relatórios diferentes para o seu inventário."
|
"reports_sub": "Gerar diferentes relatórios para o seu inventário.",
|
||||||
|
"toast": {
|
||||||
|
"asset_success": "{ results } ativos foram atualizados.",
|
||||||
|
"failed_ensure_ids": "Falha ao garantir IDs dos ativos.",
|
||||||
|
"failed_ensure_import_refs": "Falha ao garantir referências de importação.",
|
||||||
|
"failed_set_primary_photos": "Falha ao definir fotos principais.",
|
||||||
|
"failed_zero_datetimes": "Falha ao repor datas e horas."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,7 +325,7 @@
|
|||||||
if (preferences.value.showEmpty) {
|
if (preferences.value.showEmpty) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return item.value?.purchaseFrom || item.value?.purchasePrice !== 0;
|
return item.value?.purchaseFrom || item.value?.purchasePrice !== 0 || validDate(item.value?.purchaseTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
const purchaseDetails = computed<Details>(() => {
|
const purchaseDetails = computed<Details>(() => {
|
||||||
@@ -358,7 +358,7 @@
|
|||||||
if (preferences.value.showEmpty) {
|
if (preferences.value.showEmpty) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return item.value?.soldTo || item.value?.soldPrice !== 0;
|
return item.value?.soldTo || item.value?.soldPrice !== 0 || validDate(item.value?.soldTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
const soldDetails = computed<Details>(() => {
|
const soldDetails = computed<Details>(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user