1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-12-21 13:38:56 +02:00

Merge branch 'main' into gh-4435-typo

This commit is contained in:
Mattermost Build 2023-03-08 22:30:43 +02:00 committed by GitHub
commit 0cb768e088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 364 additions and 59 deletions

View File

@ -227,7 +227,7 @@ func jsonStringResponse(w http.ResponseWriter, code int, message string) { //nol
fmt.Fprint(w, message)
}
func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) {
func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { //nolint:unparam
setResponseHeader(w, "Content-Type", "application/json")
w.WriteHeader(code)
_, _ = w.Write(json)

View File

@ -123,37 +123,12 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("teamID", board.TeamID)
auditRec.AddMeta("filename", filename)
fileInfo, err := a.app.GetFileInfo(filename)
fileInfo, fileReader, err := a.app.GetFile(board.TeamID, boardID, filename)
if err != nil && !model.IsErrNotFound(err) {
a.errorResponse(w, r, err)
return
}
if fileInfo != nil && fileInfo.Archived {
fileMetadata := map[string]interface{}{
"archived": true,
"name": fileInfo.Name,
"size": fileInfo.Size,
"extension": fileInfo.Extension,
}
data, jsonErr := json.Marshal(fileMetadata)
if jsonErr != nil {
a.logger.Error("failed to marshal archived file metadata", mlog.String("filename", filename), mlog.Err(jsonErr))
a.errorResponse(w, r, jsonErr)
return
}
jsonBytesResponse(w, http.StatusBadRequest, data)
return
}
fileReader, err := a.app.GetFileReader(board.TeamID, boardID, filename)
if err != nil && !errors.Is(err, app.ErrFileNotFound) {
a.errorResponse(w, r, err)
return
}
if errors.Is(err, app.ErrFileNotFound) && board.ChannelID != "" {
// prior to moving from workspaces to teams, the filepath was constructed from
// workspaceID, which is the channel ID in plugin mode.

View File

@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"github.com/mattermost/focalboard/server/model"
mmModel "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/focalboard/server/utils"
@ -28,7 +29,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
createdFilename := utils.NewID(utils.IDTypeNone)
fullFilename := fmt.Sprintf(`%s%s`, createdFilename, fileExtension)
filePath := filepath.Join(teamID, rootID, fullFilename)
filePath := filepath.Join(utils.GetBaseFilePath(), fullFilename)
fileSize, appErr := a.filesBackend.WriteFile(reader, filePath)
if appErr != nil {
@ -45,7 +46,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
CreateAt: now,
UpdateAt: now,
DeleteAt: 0,
Path: emptyString,
Path: filePath,
ThumbnailPath: emptyString,
PreviewPath: emptyString,
Name: filename,
@ -59,6 +60,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
Content: "",
RemoteId: nil,
}
err := a.store.SaveFileInfo(fileInfo)
if err != nil {
return "", err
@ -77,6 +79,7 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
// will be the fileinfo id.
parts := strings.Split(filename, ".")
fileInfoID := parts[0][1:]
fileInfo, err := a.store.GetFileInfo(fileInfoID)
if err != nil {
return nil, err
@ -85,6 +88,40 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
return fileInfo, nil
}
func (a *App) GetFile(teamID, rootID, fileName string) (*mmModel.FileInfo, filestore.ReadCloseSeeker, error) {
fileInfo, err := a.GetFileInfo(fileName)
if err != nil && !model.IsErrNotFound(err) {
a.logger.Error("111")
return nil, nil, err
}
var filePath string
if fileInfo != nil && fileInfo.Path != "" {
filePath = fileInfo.Path
} else {
filePath = filepath.Join(teamID, rootID, fileName)
}
exists, err := a.filesBackend.FileExists(filePath)
if err != nil {
a.logger.Error(fmt.Sprintf("GetFile: Failed to check if file exists as path. Path: %s, error: %e", filePath, err))
return nil, nil, err
}
if !exists {
return nil, nil, ErrFileNotFound
}
reader, err := a.filesBackend.Reader(filePath)
if err != nil {
a.logger.Error(fmt.Sprintf("GetFile: Failed to get file reader of existing file at path: %s, error: %e", filePath, err))
return nil, nil, err
}
return fileInfo, reader, nil
}
func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
filePath := filepath.Join(teamID, rootID, filename)
exists, err := a.filesBackend.FileExists(filePath)

View File

@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
@ -195,8 +196,8 @@ func TestSaveFile(t *testing.T) {
writeFileFunc := func(reader io.Reader, path string) int64 {
paths := strings.Split(path, string(os.PathSeparator))
assert.Equal(t, "1", paths[0])
assert.Equal(t, testBoardID, paths[1])
assert.Equal(t, "boards", paths[0])
assert.Equal(t, time.Now().Format("20060102"), paths[1])
fileName = paths[2]
return int64(10)
}
@ -219,8 +220,8 @@ func TestSaveFile(t *testing.T) {
writeFileFunc := func(reader io.Reader, path string) int64 {
paths := strings.Split(path, string(os.PathSeparator))
assert.Equal(t, "1", paths[0])
assert.Equal(t, "test-board-id", paths[1])
assert.Equal(t, "boards", paths[0])
assert.Equal(t, time.Now().Format("20060102"), paths[1])
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
return int64(10)
}
@ -243,8 +244,8 @@ func TestSaveFile(t *testing.T) {
writeFileFunc := func(reader io.Reader, path string) int64 {
paths := strings.Split(path, string(os.PathSeparator))
assert.Equal(t, "1", paths[0])
assert.Equal(t, "test-board-id", paths[1])
assert.Equal(t, "boards", paths[0])
assert.Equal(t, time.Now().Format("20060102"), paths[1])
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
return int64(10)
}
@ -304,3 +305,80 @@ func TestGetFileInfo(t *testing.T) {
assert.Nil(t, fetchedFileInfo)
})
}
func TestGetFile(t *testing.T) {
th, _ := SetupTestHelper(t)
t.Run("when FileInfo exists", func(t *testing.T) {
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
Id: "fileInfoID",
Path: "/path/to/file/fileName.txt",
}, nil)
mockedFileBackend := &mocks.FileBackend{}
th.App.filesBackend = mockedFileBackend
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
readerFunc := func(path string) filestore.ReadCloseSeeker {
return mockedReadCloseSeek
}
readerErrorFunc := func(path string) error {
return nil
}
mockedFileBackend.On("Reader", "/path/to/file/fileName.txt").Return(readerFunc, readerErrorFunc)
mockedFileBackend.On("FileExists", "/path/to/file/fileName.txt").Return(true, nil)
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
assert.NoError(t, err)
assert.NotNil(t, fileInfo)
assert.NotNil(t, seeker)
})
t.Run("when FileInfo doesn't exist", func(t *testing.T) {
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(nil, nil)
mockedFileBackend := &mocks.FileBackend{}
th.App.filesBackend = mockedFileBackend
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
readerFunc := func(path string) filestore.ReadCloseSeeker {
return mockedReadCloseSeek
}
readerErrorFunc := func(path string) error {
return nil
}
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
assert.NoError(t, err)
assert.Nil(t, fileInfo)
assert.NotNil(t, seeker)
})
t.Run("when FileInfo exists but FileInfo.Path is not set", func(t *testing.T) {
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
Id: "fileInfoID",
Path: "",
}, nil)
mockedFileBackend := &mocks.FileBackend{}
th.App.filesBackend = mockedFileBackend
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
readerFunc := func(path string) filestore.ReadCloseSeeker {
return mockedReadCloseSeek
}
readerErrorFunc := func(path string) error {
return nil
}
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
assert.NoError(t, err)
assert.NotNil(t, fileInfo)
assert.NotNil(t, seeker)
})
}

View File

@ -380,6 +380,8 @@ func (c *Client) GetCards(boardID string, page int, perPage int) ([]*model.Card,
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
var cards []*model.Card
if err := json.NewDecoder(r.Body).Decode(&cards); err != nil {
return nil, BuildErrorResponse(r, err)
@ -398,6 +400,8 @@ func (c *Client) PatchCard(cardID string, cardPatch *model.CardPatch, disableNot
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
var cardNew *model.Card
if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {
return nil, BuildErrorResponse(r, err)
@ -412,6 +416,8 @@ func (c *Client) GetCard(cardID string) (*model.Card, *Response) {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
var card *model.Card
if err := json.NewDecoder(r.Body).Decode(&card); err != nil {
return nil, BuildErrorResponse(r, err)
@ -450,6 +456,7 @@ func (c *Client) DeleteCategory(teamID, categoryID string) *Response {
return BuildErrorResponse(r, err)
}
defer closeBody(r)
return BuildResponse(r)
}
@ -1049,6 +1056,7 @@ func (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {
return BuildErrorResponse(r, err)
}
defer closeBody(r)
return BuildResponse(r)
}
@ -1058,5 +1066,6 @@ func (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {
return BuildErrorResponse(r, err)
}
defer closeBody(r)
return BuildResponse(r)
}

View File

@ -22,6 +22,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
"extension",
"size",
"delete_at",
"path",
"archived",
).
Values(
@ -31,6 +32,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
fileInfo.Extension,
fileInfo.Size,
fileInfo.DeleteAt,
fileInfo.Path,
false,
)
@ -57,6 +59,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
"extension",
"size",
"archived",
"path",
).
From(s.tablePrefix + "file_info").
Where(sq.Eq{"Id": id})
@ -73,6 +76,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
&fileInfo.Extension,
&fileInfo.Size,
&fileInfo.Archived,
&fileInfo.Path,
)
if err != nil {

View File

@ -0,0 +1 @@
{{ addColumnIfNeeded "file_info" "path" "varchar(512)" "" }}

View File

@ -2,6 +2,7 @@ package utils
import (
"encoding/json"
"path"
"reflect"
"time"
@ -120,3 +121,7 @@ func DedupeStringArr(arr []string) []string {
return dedupedArr
}
func GetBaseFilePath() string {
return path.Join("boards", time.Now().Format("20060102"))
}

View File

@ -1,14 +1,42 @@
{
"AppBar.Tooltip": "Veksle lenkede tavler",
"Attachment.Attachment-title": "Vedlegg",
"AttachmentBlock.DeleteAction": "slett",
"AttachmentBlock.addElement": "legg til {type}",
"AttachmentBlock.delete": "Vedlegg slettet.",
"AttachmentBlock.failed": "Denne filen kunne ikke lastes opp fordi størrelsesgrensen er nådd.",
"AttachmentBlock.upload": "Vedlegg lastes opp.",
"AttachmentBlock.uploadSuccess": "Vedlegg lastet opp.",
"AttachmentElement.delete-confirmation-dialog-button-text": "Slett",
"AttachmentElement.download": "Last ned",
"AttachmentElement.upload-percentage": "Laster opp ...({uploadPercent}%)",
"BoardComponent.add-a-group": "+ Legg til gruppe",
"BoardComponent.delete": "Slett",
"BoardComponent.hidden-columns": "Skjulte kolonner",
"BoardComponent.hide": "Skjul",
"BoardComponent.new": "+ Ny",
"BoardComponent.no-property": "Ingen {property}",
"BoardComponent.no-property-title": "Elementer med en tom {property} område kommer hit. Denne kolonnen kan ikke fjernes.",
"BoardComponent.no-property-title": "Elementer med tom {property} atributt legges her. Denne kolonnen kan ikke fjernes.",
"BoardComponent.show": "Vis",
"BoardMember.schemeAdmin": "Admin",
"BoardMember.schemeCommenter": "Kommentator",
"BoardMember.schemeEditor": "Redaktør",
"BoardMember.schemeNone": "Ingen",
"BoardMember.schemeViewer": "Viser",
"BoardMember.unlinkChannel": "Fjern lenke",
"BoardPage.newVersion": "En ny versjon av Boards er tilgjengelig, klikk her for å laste inn på nytt.",
"BoardPage.syncFailed": "Tavle kan slettes eller adgangen trekkes tilbake.",
"BoardTemplateSelector.add-template": "Lag ny mal",
"BoardTemplateSelector.create-empty-board": "Opprett tom tavle",
"BoardTemplateSelector.delete-template": "Slett",
"BoardTemplateSelector.description": "Legg til en tavle til sidestolpen med hvilken mal du vil fra listen under, eller start med en helt tom tavle.",
"BoardTemplateSelector.edit-template": "Rediger",
"BoardTemplateSelector.plugin.no-content-description": "Legg til en tavle i sidestolpen med hvilken mal du vil, eller start med en tom tavle.",
"BoardTemplateSelector.plugin.no-content-title": "Lag ny tavle",
"BoardTemplateSelector.title": "Lag ny tavle",
"BoardTemplateSelector.use-this-template": "Bruk denne malen",
"BoardsSwitcher.Title": "Finn tavle",
"BoardsUnfurl.Limited": "Flere detaljer er skjult fordi kortet er arkivert",
"BoardsUnfurl.Remainder": "+{remainder} mer",
"BoardsUnfurl.Updated": "Oppdatert {time}",
"Calculations.Options.average.displayName": "Gjennomsnitt",
@ -16,11 +44,142 @@
"Calculations.Options.count.displayName": "Antall",
"Calculations.Options.count.label": "Antall",
"Calculations.Options.countChecked.displayName": "Avkrysset",
"Calculations.Options.countChecked.label": "Antall avsjekket",
"Calculations.Options.countChecked.label": "Antall valgt",
"Calculations.Options.countUnchecked.displayName": "Ikke avmerket",
"Calculations.Options.countUnchecked.label": "Antall Ikke Avmerket",
"Calculations.Options.countUnchecked.label": "Antall ikke valgt",
"Calculations.Options.countUniqueValue.displayName": "Unik",
"Calculations.Options.countUniqueValue.label": "Antall Unike Verdier",
"Calculations.Options.countUniqueValue.label": "Antall unike verdier",
"Calculations.Options.countValue.displayName": "Verdier",
"Calculations.Options.countValue.label": "Antall Verdier"
"Calculations.Options.countValue.label": "Antall verdier",
"Calculations.Options.dateRange.displayName": "Tidsrom",
"Calculations.Options.dateRange.label": "Tidsrom",
"Calculations.Options.earliest.displayName": "Tiligst",
"Calculations.Options.earliest.label": "Tiligst",
"Calculations.Options.latest.displayName": "Senest",
"Calculations.Options.latest.label": "Senest",
"Calculations.Options.max.displayName": "Maks",
"Calculations.Options.max.label": "Maks",
"Calculations.Options.median.displayName": "Median",
"Calculations.Options.median.label": "Median",
"Calculations.Options.min.displayName": "Min",
"Calculations.Options.min.label": "Min",
"Calculations.Options.none.displayName": "Kalkulèr",
"Calculations.Options.none.label": "Ingen",
"Calculations.Options.percentChecked.displayName": "Valgt",
"Calculations.Options.percentChecked.label": "Prosent valgt",
"Calculations.Options.percentUnchecked.displayName": "Ikke valgt",
"Calculations.Options.percentUnchecked.label": "Prosent ikke valgt",
"Calculations.Options.range.displayName": "Tidsrom",
"Calculations.Options.range.label": "Tidsrom",
"Calculations.Options.sum.displayName": "Sum",
"Calculations.Options.sum.label": "Sum",
"CalendarCard.untitled": "Uten navn",
"CardActionsMenu.copiedLink": "Kopiert!",
"CardActionsMenu.copyLink": "Kopier lenke",
"CardActionsMenu.delete": "Slett",
"CardActionsMenu.duplicate": "Dupliser",
"CardBadges.title-checkboxes": "Avkrysningsbokser",
"CardBadges.title-comments": "Kommentarer",
"CardBadges.title-description": "Dette kortet har en beskrivelsestekst",
"CardDetail.Attach": "Legg ved",
"CardDetail.Follow": "Følg",
"CardDetail.Following": "Følger",
"CardDetail.add-content": "Legg til innhold",
"CardDetail.add-icon": "Legg til ikon",
"CardDetail.add-property": "+ Legg til en verdi",
"CardDetail.addCardText": "legg inn tekst i kortet",
"CardDetail.limited-body": "Oppgrader til vår profesjonelle eller bedriftsplan.",
"CardDetail.limited-button": "Oppgrader",
"CardDetail.limited-title": "Dette kortet er skjult",
"CardDetail.moveContent": "Flytt innholdet",
"CardDetail.new-comment-placeholder": "Legg til kommentar ...",
"CardDetailProperty.confirm-delete-heading": "Bekreft sletting av verdi",
"CardDetailProperty.confirm-delete-subtext": "Er du sikker på at du vil slette verdien \"{propertyName}\"? Dette vil fjerne verdien fra alle kortene på denne tavlen.",
"CardDetailProperty.confirm-property-name-change-subtext": "Er du sikker på at du vil endre verdien \"{propertyName}\" {customText}? Dette vil påvirke verdien på {numOfCards} kort på denne tavlen, og kan forårsake at du mister informasjon.",
"CardDetailProperty.confirm-property-type-change": "Bekreft endring av verditype",
"CardDetailProperty.delete-action-button": "Slett",
"CardDetailProperty.property-change-action-button": "Endre verdi",
"CardDetailProperty.property-changed": "Verdi endret!",
"CardDetailProperty.property-deleted": "Fjernet {propertyName}!",
"CardDetailProperty.property-name-change-subtext": "type fra \"{oldPropType}\" til \"{newPropType}\"",
"CardDetial.limited-link": "Lær mer om våre planer.",
"CardDialog.delete-confirmation-dialog-attachment": "Bekreft sletting av vedlegg",
"CardDialog.delete-confirmation-dialog-button-text": "Slett",
"CardDialog.delete-confirmation-dialog-heading": "Bekreft sletting av kort",
"CardDialog.editing-template": "Du redigerer en mal.",
"CardDialog.nocard": "Dette kortet eksisterer ikke eller du har ikke tilgang.",
"Categories.CreateCategoryDialog.CancelText": "Avbryt",
"Categories.CreateCategoryDialog.CreateText": "Opprett",
"Categories.CreateCategoryDialog.Placeholder": "Navngi kategorien",
"Categories.CreateCategoryDialog.UpdateText": "Oppdater",
"CenterPanel.Login": "Logg inn",
"CenterPanel.Share": "Del",
"ChannelIntro.CreateBoard": "Opprett tavle",
"CloudMessage.cloud-server": "Få din egen gratis skytjener.",
"ColorOption.selectColor": "Velg {color} farge",
"Comment.delete": "Slett",
"CommentsList.send": "Send",
"ConfirmPerson.empty": "Tom",
"ConfirmPerson.search": "Søk ...",
"ConfirmationDialog.cancel-action": "Avbryt",
"ConfirmationDialog.confirm-action": "Bekreft",
"ContentBlock.Delete": "Slett",
"ContentBlock.DeleteAction": "slett",
"ContentBlock.addElement": "legg til {type}",
"ContentBlock.checkbox": "avkrysningsboks",
"ContentBlock.divider": "avdeler",
"ContentBlock.editCardCheckbox": "krysset-boks",
"ContentBlock.editCardCheckboxText": "rediger kort tekst",
"ContentBlock.editCardText": "rediger kort tekst",
"ContentBlock.editText": "Rediger tekst ...",
"ContentBlock.image": "bilde",
"ContentBlock.insertAbove": "Sett inn over",
"ContentBlock.moveBlock": "flytt kort innhold",
"ContentBlock.moveDown": "Flytt ned",
"ContentBlock.moveUp": "Flytt opp",
"ContentBlock.text": "tekst",
"DateRange.clear": "Tøm",
"DateRange.empty": "Tom",
"DateRange.endDate": "Sluttdato",
"DateRange.today": "I dag",
"DeleteBoardDialog.confirm-cancel": "Avbryt",
"DeleteBoardDialog.confirm-delete": "Slett",
"DeleteBoardDialog.confirm-info": "Er du sikker på at du vil slette tavlen \"{boardTitle}\"? Dette vil slette alle kortene på tavlen.",
"DeleteBoardDialog.confirm-info-template": "Er du sikker på at du vil slette tavlemalen \"{boardTitle}\"?",
"DeleteBoardDialog.confirm-tite": "Bekreft sletting av tavle",
"DeleteBoardDialog.confirm-tite-template": "Bekreft sletting av tavlemal",
"Dialog.closeDialog": "Lukk",
"EditableDayPicker.today": "I dag",
"Error.mobileweb": "Støtte for bruk i nettleser på mobil er i tidlig beta. Alt vil ikke fungere.",
"Error.websocket-closed": "Problemer med kobling til tjeneren. Sjekk konfigurasjonen hvis problemet vedvarer.",
"Filter.contains": "inneholder",
"Filter.ends-with": "ender med",
"Filter.includes": "inkluderer",
"Filter.is": "er",
"Filter.is-empty": "er tom",
"Filter.is-not-empty": "er ikke tom",
"Filter.is-not-set": "er ikke satt",
"Filter.is-set": "er satt",
"Filter.not-contains": "inkluderer ikke",
"Filter.not-ends-with": "ender ikke med",
"Filter.not-includes": "inkluderer ikke",
"Filter.not-starts-with": "starter ikke med",
"Filter.starts-with": "starter med",
"FilterByText.placeholder": "filtrer tekst",
"FilterComponent.add-filter": "+ Nytt filter",
"FilterComponent.delete": "Slett",
"FilterValue.empty": "(tom)",
"FindBoardsDialog.IntroText": "Søk etter tavle",
"FindBoardsDialog.NoResultsFor": "Ingen resultat for \"{searchQuery}\"",
"FindBoardsDialog.NoResultsSubtext": "Sjekk stavingen eller søk på noe annet.",
"FindBoardsDialog.SubTitle": "Skriv for å finne en tavle. Bruk <b>opp/ned</b> for å navigere. <b>Enter</b> for å velge, eller <b>Esc</b> for å avbryte",
"FindBoardsDialog.Title": "Finn tavle",
"GroupBy.hideEmptyGroups": "Skjul {count} tomme grupper",
"GroupBy.showHiddenGroups": "Vis {count} tomme grupper",
"GroupBy.ungroup": "Fjern fra gruppe",
"HideBoard.MenuOption": "Skjul tavlen",
"KanbanCard.untitled": "Uten navn",
"Mutator.new-board-from-template": "ny tavle fra mal",
"Mutator.new-card-from-template": "nytt kort fra mal",
"Mutator.new-template-from-card": "ny mal fra kort"
}

View File

@ -1,5 +1,12 @@
{
"AppBar.Tooltip": "Ativar Boards vinculados",
"AppBar.Tooltip": "Ativar boards vinculados",
"Attachment.Attachment-title": "Anexo",
"AttachmentBlock.DeleteAction": "apagar",
"AttachmentBlock.addElement": "adicionar {type}",
"AttachmentBlock.delete": "Anexo apagado.",
"AttachmentBlock.uploadSuccess": "Anexo enviado.",
"AttachmentElement.delete-confirmation-dialog-button-text": "Apagar",
"AttachmentElement.upload-percentage": "Enviando...({uploadPercent}%)",
"BoardComponent.add-a-group": "+ Adicione um grupo",
"BoardComponent.delete": "Excluir",
"BoardComponent.hidden-columns": "Colunas ocultas",
@ -16,8 +23,8 @@
"BoardMember.unlinkChannel": "Desvincular",
"BoardPage.newVersion": "Uma nova versão do Boards está disponível, clique aqui para recarregar.",
"BoardPage.syncFailed": "O Board pode ter sido excluído ou o acesso revogado.",
"BoardTemplateSelector.add-template": "Novo modelo",
"BoardTemplateSelector.create-empty-board": "Criar board vazio",
"BoardTemplateSelector.add-template": "Criar novo modelo",
"BoardTemplateSelector.create-empty-board": "Criar um board vazio",
"BoardTemplateSelector.delete-template": "Excluir",
"BoardTemplateSelector.description": "Adicione um quadro à barra lateral usando qualquer um dos modelos definidos abaixo ou comece do zero.",
"BoardTemplateSelector.edit-template": "Editar",
@ -25,7 +32,7 @@
"BoardTemplateSelector.plugin.no-content-title": "Criar um board",
"BoardTemplateSelector.title": "Criar um board",
"BoardTemplateSelector.use-this-template": "Use este template",
"BoardsSwitcher.Title": "Encontrar Boards",
"BoardsSwitcher.Title": "Encontrar boards",
"BoardsUnfurl.Limited": "Detalhes adicionais estão ocultos devido ao cartão ter sido arquivado",
"BoardsUnfurl.Remainder": "+{remainder} mais",
"BoardsUnfurl.Updated": "Atualizado {time}",
@ -71,6 +78,7 @@
"CardBadges.title-checkboxes": "Caixa de seleção",
"CardBadges.title-comments": "Comentários",
"CardBadges.title-description": "Este cartão tem uma descrição",
"CardDetail.Attach": "Anexar",
"CardDetail.Follow": "Seguir",
"CardDetail.Following": "Seguindo",
"CardDetail.add-content": "Adicionar conteúdo",
@ -102,10 +110,13 @@
"Categories.CreateCategoryDialog.UpdateText": "Atualizar",
"CenterPanel.Login": "Login",
"CenterPanel.Share": "Compartilhar",
"ChannelIntro.CreateBoard": "Criar um board",
"CloudMessage.cloud-server": "Obtenha seu próprio cloud server de graça.",
"ColorOption.selectColor": "Selecione {color} Cor",
"Comment.delete": "Excluir",
"CommentsList.send": "Enviar",
"ConfirmPerson.empty": "Vazio",
"ConfirmPerson.search": "Buscar...",
"ConfirmationDialog.cancel-action": "Cancelar",
"ConfirmationDialog.confirm-action": "Confirmar",
"ContentBlock.Delete": "Excluir",
@ -181,6 +192,7 @@
"OnboardingTour.ShareBoard.Body": "Você pode compartilhar seu board internament, com seu time, ou public para permitir visibilidade fora da sua organização.",
"OnboardingTour.ShareBoard.Title": "Compartilhar quadro",
"PersonProperty.board-members": "Membros do Board",
"PersonProperty.me": "Eu",
"PersonProperty.non-board-members": "Não membros do board",
"PropertyMenu.Delete": "Excluir",
"PropertyMenu.changeType": "Alterar tipo da propriedade",
@ -235,6 +247,7 @@
"Sidebar.import-archive": "Importar arquivo",
"Sidebar.invite-users": "Convidar usuários",
"Sidebar.logout": "Sair",
"Sidebar.new-category.badge": "Novo",
"Sidebar.no-boards-in-category": "Nenhum board",
"Sidebar.product-tour": "Tour pelo produto",
"Sidebar.random-icons": "Ícones aleatórios",
@ -257,6 +270,7 @@
"SidebarTour.SidebarCategories.Body": "Todos seus boards agora são organizados sob sua nova barra lateral. Não é mais necessárioa alternar entre espaços de trabalho. Categorias personalizadas em suas estações prévias de trabalho foram automaticamente criadas para você como parte do seu upgrade para v7.2. Estas podem ser removidas ou editadas de acordo com a sua preferência.",
"SidebarTour.SidebarCategories.Link": "Saiba mais",
"SidebarTour.SidebarCategories.Title": "Categorias de barra lateral",
"SiteStats.total_boards": "Total de boards",
"TableComponent.add-icon": "Adicionar Ícone",
"TableComponent.name": "Nome",
"TableComponent.plus-new": "+ Novo",
@ -267,6 +281,7 @@
"TableHeaderMenu.insert-right": "Inserir à direita",
"TableHeaderMenu.sort-ascending": "Ordem ascendente",
"TableHeaderMenu.sort-descending": "Ordem descendente",
"TableRow.MoreOption": "Mais ações",
"TableRow.open": "Abrir",
"TopBar.give-feedback": "Dar feedback",
"URLProperty.copiedLink": "Copiado!",
@ -348,6 +363,7 @@
"calendar.month": "Mês",
"calendar.today": "HOJE",
"calendar.week": "Semana",
"centerPanel.unknown-user": "Usuário desconhecido",
"cloudMessage.learn-more": "Saiba mais",
"createImageBlock.failed": "Não foi possível enviar o arquivo. Limite de tamanho alcançado.",
"default-properties.badges": "Comentários e descrição",
@ -369,6 +385,7 @@
"login.log-in-button": "Entrar",
"login.log-in-title": "Entrar",
"login.register-button": "ou criar uma conta se você ainda não tiver uma",
"new_channel_modal.create_board.select_template_placeholder": "Selecionar um modelo",
"notification-box-card-limit-reached.close-tooltip": "Soneca por 10 dias",
"notification-box-card-limit-reached.contact-link": "notificar seu admin",
"notification-box-card-limit-reached.link": "Atualizar para um plano pago",
@ -388,14 +405,14 @@
"rhs-boards.dm": "DM",
"rhs-boards.gm": "GM",
"rhs-boards.header.dm": "esta Direct Message",
"rhs-boards.header.gm": "Este Gruop Message",
"rhs-boards.header.gm": "esta mensagem de grupo",
"rhs-boards.last-update-at": "Última atualização em: {datetime}",
"rhs-boards.link-boards-to-channel": "Vincular boards para {channelName}",
"rhs-boards.linked-boards": "Boards vinculados",
"rhs-boards.no-boards-linked-to-channel": "Nenhum board está vinculado a {channelName} ainda",
"rhs-boards.no-boards-linked-to-channel-description": "Boards é uma ferramenta de gerenciamento de projeto que ajuda a definir, organizar, rastrear e gerenciar o trabalho entre times, usando uma visualização de quadro estilo Kaban familiar.",
"rhs-boards.unlink-board": "Desvincular board",
"rhs-boards.unlink-board1": "Desvincular board Hello",
"rhs-boards.unlink-board1": "Desvincular board",
"rhs-channel-boards-header.title": "Boards",
"share-board.publish": "Publicar",
"share-board.share": "Compartilhar",

View File

@ -2,14 +2,14 @@
"AppBar.Tooltip": "Переключить связанные доски",
"Attachment.Attachment-title": "Вложение",
"AttachmentBlock.DeleteAction": "Удалить",
"AttachmentBlock.addElement": "добавить",
"AttachmentBlock.delete": "Вложение успешно удалено.",
"AttachmentBlock.failed": "Не удалось загрузить файл. Достигнут предел размера вложения.",
"AttachmentBlock.addElement": "добавить {type}",
"AttachmentBlock.delete": "Вложение удалено.",
"AttachmentBlock.failed": "Не удалось загрузить файл, так как превышена квота на размер файла.",
"AttachmentBlock.upload": "Загрузка вложения.",
"AttachmentBlock.uploadSuccess": "Вложение успешно загружено.",
"AttachmentBlock.uploadSuccess": "Вложение загружено.",
"AttachmentElement.delete-confirmation-dialog-button-text": "Удалить",
"AttachmentElement.download": "Скачать",
"AttachmentElement.upload-percentage": "Загрузка",
"AttachmentElement.upload-percentage": "Загрузка...({uploadPercent}%)",
"BoardComponent.add-a-group": "+ Добавить группу",
"BoardComponent.delete": "Удалить",
"BoardComponent.hidden-columns": "Скрытые столбцы",
@ -31,12 +31,12 @@
"BoardTemplateSelector.delete-template": "Удалить",
"BoardTemplateSelector.description": "Добавьте доску на боковую панель, используя любой из шаблонов, описанных ниже, или начните с нуля.",
"BoardTemplateSelector.edit-template": "Изменить",
"BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.{lineBreak} Участники \"{teamName}\" будут иметь доступ к созданным здесь доскам.",
"BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.",
"BoardTemplateSelector.plugin.no-content-title": "Создать доску",
"BoardTemplateSelector.title": "Создать доску",
"BoardTemplateSelector.use-this-template": "Использовать этот шаблон",
"BoardsSwitcher.Title": "Найти доски",
"BoardsUnfurl.Limited": "Информация скрыта в связи с тем, что карточка находится в архиве",
"BoardsUnfurl.Limited": "Информация скрыта, потому что карточка находится в архиве",
"BoardsUnfurl.Remainder": "+{remainder} ещё",
"BoardsUnfurl.Updated": "Обновлено {time}",
"Calculations.Options.average.displayName": "Среднее",
@ -103,9 +103,9 @@
"CardDetailProperty.property-deleted": "{propertyName} успешно удалено!",
"CardDetailProperty.property-name-change-subtext": "тип из \"{oldPropType}\" в \"{newPropType}\"",
"CardDetial.limited-link": "Узнайте больше о наших планах.",
"CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения!",
"CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения",
"CardDialog.delete-confirmation-dialog-button-text": "Удалить",
"CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки!",
"CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки",
"CardDialog.editing-template": "Вы редактируете шаблон.",
"CardDialog.nocard": "Эта карточка не существует или недоступна.",
"Categories.CreateCategoryDialog.CancelText": "Отмена",
@ -114,10 +114,13 @@
"Categories.CreateCategoryDialog.UpdateText": "Обновить",
"CenterPanel.Login": "Логин",
"CenterPanel.Share": "Поделиться",
"ChannelIntro.CreateBoard": "Создать доску",
"CloudMessage.cloud-server": "Получите свой бесплатный облачный сервер.",
"ColorOption.selectColor": "Выберите цвет {color}",
"Comment.delete": "Удалить",
"CommentsList.send": "Отправить",
"ConfirmPerson.empty": "Пусто",
"ConfirmPerson.search": "Поиск...",
"ConfirmationDialog.cancel-action": "Отмена",
"ConfirmationDialog.confirm-action": "Подтвердить",
"ContentBlock.Delete": "Удалить",
@ -165,6 +168,7 @@
"FilterByText.placeholder": "фильтровать текст",
"FilterComponent.add-filter": "+ Добавить фильтр",
"FilterComponent.delete": "Удалить",
"FilterValue.empty": "(пусто)",
"FindBoardsDialog.IntroText": "Поиск досок",
"FindBoardsDialog.NoResultsFor": "Нет результатов для \"{searchQuery}\"",
"FindBoardsDialog.NoResultsSubtext": "Проверьте правильность написания или попробуйте другой запрос.",
@ -183,7 +187,7 @@
"OnboardingTour.AddComments.Title": "Добавить комментарии",
"OnboardingTour.AddDescription.Body": "Добавьте описание к своей карточке, чтобы Ваши коллеги по команде знали, о чем эта карточка.",
"OnboardingTour.AddDescription.Title": "Добавить описание",
"OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более мощными!",
"OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более значительными.",
"OnboardingTour.AddProperties.Title": "Добавить свойства",
"OnboardingTour.AddView.Body": "Перейдите сюда, чтобы создать новый вид для организации доски с использованием различных макетов.",
"OnboardingTour.AddView.Title": "Добавить новый вид",
@ -284,6 +288,7 @@
"TableHeaderMenu.insert-right": "Вставить справа",
"TableHeaderMenu.sort-ascending": "Сортировать по возрастанию",
"TableHeaderMenu.sort-descending": "Сортировать по убыванию",
"TableRow.DuplicateCard": "дублировать карточку",
"TableRow.MoreOption": "Больше действий",
"TableRow.open": "Открыть",
"TopBar.give-feedback": "Дать обратную связь",
@ -351,10 +356,13 @@
"WelcomePage.Explore.Button": "Исследовать",
"WelcomePage.Heading": "Добро пожаловать на Доски",
"WelcomePage.NoThanks.Text": "Нет спасибо, сам разберусь",
"WelcomePage.StartUsingIt.Text": "Начать пользоваться",
"Workspace.editing-board-template": "Вы редактируете шаблон доски.",
"badge.guest": "Гость",
"boardSelector.confirm-link-board": "Привязать доску к каналу",
"boardSelector.confirm-link-board-button": "Да, ссылка доски",
"boardSelector.confirm-link-board-subtext": "Связывание доски \"{boardName}\" с этим каналом даст всем участникам этого канала доступ на редактирование доски. Вы уверены, что хотите связать это?",
"boardSelector.confirm-link-board-subtext": "Связывание доски \"{boardName}\" с каналом даст всем участникам канала доступ на редактирование доски. Вы можете в любое время отвязать доску о канала.",
"boardSelector.confirm-link-board-subtext-with-other-channel": "Привязка \"{boardName}\" с каналом приведет к возможности её редактирования всеми участниками канала (существующими и новыми). Кроме гостей канала.{lineBreak} Эта доска сейчас связана с другим каналом. Он будет отключен, если вы решите изменить привязку.",
"boardSelector.create-a-board": "Создать доску",
"boardSelector.link": "Ссылка",
"boardSelector.search-for-boards": "Поиск досок",
@ -363,6 +371,8 @@
"calendar.month": "Месяц",
"calendar.today": "СЕГОДНЯ",
"calendar.week": "Неделя",
"centerPanel.undefined": "Отсутствует {propertyName}",
"centerPanel.unknown-user": "Неизвестный пользователь",
"cloudMessage.learn-more": "Учить больше",
"createImageBlock.failed": "Не удалось загрузить файл. Достигнут предел размера файла.",
"default-properties.badges": "Комментарии и описание",
@ -377,11 +387,15 @@
"error.team-undefined": "Не корректная команда.",
"error.unknown": "Произошла ошибка.",
"generic.previous": "Предыдущий",
"imagePaste.upload-failed": "Некоторые файлы не загружены. Достигнут предел размера файла",
"imagePaste.upload-failed": "Некоторые файлы не загружены из-за превышения квоты на размер файла.",
"limitedCard.title": "Карточки скрыты",
"login.log-in-button": "Вход в систему",
"login.log-in-title": "Вход в систему",
"login.register-button": "или создать аккаунт, если у Вас его нет",
"new_channel_modal.create_board.empty_board_description": "Создать новую пустую доску",
"new_channel_modal.create_board.empty_board_title": "Пустая доска",
"new_channel_modal.create_board.select_template_placeholder": "Выбрать шаблон",
"new_channel_modal.create_board.title": "Создать доску для этого канала",
"notification-box-card-limit-reached.close-tooltip": "Отложить на 10 дней",
"notification-box-card-limit-reached.contact-link": "уведомить Вашего администратора",
"notification-box-card-limit-reached.link": "Перейти на платный тариф",
@ -389,13 +403,18 @@
"notification-box-cards-hidden.title": "Это действие скрыло другую карточку",
"notification-box.card-limit-reached.not-admin.text": "Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.",
"notification-box.card-limit-reached.text": "Достигнут лимит карточки, чтобы просмотреть старые карточки, {link}",
"person.add-user-to-board": "Добавить {username} на доску",
"person.add-user-to-board-confirm-button": "Добавить доску",
"person.add-user-to-board-permissions": "Разрешения",
"person.add-user-to-board-question": "Вы хотите добавить {username} на доску?",
"register.login-button": "или войти в систему, если у вас уже есть аккаунт",
"register.signup-title": "Зарегистрируйте свой аккаунт",
"rhs-board-non-admin-msg": "Вы не являетесь администратором этой доски",
"rhs-boards.add": "Добавить",
"rhs-boards.last-update-at": "Последнее обновление: {datetime}",
"rhs-boards.link-boards-to-channel": "Связать доски с {channelName}",
"rhs-boards.linked-boards": "Связанные доски",
"rhs-boards.no-boards-linked-to-channel": "К каналу {channelName} пока не подключены доски.",
"rhs-boards.no-boards-linked-to-channel": "К каналу {channelName} пока не подключены доски",
"rhs-boards.no-boards-linked-to-channel-description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Канбан.",
"rhs-boards.unlink-board": "Отвязать доску",
"rhs-channel-boards-header.title": "Доски",