Merge branch 'master' into MM-65587_fix-audit-log-compression-setting

This commit is contained in:
Mattermost Build 2026-02-03 12:26:35 +02:00 committed by GitHub
commit 04c3800685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 1003 additions and 872 deletions

View file

@ -12880,6 +12880,48 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
## x/sys
This product contains 'x/sys' by Go.
[mirror] Go packages for low-level interaction with the operating system
* HOMEPAGE:
* https://golang.org/x/sys
* LICENSE: BSD 3-Clause "New" or "Revised" License
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
## x/term

View file

@ -360,6 +360,36 @@
$ref: "#/components/responses/Forbidden"
"501":
$ref: "#/components/responses/NotImplemented"
/api/v4/cloud/check-cws-connection:
get:
tags:
- cloud
summary: Check CWS connection
description: >
Checks whether the Customer Web Server (CWS) is reachable from this instance.
Used to detect if the deployment is air-gapped.
##### Permissions
No permissions required.
__Minimum server version__: 5.28
__Note:__ This is intended for internal use and is subject to change.
operationId: CheckCWSConnection
responses:
"200":
description: CWS connection status returned successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: Connection status - "available" if CWS is reachable, "unavailable" if not
enum:
- available
- unavailable
/api/v4/cloud/webhook:
post:
tags:

View file

@ -227,6 +227,39 @@ describe('Channel Info RHS', () => {
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
});
});
it('should be able to rename channel from About area', () => {
// # Create a dedicated channel for renaming to avoid affecting other tests
cy.apiCreateChannel(testTeam.id, 'channel-to-rename', 'Channel To Rename', 'O').then(({channel}) => {
cy.apiAddUserToChannel(channel.id, admin.id);
// # Go to the channel
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
// # Open Channel Info RHS
cy.get('#channel-info-btn').click();
// # Click edit on channel name (first Edit in About)
cy.uiGetRHS().findAllByLabelText('Edit').first().click({force: true});
// * Rename Channel modal appears
cy.findByRole('heading', {name: /rename channel/i}).should('be.visible');
// # Fill display name and URL
cy.findByPlaceholderText(/enter display name/i).clear().type('Renamed Channel');
cy.get('.url-input-button').click();
cy.get('.url-input-container input').clear().type('renamed-channel');
cy.get('.url-input-container button.url-input-button').click();
// # Save
cy.findByRole('button', {name: /save/i}).click();
// * URL updated
cy.location('pathname').should('include', `/${testTeam.name}/channels/renamed-channel`);
// * Header shows new name
cy.get('#channelHeaderTitle').should('contain', 'Renamed Channel');
});
});
});
describe('bottom menu', () => {
it('should be able to manage notifications', () => {
@ -237,11 +270,29 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Notification Preferences"
cy.uiGetRHS().findByText('Notification Preferences').should('be.visible').click();
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Notification Preferences').scrollIntoView().should('be.visible').click();
// * Ensures the modal is there
cy.get('.ChannelNotificationModal').should('be.visible');
});
it('should open Channel Settings from RHS menu', () => {
// # Go to test channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
// # Close RHS if it's open, then click on the channel info button
cy.get('body').then(($body) => {
if ($body.find('#rhsCloseButton').length > 0) {
cy.get('#rhsCloseButton').click();
}
cy.get('#channel-info-btn').should('be.visible').click();
});
// * Channel Settings item is visible in RHS menu
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Channel Settings').scrollIntoView().should('be.visible').click();
// * Channel Settings modal opens
cy.get('.ChannelSettingsModal').should('be.visible');
});
it('should be able to view files and come back', () => {
// # Go to test channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
@ -250,7 +301,7 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Files"
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Files').should('be.visible').click();
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Files').scrollIntoView().should('be.visible').click();
// * Ensure we see the files RHS
cy.uiGetRHS().findByText('No files yet').should('be.visible');
@ -277,10 +328,10 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Pinned Messages"
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Pinned messages').should('be.visible').click();
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Pinned messages').scrollIntoView().should('be.visible').click();
// * Ensure we see the Pinned Post RHS
cy.uiGetRHS().findByText('Hello channel info rhs spec').should('be.visible');
cy.uiGetRHS().findByText('Hello channel info rhs spec').first().should('be.visible');
// # Click the Back Icon
cy.uiGetRHS().get('[aria-label="Back Icon"]').click();
@ -296,7 +347,7 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Members"
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Members').should('be.visible').click();
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Members').scrollIntoView().should('be.visible').click();
// * Ensure we see the members
cy.uiGetRHS().contains('sysadmin').should('be.visible');
@ -399,7 +450,7 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Notification Preferences"
cy.uiGetRHS().findByText('Notification Preferences').should('be.visible').click();
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Notification Preferences').scrollIntoView().should('be.visible').click();
// * Ensures the modal is there
cy.get('.ChannelNotificationModal').should('be.visible');
@ -489,6 +540,6 @@ describe('Channel Info RHS', () => {
function ensureRHSIsOpenOnChannelInfo(testChannel) {
cy.get('#rhsContainer').then((rhsContainer) => {
cy.wrap(rhsContainer).findByText('Info').should('be.visible');
cy.wrap(rhsContainer).findByText(testChannel.display_name).should('be.visible');
cy.wrap(rhsContainer).find('.sidebar--right__title__subtitle').should('contain', testChannel.display_name);
});
}

View file

@ -849,6 +849,7 @@ const defaultServerConfig: AdminConfig = {
APIKey: '',
},
TargetLanguages: ['en'],
Workers: 4,
TimeoutMs: 5000,
Agents: {
LLMServiceID: '',

View file

@ -44,7 +44,7 @@ func (api *API) InitCloud() {
// GET /api/v4/cloud/installation
api.BaseRoutes.Cloud.Handle("/installation", api.APISessionRequired(getInstallation)).Methods(http.MethodGet)
// GET /api/v4/cloud/cws-health-check
// GET /api/v4/cloud/check-cws-connection
api.BaseRoutes.Cloud.Handle("/check-cws-connection", api.APIHandler(handleCheckCWSConnection)).Methods(http.MethodGet)
// GET /api/v4/cloud/preview/modal_data
@ -605,12 +605,16 @@ func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request
return
}
status := "available"
if err := c.App.Cloud().CheckCWSConnection(c.AppContext.Session().UserId); err != nil {
c.Err = model.NewAppError("Api4.handleCWSHealthCheck", "api.server.cws.health_check.app_error", nil, "CWS Server is not available.", http.StatusInternalServerError)
return
status = "unavailable"
}
ReturnStatusOK(w)
response := map[string]string{"status": status}
if err := json.NewEncoder(w).Encode(response); err != nil {
c.Err = model.NewAppError("Api4.handleCheckCWSConnection", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
return
}
}
func getPreviewModalData(c *Context, w http.ResponseWriter, r *http.Request) {

View file

@ -5,10 +5,12 @@ package api4
import (
"context"
"encoding/json"
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@ -430,3 +432,57 @@ func TestGetCloudProducts(t *testing.T) {
require.Equal(t, returnedProducts[2].CrossSellsTo, "prod_test2")
})
}
func TestCheckCWSConnection(t *testing.T) {
mainHelper.Parallel(t)
t.Run("returns available when CWS is reachable", func(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(model.NewTestLicense())
cloud := mocks.CloudInterface{}
cloud.Mock.On("CheckCWSConnection", mock.Anything).Return(nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = &cloud
r, err := th.Client.DoAPIGet(context.Background(), "/cloud/check-cws-connection", "")
require.NoError(t, err)
defer closeBody(r)
require.Equal(t, http.StatusOK, r.StatusCode)
var response map[string]string
require.NoError(t, json.NewDecoder(r.Body).Decode(&response))
assert.Equal(t, "available", response["status"])
})
t.Run("returns unavailable when CWS is not reachable", func(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(model.NewTestLicense())
cloud := mocks.CloudInterface{}
cloud.Mock.On("CheckCWSConnection", mock.Anything).Return(errors.New("connection failed"))
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = &cloud
r, err := th.Client.DoAPIGet(context.Background(), "/cloud/check-cws-connection", "")
require.NoError(t, err)
defer closeBody(r)
require.Equal(t, http.StatusOK, r.StatusCode)
var response map[string]string
require.NoError(t, json.NewDecoder(r.Body).Decode(&response))
assert.Equal(t, "unavailable", response["status"])
})
}

View file

@ -643,14 +643,6 @@ func (a *App) handlePostEvents(rctx request.CTX, post *model.Post, user *model.U
return err
}
// Send initial read receipt counts for burn-on-read posts
if post.Type == model.PostTypeBurnOnRead {
// No revealing user yet (post just created), send empty string
if err := a.publishPostRevealedEventToAuthor(rctx, post, "", ""); err != nil {
rctx.Logger().Error("Failed to publish initial burn-on-read read receipt event", mlog.String("post_id", post.Id), mlog.Err(err))
}
}
if post.Type != model.PostTypeAutoResponder { // don't respond to an auto-responder
a.Srv().Go(func() {
_, err := a.SendAutoResponseIfNecessary(rctx, channel, user, post)

View file

@ -1488,6 +1488,12 @@ func (s *Server) initJobs() {
s.Jobs.RegisterJobType(model.JobTypePushProxyAuth, builder.MakeWorker(), builder.MakeScheduler())
}
if s.AutoTranslation != nil {
s.Jobs.RegisterJobType(model.JobTypeAutoTranslationRecovery,
s.AutoTranslation.MakeWorker(),
s.AutoTranslation.MakeScheduler())
}
s.Jobs.RegisterJobType(
model.JobTypeMigrations,
migrations.MakeWorker(s.Jobs, s.Store()),

View file

@ -7,6 +7,7 @@ import (
"context"
"github.com/mattermost/mattermost/server/public/model"
ejobs "github.com/mattermost/mattermost/server/v8/einterfaces/jobs"
)
// AutoTranslationInterface defines the enterprise advanced auto-translation functionality.
@ -75,4 +76,12 @@ type AutoTranslationInterface interface {
// Shutdown gracefully shuts down the auto-translation service.
Shutdown() error
// MakeWorker creates a worker for the autotranslation recovery sweep job.
// The worker picks up stuck translations and re-queues them for processing.
MakeWorker() model.Worker
// MakeScheduler creates a scheduler for the autotranslation recovery sweep job.
// The scheduler runs periodically to detect stuck translations.
MakeScheduler() ejobs.Scheduler
}

View file

@ -7,6 +7,8 @@ package mocks
import (
context "context"
jobs "github.com/mattermost/mattermost/server/v8/einterfaces/jobs"
mock "github.com/stretchr/testify/mock"
model "github.com/mattermost/mattermost/server/public/model"
@ -214,6 +216,46 @@ func (_m *AutoTranslationInterface) IsUserEnabled(channelID string, userID strin
return r0, r1
}
// MakeScheduler provides a mock function with no fields
func (_m *AutoTranslationInterface) MakeScheduler() jobs.Scheduler {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for MakeScheduler")
}
var r0 jobs.Scheduler
if rf, ok := ret.Get(0).(func() jobs.Scheduler); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(jobs.Scheduler)
}
}
return r0
}
// MakeWorker provides a mock function with no fields
func (_m *AutoTranslationInterface) MakeWorker() model.Worker {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for MakeWorker")
}
var r0 model.Worker
if rf, ok := ret.Get(0).(func() model.Worker); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(model.Worker)
}
}
return r0
}
// SetChannelEnabled provides a mock function with given fields: channelID, enabled
func (_m *AutoTranslationInterface) SetChannelEnabled(channelID string, enabled bool) *model.AppError {
ret := _m.Called(channelID, enabled)

View file

@ -8179,10 +8179,6 @@
"id": "api.command_mute.success_mute",
"translation": "Вы не будзеце атрымліваць апавяшчэнні для {{.Channel}}, пакуль адключаны гук канала."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Немагчыма адключыць гук у канале {{.Channel}}, бо вы не з'яўляецеся яго ўдзельнікам."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Немагчыма знайсці ўказаны канал. Калі ласка, выкарыстоўвайце [ідэнтыфікатар канала](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel), каб ідэнтыфікаваць каналы."
@ -10483,10 +10479,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Немагчыма адмяніць прызначэнне палітыкі кантролю доступу ад каналаў."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Немагчыма змяніць актыўны статус палітыкі кантролю доступу."
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "Не атрымалася ацаніць палітыку кантролю доступу."
@ -10951,18 +10943,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "Непадтрымоўваны пастаўшчык аўтаматычнага перакладу."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "Несапраўдны час чакання атрымання для налад аўтаматычнага перакладу. Павінна быць станоўчым лікам."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "Несапраўдны час чакання новага паведамлення для налад аўтаматычнага перакладу. Павінна быць станоўчым лікам."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Несапраўдны час чакання апавяшчэння для налад аўтаматычнага перакладу. Павінна быць станоўчым лікам."
},
{
"id": "api.channel.get_channel.flagged_post_mismatch.app_error",
"translation": "Ідэнтыфікатар канала не супадае з ідэнтыфікатарам маркіраванага паведамлення."
@ -11387,66 +11367,6 @@
"id": "model.post.query_params.invalid_time_field",
"translation": "Няслушнае поле часу."
},
{
"id": "store.sql_autotranslation.channel_not_found",
"translation": "Канал не знойдзены."
},
{
"id": "store.sql_autotranslation.get.app_error",
"translation": "Немагчыма атрымаць пераклад."
},
{
"id": "store.sql_autotranslation.get_active_languages.app_error",
"translation": "Немагчыма атрымаць актыўныя мовы."
},
{
"id": "store.sql_autotranslation.get_channel_enabled.app_error",
"translation": "Немагчыма атрымаць статус уключэння аўтаперакладу канала."
},
{
"id": "store.sql_autotranslation.get_user_enabled.app_error",
"translation": "Немагчыма атрымаць статус уключэння аўтаперакладу карыстальніка."
},
{
"id": "store.sql_autotranslation.get_user_language.app_error",
"translation": "Немагчыма атрымаць мову карыстальніка."
},
{
"id": "store.sql_autotranslation.is_channel_enabled.app_error",
"translation": "Немагчыма атрымаць статус уключэння аўтаперакладу канала."
},
{
"id": "store.sql_autotranslation.member_not_found",
"translation": "Удзельнік не знойдзены."
},
{
"id": "store.sql_autotranslation.meta_json.app_error",
"translation": "Не атрымалася прааналізаваць JSON метададзеных перакладу."
},
{
"id": "store.sql_autotranslation.query_build_error",
"translation": "Не атрымалася стварыць запыт."
},
{
"id": "store.sql_autotranslation.save.app_error",
"translation": "Немагчыма захаваць пераклад."
},
{
"id": "store.sql_autotranslation.save.invalid_translation",
"translation": "Праверка перакладу не ўдалася. Аб'ект перакладу няслушны."
},
{
"id": "store.sql_autotranslation.save.meta_json.app_error",
"translation": "Не атрымалася серыялізаваць метададзеныя перакладу ў JSON."
},
{
"id": "store.sql_autotranslation.set_channel_enabled.app_error",
"translation": "Немагчыма ўсталяваць статус уключэння аўтаперакладу канала."
},
{
"id": "store.sql_autotranslation.set_user_enabled.app_error",
"translation": "Немагчыма ўсталяваць статус уключэння аўтаперакладу карыстальніка."
},
{
"id": "api.post.burn_post.user_not_in_channel.app_error",
"translation": "У вас няма дазволу на выдаленне гэтага паведамлення. Вы павінны быць удзельнікам канала."

View file

@ -5360,10 +5360,6 @@
"id": "api.command_mute.success_mute",
"translation": "Няма да получавате известия за канала {{.Channel}}, докато е заглушен."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Не може да заглушите канала {{.Channel}} защото не сте член."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Не е намерен зададения канал. Моля, използвайте [адреса на канала](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) за да определяте канали."

View file

@ -3491,10 +3491,6 @@
"id": "api.command_mute.success_mute",
"translation": "No rebràs notificacions per {{.Channel}} mentrestant el canal estigui silenciat."
},
{
"id": "api.command_mute.not_member.error",
"translation": "No s'ha pogut silenciar el canal {{.Channel}} perquè no ets membre."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "No s'ha pogut trobar el canal especificat. Utilitza l'[identificador del canal](https://about.mattermost.com/default-channel-handle-documentation) per identificar canals."

View file

@ -3979,10 +3979,6 @@
"id": "api.command_mute.success_mute",
"translation": "Nebudete dostávat upozornění pro {{.Channel}}, dokud nevypnete ztlumení."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Nelze ztlumit kanál {{.Channel}}, protože nejste členem."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Zadaný kanál nebyl nalezen. Použijte prosím [identifikátor kanálu](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) k určení kanálů."
@ -10667,10 +10663,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Nepodařilo se odebrat přiřazení pravidla řízení přístupu z kanálů."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Nepodařilo se změnit aktivní stav pravidla řízení přístupu."
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "Nepodařilo se vyhodnotit pravidlo řízení přístupu."

View file

@ -789,10 +789,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Konnte den Kanal nicht finden. Bitte nutze den [Kanal-Handle](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel), um Kanäle zu identifizieren."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Kanal {{.Channel}} konnte nicht stumm geschaltet werden, da du kein Mitglied bist."
},
{
"id": "api.command_mute.success_mute",
"translation": "Du wirst keine Benachrichtigungen mehr für {{.Channel}} erhalten bis die Stummschaltung aufgehoben wurde."
@ -10507,10 +10503,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Die Zuweisung der Zugriffskontrollrichtlinie für Kanäle konnte nicht aufgehoben werden."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Der aktive Status der Zugriffskontrollrichtlinie konnte nicht geändert werden."
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "Die Auswertung der Zugriffskontrollrichtlinie ist fehlgeschlagen."
@ -10991,18 +10983,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "Nicht unterstützter Autotranslationsanbieter."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "Ungültiges Fetch-Timeout für die Autotranslation-Einstellungen. Muss eine positive Zahl sein."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "Ungültige Zeitüberschreitung für neue Beiträge bei automatischen Übersetzungseinstellungen. Muss eine positive Zahl sein."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Ungültiges Zeitlimit für Benachrichtigungen bei automatischen Übersetzungseinstellungen. Muss eine positive Zahl sein."
},
{
"id": "api.channel.get_channel.flagged_post_mismatch.app_error",
"translation": "Die Kanal-ID stimmt nicht mit der Kanal-ID des markierten Beitrags überein."
@ -11427,66 +11407,6 @@
"id": "model.post.query_params.invalid_time_field",
"translation": "Ungültiges Zeitfeld."
},
{
"id": "store.sql_autotranslation.channel_not_found",
"translation": "Kanal nicht gefunden."
},
{
"id": "store.sql_autotranslation.get.app_error",
"translation": "Ich kann keine Übersetzung bekommen."
},
{
"id": "store.sql_autotranslation.get_active_languages.app_error",
"translation": "Aktive Sprachen können nicht abgerufen werden."
},
{
"id": "store.sql_autotranslation.get_channel_enabled.app_error",
"translation": "Der Status der aktivierten Kanal-Autotranslation kann nicht abgerufen werden."
},
{
"id": "store.sql_autotranslation.get_user_enabled.app_error",
"translation": "Es ist nicht möglich, den Status der aktivierten Benutzerautoübersetzung zu erhalten."
},
{
"id": "store.sql_autotranslation.get_user_language.app_error",
"translation": "Die Sprache des Benutzers kann nicht abgerufen werden."
},
{
"id": "store.sql_autotranslation.is_channel_enabled.app_error",
"translation": "Der Status der aktivierten Kanal-Autotranslation kann nicht abgerufen werden."
},
{
"id": "store.sql_autotranslation.member_not_found",
"translation": "Mitglied nicht gefunden."
},
{
"id": "store.sql_autotranslation.meta_json.app_error",
"translation": "Das Parsen der Übersetzungsmetadaten JSON ist fehlgeschlagen."
},
{
"id": "store.sql_autotranslation.query_build_error",
"translation": "Fehler beim Erstellen der Abfrage."
},
{
"id": "store.sql_autotranslation.save.app_error",
"translation": "Die Übersetzung kann nicht gespeichert werden."
},
{
"id": "store.sql_autotranslation.save.invalid_translation",
"translation": "Validierung der Übersetzung fehlgeschlagen. Das Übersetzungsobjekt ist ungültig."
},
{
"id": "store.sql_autotranslation.save.meta_json.app_error",
"translation": "Die Übersetzungsmetadaten konnten nicht in JSON serialisiert werden."
},
{
"id": "store.sql_autotranslation.set_channel_enabled.app_error",
"translation": "Status der automatischen Kanalübersetzung kann nicht gesetzt werden."
},
{
"id": "store.sql_autotranslation.set_user_enabled.app_error",
"translation": "Der Status \"Benutzerautoübersetzung aktiviert\" konnte nicht gesetzt werden."
},
{
"id": "api.post.burn_post.user_not_in_channel.app_error",
"translation": "Du hast nicht die Erlaubnis, diesen Beitrag als Löschen-nach-Lesen zu markieren. Du musst ein Mitglied des Kanals sein."

View file

@ -547,10 +547,6 @@
"id": "api.command_mute.success_mute",
"translation": "Δε θα λάβετε ειδοποιήσεις για το {{.Channel}} μέχρι να απενεργοποιηθεί η σίγαση του καναλιού."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Δεν ήταν δυνατή η σίγαση του καναλιού {{.Channel}} καθώς δεν είστε μέλος."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Δεν ήταν δυνατή η εύρεση του συγκεκριμένου καναλιού. Χρησιμοποιήστε τη διαχείριση καναλιού για τον εντοπισμό καναλιών."

View file

@ -6369,10 +6369,6 @@
"id": "api.command_mute.success_mute",
"translation": "You will not receive notifications for {{.Channel}} until channel mute is turned off."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Could not mute channel {{.Channel}} as you are not a member."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Could not find the specified channel. Please use the [channel handle](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) to identify channels."
@ -10488,10 +10484,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Could not unassign access control policy from channels."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Could not change active status of access control policy."
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "Failed evaluate access control policy."
@ -11040,18 +11032,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "Unsupported autotranslation provider."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "Invalid fetch timeout for autotranslation settings. Must be a positive number."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "Invalid new post timeout for autotranslation settings. Must be a positive number."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Invalid notification timeout for autotranslation settings. Must be a positive number."
},
{
"id": "model.config.is_valid.client_side_cert_enable.app_error",
"translation": "Certificate-based authentication has been removed. Please disable ClientSideCertEnable to continue."
@ -11615,65 +11595,5 @@
{
"id": "model.post.query_params.invalid_time_field",
"translation": "Invalid time field."
},
{
"id": "store.sql_autotranslation.channel_not_found",
"translation": "Channel not found."
},
{
"id": "store.sql_autotranslation.get.app_error",
"translation": "Unable to get translation."
},
{
"id": "store.sql_autotranslation.get_active_languages.app_error",
"translation": "Unable to get active languages."
},
{
"id": "store.sql_autotranslation.get_channel_enabled.app_error",
"translation": "Unable to get channel autotranslation enabled status."
},
{
"id": "store.sql_autotranslation.get_user_enabled.app_error",
"translation": "Unable to get user autotranslation enabled status."
},
{
"id": "store.sql_autotranslation.get_user_language.app_error",
"translation": "Unable to get user language."
},
{
"id": "store.sql_autotranslation.is_channel_enabled.app_error",
"translation": "Unable to get channel autotranslation enabled status."
},
{
"id": "store.sql_autotranslation.member_not_found",
"translation": "Member not found."
},
{
"id": "store.sql_autotranslation.meta_json.app_error",
"translation": "Failed to parse translation metadata JSON."
},
{
"id": "store.sql_autotranslation.query_build_error",
"translation": "Failed to build query."
},
{
"id": "store.sql_autotranslation.save.app_error",
"translation": "Unable to save translation."
},
{
"id": "store.sql_autotranslation.save.invalid_translation",
"translation": "Translation validation failed. The translation object is invalid."
},
{
"id": "store.sql_autotranslation.save.meta_json.app_error",
"translation": "Failed to serialise translation metadata to JSON."
},
{
"id": "store.sql_autotranslation.set_channel_enabled.app_error",
"translation": "Unable to set channel autotranslation enabled status."
},
{
"id": "store.sql_autotranslation.set_user_enabled.app_error",
"translation": "Unable to set user autotranslation enabled status."
}
]

View file

@ -3146,10 +3146,6 @@
"id": "api.server.cws.disabled",
"translation": "Interactions with the Mattermost Customer Portal have been disabled by the system admin."
},
{
"id": "api.server.cws.health_check.app_error",
"translation": "CWS Server is not available."
},
{
"id": "api.server.cws.needs_enterprise_edition",
"translation": "Service only available in Mattermost Enterprise edition"
@ -9908,6 +9904,10 @@
"id": "model.config.is_valid.autotranslation.timeout.app_error",
"translation": "Invalid timeout for autotranslation settings. Must be a positive number."
},
{
"id": "model.config.is_valid.autotranslation.workers.app_error",
"translation": "Workers must be between 1 and 32."
},
{
"id": "model.config.is_valid.cache_type.app_error",
"translation": "Cache type must be either lru or redis."

View file

@ -791,10 +791,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "No se encontró el canal especificado. Por favor utiliza el [identificador del canal](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) para identificar canales."
},
{
"id": "api.command_mute.not_member.error",
"translation": "No se pudo silenciar el canal {{.Channel}} porque no eres miembro."
},
{
"id": "api.command_mute.success_mute",
"translation": "No recibirás notificaciones para {{.Channel}} mientras el canal es silenciado."

View file

@ -6940,10 +6940,6 @@
"id": "api.command_remote.name",
"translation": "ارتباط امن"
},
{
"id": "api.command_mute.not_member.error",
"translation": "کانال {{.Channel}} بیصدا نمی شود زیرا عضو آن نیستید."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "کانال مشخص شده یافت نشد. برای شناسایی کانال ها لطفاً از [هندل کانال](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) استفاده کنید."

View file

@ -3740,10 +3740,6 @@
"id": "api.command_mute.success_mute",
"translation": "Et saa ilmoituksia kanavalta {{.Channel}} ennen kuin mykistys on otettu pois päältä."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Ei voitu mykistää kanavaa {{.Channel}} koska et ole sen jäsen."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Kanavaa ei löydetty. Käytä [kanavan tunnistetta](https://about.mattermost.com/default-channel-handle-documentation) sen määrittämiseen."

View file

@ -791,10 +791,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Impossible de trouver le canal spécifié. Veuillez utiliser l'[identifiant de canal](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) pour identifier les canaux."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Impossible de mettre en sourdine le canal {{.Channel}}, car vous n'êtes pas membre de celui-ci."
},
{
"id": "api.command_mute.success_mute",
"translation": "Vous ne recevrez pas de notifications pour le canal {{.Channel}} jusqu'à ce que vous désactiviez le mode sourdine."

View file

@ -2103,10 +2103,6 @@
"id": "api.command_mute.success_mute",
"translation": "चैनल म्यूट बंद होने तक आपको {{.Channel}} के लिए सूचनाएं प्राप्त नहीं होंगी।"
},
{
"id": "api.command_mute.not_member.error",
"translation": "चैनल {{.Channel}} को म्यूट नहीं किया जा सका क्योंकि आप सदस्य नहीं हैं।"
},
{
"id": "api.command_mute.no_channel.error",
"translation": "निर्दिष्ट चैनल नहीं मिल सका। चैनलों की पहचान करने के लिए कृपया [चैनल हैंडल](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) का उपयोग करें।"

View file

@ -7364,10 +7364,6 @@
"id": "api.command_mute.success_mute",
"translation": "Nem fog kapni értesítéseket a {{.Channel}} csatornán, amíg a csatorna el van némítva."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Nem tudta elnémítani a {{.Channel}} csatornát, mivel Ön nem tagja annak."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Nem található a megadott csatorna. A csatornák azonosításához használja a [csatorna azonosítót](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel)."

View file

@ -3225,10 +3225,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Tidak dapat menemukan saluran yang ditentukan. Silakan gunakan [pegangan saluran] (https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) untuk mengidentifikasi saluran."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Tidak dapat membisukan saluran {{.Channel}} karena Anda bukan anggota."
},
{
"id": "api.command_mute.success_unmute",
"translation": "{{.Channel}} tidak lagi dibisukan."

View file

@ -787,10 +787,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Impossibile trovare il canale specificato. Usare [channel handle](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) per identificare i canali."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Impossibile silenziare il canale {{.Channel}}, non ne sei membro."
},
{
"id": "api.command_mute.success_mute",
"translation": "Non riceverai ulteriori notifiche per {{.Channel}} fino a quando sarà silenziato."

View file

@ -787,10 +787,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "指定したチャンネルが見つかりませんでした。チャンネルの指定には[チャンネルのハンドル名](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel)を使用してください。"
},
{
"id": "api.command_mute.not_member.error",
"translation": "あなたはメンバーではないため、チャンネル {{.Channel}} をミュートできません。"
},
{
"id": "api.command_mute.success_mute",
"translation": "チャンネルミュートがオフになるまで {{.Channel}} の通知を受け取らなくなります。"
@ -10433,10 +10429,6 @@
"id": "app.pap.is_ready.app_error",
"translation": "アクセス制御サービスの準備ができていません。"
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "アクセス制御ポリシーの有効化状況を変更できませんでした。"
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "アクセス制御ポリシーを評価できませんでした。"

View file

@ -787,10 +787,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "지정한 채널을 찾을 수 없습니다. 채널을 식별하려면 [채널 핸들](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel)을 사용하세요."
},
{
"id": "api.command_mute.not_member.error",
"translation": "사용자가 채널 회원이 아니기 때문에 {{.Channel}} 채널 음소거할 수 없습니다."
},
{
"id": "api.command_mute.success_mute",
"translation": "채널 음소거가 해제될 때까지{{.Channel}}에 대한 알림을 받지 않습니다."
@ -9313,10 +9309,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "채널에서 액세스 제어 정책을 할당 해제할 수 없습니다."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "액세스 제어 정책의 활성 상태를 변경할 수 없습니다."
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "액세스 제어 정책을 평가하지 못했습니다."

View file

@ -994,10 +994,6 @@
"id": "api.command_mute.success_mute",
"translation": "ചാനൽ നിശബ്ദമാക്കുന്നത് വരെ നിങ്ങൾക്ക് {{.Channel}}-നായി അറിയിപ്പുകൾ ലഭിക്കില്ല."
},
{
"id": "api.command_mute.not_member.error",
"translation": "നിങ്ങൾ അംഗമല്ലാത്തതിനാൽ {{.Channel}} ചാനൽ നിശബ്ദമാക്കാൻ കഴിഞ്ഞില്ല."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "നിർദ്ദിഷ്ട ചാനൽ കണ്ടെത്താൻ കഴിഞ്ഞില്ല. ചാനലുകൾ തിരിച്ചറിയാൻ ദയവായി [ചാനൽ ഹാൻഡിൽ](https://about.mattermost.com/default-channel-handle-documentation) ഉപയോഗിക്കുക."

View file

@ -789,10 +789,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Kan het opgegeven kanaal niet vinden. Gebruik de [kanaalaanduiding](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) om kanalen aan te duiden."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Kan het kanaal {{.Channel}} niet dempen omdat je geen lid bent."
},
{
"id": "api.command_mute.success_mute",
"translation": "Je zal geen meldingen ontvangen voor {{.Channel}} tot het dempen van het kanaal is uitgeschakeld."
@ -10523,10 +10519,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Kan toegangscontrolebeleid niet weghalen van de kanalen."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Kon de actieve status van het toegangscontrolebeleid niet wijzigen."
},
{
"id": "ent.access_control.sync_job.app_error",
"translation": "Fout bij het uitvoeren van de sync taak voor toegangscontrole."
@ -11003,18 +10995,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "Niet-ondersteunde provider voor automatische vertalingen."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "Ongeldige fetch timeout voor instellingen voor automatische vertalingen. Moet een positief getal zijn."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "Ongeldige time-out voor nieuwe berichten bij instellingen voor automatische vertalingen. Moet een positief getal zijn."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Ongeldige melding timeout voor automatische vertalingen. Moet een positief getal zijn. Moet een positief getal zijn."
},
{
"id": "api.channel.get_channel.flagged_post_mismatch.app_error",
"translation": "Kanaal ID komt niet overeen met het kanaal ID van het gemarkeerde bericht."
@ -11439,66 +11419,6 @@
"id": "model.post.query_params.invalid_time_field",
"translation": "Ongeldig tijdveld."
},
{
"id": "store.sql_autotranslation.channel_not_found",
"translation": "Kanaal niet gevonden."
},
{
"id": "store.sql_autotranslation.get.app_error",
"translation": "Kan geen vertaling ophalen."
},
{
"id": "store.sql_autotranslation.get_active_languages.app_error",
"translation": "Kan de actieve talen niet ophalen."
},
{
"id": "store.sql_autotranslation.get_channel_enabled.app_error",
"translation": "Kan automatische vertalingsstatus van het kanaal niet ophalen."
},
{
"id": "store.sql_autotranslation.get_user_enabled.app_error",
"translation": "Kan automatische vertalingsstatus voor de gebruiker niet ophalen."
},
{
"id": "store.sql_autotranslation.get_user_language.app_error",
"translation": "Kan de taal van de gebruiker niet ophalen."
},
{
"id": "store.sql_autotranslation.is_channel_enabled.app_error",
"translation": "Kan automatische vertalingsstatus van het kanaal niet ophalen."
},
{
"id": "store.sql_autotranslation.member_not_found",
"translation": "Lid niet gevonden."
},
{
"id": "store.sql_autotranslation.meta_json.app_error",
"translation": "Fout bij het verwerken van de JSON vertaalmetagegevens."
},
{
"id": "store.sql_autotranslation.query_build_error",
"translation": "Fout bij het maken van de query."
},
{
"id": "store.sql_autotranslation.save.app_error",
"translation": "Fout bij het bewaren van de vertaling."
},
{
"id": "store.sql_autotranslation.save.invalid_translation",
"translation": "Vertaalvalidatie mislukt. Het vertaalobject is ongeldig."
},
{
"id": "store.sql_autotranslation.save.meta_json.app_error",
"translation": "Het serialiseren van vertaalmetagegevens naar JSON is mislukt."
},
{
"id": "store.sql_autotranslation.set_channel_enabled.app_error",
"translation": "Kan automatische vertaling van het kanaal niet inschakelen."
},
{
"id": "store.sql_autotranslation.set_user_enabled.app_error",
"translation": "Kan automatische vertaling voor de gebruiker niet instellen."
},
{
"id": "api.post.burn_post.user_not_in_channel.app_error",
"translation": "Je hebt geen toestemming om dit bericht te verbranden. Je moet lid zijn van het kanaal."

View file

@ -791,10 +791,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Nie można znaleźć określonego kanału. Użyj [uchwytu kanału](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel), aby zidentyfikować kanały."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Nie można wyciszyć kanału {{.Channel}} jeśli nie jesteś jego członkiem."
},
{
"id": "api.command_mute.success_mute",
"translation": "Nie będziesz otrzymywać powiadomień o kanale {{.Channel}}, dopóki wyciszenie kanału jest wyłączone."
@ -10691,10 +10687,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Nie można usunąć polityki kontroli dostępu z Kanałów."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Nie można zmienić aktywnego statusu polityki kontroli dostępu."
},
{
"id": "app.pap.is_ready.app_error",
"translation": "Usługa kontroli dostępu nie jest gotowa."
@ -10995,18 +10987,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "Nieobsługiwany dostawca autotranslacji."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "Nieprawidłowy limit czasu pobierania dla ustawień automatycznego tłumaczenia. Musi być liczbą dodatnią."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "Nieprawidłowy limit czasu nowej poczty dla ustawień autotranslacji. Musi być liczbą dodatnią."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Nieprawidłowy limit czasu powiadomienia dla ustawień autotranslacji. Musi być liczbą dodatnią."
},
{
"id": "api.channel.get_channel.flagged_post_mismatch.app_error",
"translation": "Identyfikator kanału nie pasuje do identyfikatora kanału oflagowanego postu."
@ -11431,66 +11411,6 @@
"id": "model.post.query_params.invalid_time_field",
"translation": "Nieprawidłowe pole czasu."
},
{
"id": "store.sql_autotranslation.channel_not_found",
"translation": "Kanał nie został znaleziony."
},
{
"id": "store.sql_autotranslation.get.app_error",
"translation": "Nie można uzyskać tłumaczenia."
},
{
"id": "store.sql_autotranslation.get_active_languages.app_error",
"translation": "Nie można uzyskać aktywnych języków."
},
{
"id": "store.sql_autotranslation.get_channel_enabled.app_error",
"translation": "Nie można uzyskać statusu włączenia autotranslacji Kanału."
},
{
"id": "store.sql_autotranslation.get_user_enabled.app_error",
"translation": "Nie można uzyskać statusu włączonej autotranslacji użytkownika."
},
{
"id": "store.sql_autotranslation.get_user_language.app_error",
"translation": "Nie można uzyskać języka użytkownika."
},
{
"id": "store.sql_autotranslation.is_channel_enabled.app_error",
"translation": "Nie można uzyskać statusu włączenia autotranslacji Kanału."
},
{
"id": "store.sql_autotranslation.member_not_found",
"translation": "Nie znaleziono członka."
},
{
"id": "store.sql_autotranslation.meta_json.app_error",
"translation": "Nie udało się przeanalizować metadanych tłumaczenia JSON."
},
{
"id": "store.sql_autotranslation.query_build_error",
"translation": "Nie udało się utworzyć zapytania."
},
{
"id": "store.sql_autotranslation.save.app_error",
"translation": "Nie można zapisać tłumaczenia."
},
{
"id": "store.sql_autotranslation.save.invalid_translation",
"translation": "Walidacja tłumaczenia nie powiodła się. Obiekt tłumaczenia jest nieprawidłowy."
},
{
"id": "store.sql_autotranslation.save.meta_json.app_error",
"translation": "Nie udało się serializować metadanych tłumaczenia do JSON."
},
{
"id": "store.sql_autotranslation.set_channel_enabled.app_error",
"translation": "Nie można ustawić statusu włączenia autotranslacji Kanału."
},
{
"id": "store.sql_autotranslation.set_user_enabled.app_error",
"translation": "Nie można ustawić statusu włączonej autotranslacji użytkownika."
},
{
"id": "api.post.burn_post.user_not_in_channel.app_error",
"translation": "Nie masz uprawnień do zniszczenia tego posta. Musisz być członkiem Kanału."
@ -11754,5 +11674,45 @@
{
"id": "app.recap.update.app_error",
"translation": "Nie udało się zaktualizować podsumowania."
},
{
"id": "ent.autotranslation.add_task.missing_object_id",
"translation": "Zadanie tłumaczenia musi zawierać identyfikator obiektu."
},
{
"id": "ent.autotranslation.add_task.missing_object_type",
"translation": "Zadanie tłumaczenia musi zawierać typ obiektu."
},
{
"id": "ent.autotranslation.add_task.nil_task",
"translation": "Zadanie tłumaczenia nie może mieć wartości null."
},
{
"id": "ent.autotranslation.create_translation_failed",
"translation": "Nie udało się utworzyć tłumaczenia."
},
{
"id": "ent.autotranslation.detect_language.nil_context",
"translation": "Kontekst jest niezbędny do wykrywania języka."
},
{
"id": "ent.autotranslation.detect_language.text_too_large",
"translation": "Tekst przekracza maksymalny rozmiar dla wykrywania języka."
},
{
"id": "ent.autotranslation.detect_remote.error",
"translation": "Nie udało się wykryć języka przy użyciu dostawcy tłumaczeń."
},
{
"id": "ent.autotranslation.extract_content.mask_error",
"translation": "Nie udało się zamaskować zawartości do tłumaczenia."
},
{
"id": "ent.autotranslation.extract_post.nil_post",
"translation": "Obiekt Post nie może mieć wartości null."
},
{
"id": "ent.autotranslation.feature_unavailable",
"translation": "Automatyczne tłumaczenie nie jest dostępne. Skontaktuj się z administratorem systemu, aby Aktualizuj licencję korporacyjną."
}
]

View file

@ -791,10 +791,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Não foi possível encontrar o canal especificado Por favor use o [identificador de canal](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) para identificar os canais."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Não foi possível silenciar o canal {{.Channel}} porque você não é um membro."
},
{
"id": "api.command_mute.success_mute",
"translation": "Você não irá receber notificações de {{.Channel}} até que o mudo do canal seja desativado."

View file

@ -787,10 +787,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Canalul specificat nu a putut găsi. Vă rugăm să folosiţi [mâner canal](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) pentru a identifica canalele."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Ar putea dezactiva canalul {{.Channel}} ca nu sunteti membru."
},
{
"id": "api.command_mute.success_mute",
"translation": "Nu veți primi notificări pentru {{.Channel}} până când opțiunea de a muți canalului este dezactivată."

View file

@ -791,10 +791,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Не удалось найти указанный канал. Пожалуйста, используйте [дескриптор канала](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) для идентификации каналов."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Не удалось отключить канал {{.Channel}}, поскольку вы не являетесь его участником."
},
{
"id": "api.command_mute.success_mute",
"translation": "Вы не будете получать уведомления для {{.Channel}} до тех пор, пока отключен звук."

View file

@ -3590,10 +3590,6 @@
"id": "api.command_mute.success_mute",
"translation": "Obvestil kanala {{.Channel}} ne boste prejemali dokler ne izključite utišanje kanala."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Kanala {{.Channel}} ni mogoče utišati ker niste član kanala."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Podanega kanala ni bilo mogoče najti. Prosim uporabite [channel handle](https://about.mattermost.com/default-channel-handle-documentation) za identifikacijo kanalov."

View file

@ -5910,10 +5910,6 @@
"id": "api.command_mute.success_mute",
"translation": "Du kommer inte ta emot notifieringar om {{.Channel}} tills tyst kanal är avaktiverad."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Kunde inte tysta kanalen {{.Channel}} eftersom du inte är medlem."
},
{
"id": "api.command_mute.no_channel.error",
"translation": "Hittar ej den utpekade kanalen. Använd [channel handle](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) för att identifiera kanaler."
@ -10519,10 +10515,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Det gick inte att ta bort policy för åtkomstkontroll från kanaler."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Kunde inte ändra aktiv status för policy för åtkomstkontroll."
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "Misslyckades med att utvärdera policy för åtkomstkontroll."
@ -11051,18 +11043,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "Leverantören av autotranslation stöds inte."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "Tidsgränsen för hämtning av autotranslation är ogiltig. Måste vara ett positivt tal."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "Tidsgränsen för hämtning av autotranslation av nytt inlägg är ogiltig. Måste vara ett positivt tal."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Tidsgränsen för avisering av autotranslation är ogiltig. Måste vara ett positivt tal."
},
{
"id": "model.oauth.validate_grant.credentials.app_error",
"translation": "Ogiltiga klient-uppgifter."
@ -11083,14 +11063,6 @@
"id": "model.post.query_params.invalid_time_field",
"translation": "Ogiltigt tidsfält."
},
{
"id": "store.sql_autotranslation.channel_not_found",
"translation": "Kanal hittades inte."
},
{
"id": "store.sql_autotranslation.member_not_found",
"translation": "Medlem hittades inte."
},
{
"id": "app.post.rewrite.empty_response",
"translation": "Tomt svar från AI."
@ -11103,14 +11075,6 @@
"id": "api.templates.guest_magic_link_body.title",
"translation": "Logga in till Mattermost"
},
{
"id": "store.sql_autotranslation.get.app_error",
"translation": "Kunde inte hämta översättning."
},
{
"id": "store.sql_autotranslation.save.app_error",
"translation": "Kunde inte spara översättningen."
},
{
"id": "app.agents.get_services.app_error",
"translation": "Misslyckades med hämtningen av LLM-tjänster."
@ -11147,14 +11111,6 @@
"id": "model.post.decode_cursor.unsupported_version",
"translation": "Pekar-version stöds inte."
},
{
"id": "store.sql_autotranslation.get_active_languages.app_error",
"translation": "Kunde inte hämta aktiva språk."
},
{
"id": "store.sql_autotranslation.get_user_language.app_error",
"translation": "Kunde inte hämta användarens språk."
},
{
"id": "app.agents.get_agents.app_error",
"translation": "Hämtningen av agenterna misslyckades."
@ -11191,10 +11147,6 @@
"id": "model.post.query_params.invalid_cursor_id",
"translation": "Ogiltigt cursor-id."
},
{
"id": "store.sql_autotranslation.query_build_error",
"translation": "Misslyckades att skapa frågan."
},
{
"id": "model.post.query_params.invalid_sort_direction",
"translation": "Ogiltig sorteringsriktning."
@ -11231,10 +11183,6 @@
"id": "model.post.decode_cursor.invalid_format",
"translation": "Ogiltigt pekar-format: 8 delar förväntades."
},
{
"id": "store.sql_autotranslation.meta_json.app_error",
"translation": "Misslyckades att tolka översättningens JSON-metadata."
},
{
"id": "api.user.login_by_intune.not_available.app_error",
"translation": "Microsoft Intune-autentiseringen är inte tillgänglig."

View file

@ -789,10 +789,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Belirtilen kanal bulunamadı. Lütfen kanalları belirtmek için [kanal kısaltması](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) kullanın."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Üyesi olmadığınızdan {{.Channel}} kanalının bildirimlerini kapatamazsınız."
},
{
"id": "api.command_mute.success_mute",
"translation": "Bildirimleri açana kadar {{.Channel}} kanalından bildirim almayacaksınız."
@ -10671,10 +10667,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "Kanallardan erişim denetimi ilkesi ataması kaldırılamadı."
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "Erişim denetimi ilkesinin etkin durumu değiştirilemedi."
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "Erişim denetimi ilkesi değerlendirilemedi."
@ -10991,18 +10983,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "Otomatik çeviri hizmeti sağlayıcı desteklenmiyor."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "Otomatik çeviri ayarları için alma zaman aşımı değeri geçersiz. Pozitif bir sayı olmalıdır."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "Otomatik çeviri ayarları için yeni gönderi zaman aşımı değeri geçersiz. Pozitif bir sayı olmalıdır."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Otomatik çeviri ayarları için bildirim zaman aşımı değeri geçersiz. Pozitif bir sayı olmalıdır."
},
{
"id": "api.channel.get_channel.flagged_post_mismatch.app_error",
"translation": "Kanal kimliği, işaretlenmiş iletinin kanal kimliğiyle eşleşmiyor."

View file

@ -791,10 +791,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "Не вдалося знайти вказаний канал. Будь ласка, використовуйте [гештег каналу](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) для ідентифікаціі каналів."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Не вдалося вимкнути звук в каналі {{.Channel}}, оскільки ви не є його учасником."
},
{
"id": "api.command_mute.success_mute",
"translation": "Ви не будете отримувати сповіщення для {{.Channel}}, поки вимкнений звук каналу."

View file

@ -2111,10 +2111,6 @@
"id": "model.channel.is_valid.name.app_error",
"translation": "Tên kênh không hợp lệ. ID người dùng không được phép có trong tên kênh cho các kênh tin nhắn không trực tiếp."
},
{
"id": "api.command_mute.not_member.error",
"translation": "Không thể tắt tiếng kênh {{.Channel}} vì bạn không phải là thành viên."
},
{
"id": "app.import.validate_user_import_data.password_length.error",
"translation": "Mật khẩu người dùng có độ dài không hợp lệ."

View file

@ -787,10 +787,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "无法找到指定的频道。请使用[频道识别](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel) 以分辨频道。"
},
{
"id": "api.command_mute.not_member.error",
"translation": "无法静音频道 {{.Channel}} 因为您不是成员。"
},
{
"id": "api.command_mute.success_mute",
"translation": "您将不会收到 {{.Channel}} 的通知直到取消频道静音。"
@ -10493,10 +10489,6 @@
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
"translation": "无法从频道取消分配访问控制策略。"
},
{
"id": "app.pap.update_access_control_policy_active.app_error",
"translation": "无法更改访问控制策略的活跃状态。"
},
{
"id": "app.pdp.access_evaluation.app_error",
"translation": "评估访问控制策略失败。"
@ -11085,18 +11077,6 @@
"id": "model.config.is_valid.autotranslation.provider.unsupported.app_error",
"translation": "不支持的自动翻译服务提供商。"
},
{
"id": "model.config.is_valid.autotranslation.timeouts.fetch.app_error",
"translation": "自动翻译设置的获取超时无效。必须为正数。"
},
{
"id": "model.config.is_valid.autotranslation.timeouts.new_post.app_error",
"translation": "自动翻译设置的新消息超时无效。必须为正数。"
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "自动翻译设置的通知超时无效。必须为正数。"
},
{
"id": "api.command.move_command.creator_no_permission.app_error",
"translation": "没有移动命令的权限"
@ -11600,65 +11580,5 @@
{
"id": "model.post.query_params.invalid_time_field",
"translation": "无效的时间字段。"
},
{
"id": "store.sql_autotranslation.channel_not_found",
"translation": "未找到频道。"
},
{
"id": "store.sql_autotranslation.get.app_error",
"translation": "无法获取翻译。"
},
{
"id": "store.sql_autotranslation.get_active_languages.app_error",
"translation": "无法获取可用语言。"
},
{
"id": "store.sql_autotranslation.get_channel_enabled.app_error",
"translation": "无法获取频道自动翻译启用状态。"
},
{
"id": "store.sql_autotranslation.get_user_enabled.app_error",
"translation": "无法获取用户自动翻译启用状态。"
},
{
"id": "store.sql_autotranslation.get_user_language.app_error",
"translation": "无法获取用户语言。"
},
{
"id": "store.sql_autotranslation.is_channel_enabled.app_error",
"translation": "无法获取频道自动翻译启用状态。"
},
{
"id": "store.sql_autotranslation.member_not_found",
"translation": "未找到成员。"
},
{
"id": "store.sql_autotranslation.meta_json.app_error",
"translation": "解析翻译元数据 JSON 失败。"
},
{
"id": "store.sql_autotranslation.query_build_error",
"translation": "构建查询失败。"
},
{
"id": "store.sql_autotranslation.save.app_error",
"translation": "无法保存翻译内容。"
},
{
"id": "store.sql_autotranslation.save.invalid_translation",
"translation": "翻译校验失败。翻译对象无效。"
},
{
"id": "store.sql_autotranslation.save.meta_json.app_error",
"translation": "序列化翻译元数据至 JSON 失败。"
},
{
"id": "store.sql_autotranslation.set_channel_enabled.app_error",
"translation": "无法设置频道自动翻译启用状态。"
},
{
"id": "store.sql_autotranslation.set_user_enabled.app_error",
"translation": "无法设置用户自动翻译启用状态。"
}
]

View file

@ -785,10 +785,6 @@
"id": "api.command_mute.no_channel.error",
"translation": "找不到指定的頻道。請用[頻道代號](https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel)找出頻道。"
},
{
"id": "api.command_mute.not_member.error",
"translation": "由於不是頻道成員,無法對頻道 {{.Channel}} 靜音。"
},
{
"id": "api.command_mute.success_mute",
"translation": "直到頻道靜音關閉為止,將不會收到來自 {{.Channel}} 的通知。"

View file

@ -2787,6 +2787,7 @@ type AutoTranslationSettings struct {
Enable *bool `access:"site_localization,cloud_restrictable"`
Provider *string `access:"site_localization,cloud_restrictable"`
TargetLanguages *[]string `access:"site_localization,cloud_restrictable"`
Workers *int `access:"site_localization,cloud_restrictable"`
TimeoutMs *int `access:"site_localization,cloud_restrictable"`
LibreTranslate *LibreTranslateProviderSettings `access:"site_localization,cloud_restrictable"`
Agents *AgentsProviderSettings `access:"site_localization,cloud_restrictable"`
@ -2815,6 +2816,10 @@ func (s *AutoTranslationSettings) SetDefaults() {
s.TargetLanguages = &[]string{"en"}
}
if s.Workers == nil {
s.Workers = NewPointer(4)
}
if s.TimeoutMs == nil {
s.TimeoutMs = NewPointer(5000)
}
@ -4823,6 +4828,11 @@ func (s *AutoTranslationSettings) isValid() *AppError {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.timeout.app_error", nil, "", http.StatusBadRequest)
}
// Validate workers if set (must be between 1 and 32)
if s.Workers != nil && (*s.Workers < 1 || *s.Workers > 32) {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.workers.app_error", nil, "", http.StatusBadRequest)
}
return nil
}

View file

@ -47,6 +47,7 @@ const (
JobTypePushProxyAuth = "push_proxy_auth"
JobTypeRecap = "recap"
JobTypeDeleteExpiredPosts = "delete_expired_posts"
JobTypeAutoTranslationRecovery = "autotranslation_recovery"
JobStatusPending = "pending"
JobStatusInProgress = "in_progress"

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import type {MultiValueProps} from 'react-select/dist/declarations/src/components/MultiValue';
import type {MultiValueProps} from 'react-select';
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import type {MultiValueProps} from 'react-select/dist/declarations/src/components/MultiValue';
import type {MultiValueProps} from 'react-select';
import CloseCircleSolidIcon from 'components/widgets/icons/close_circle_solid_icon';

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import type {MultiValueProps} from 'react-select/dist/declarations/src/components/MultiValue';
import type {MultiValueProps} from 'react-select';
import type {Group} from '@mattermost/types/groups';
import type {Team} from '@mattermost/types/teams';

View file

@ -4,8 +4,7 @@
import type {JSX} from 'react';
import React from 'react';
import {useSelector} from 'react-redux';
import type {SingleValueProps} from 'react-select';
import type {MultiValueProps} from 'react-select/dist/declarations/src/components/MultiValue';
import type {SingleValueProps, MultiValueProps} from 'react-select';
import type {Group} from '@mattermost/types/groups';
import type {Team} from '@mattermost/types/teams';

View file

@ -40,6 +40,7 @@ interface Props {
gmUsers?: UserProfile[];
canEditChannelProperties: boolean;
actions: {
editChannelName: () => void;
editChannelPurpose: () => void;
editChannelHeader: () => void;
};

View file

@ -6,7 +6,7 @@ import React from 'react';
import type {Channel} from '@mattermost/types/channels';
import type {DeepPartial} from '@mattermost/types/utilities';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
import type {GlobalState} from 'types/store';
@ -112,12 +112,15 @@ describe('channel_info_rhs/about_area_channel', () => {
const defaultProps = {
channel: {
id: 'test-c-id',
name: 'my-channel',
header: 'my channel header',
purpose: 'my channel purpose',
display_name: 'My Channel',
} as Channel,
channelURL: 'https://my-url.mm',
canEditChannelProperties: true,
actions: {
editChannelName: jest.fn(),
editChannelPurpose: jest.fn(),
editChannelHeader: jest.fn(),
},
@ -144,4 +147,25 @@ describe('channel_info_rhs/about_area_channel', () => {
expect(screen.getByText('my channel header')).toBeInTheDocument();
});
test('should trigger editChannelName when clicking channel display name', () => {
const props = {
...defaultProps,
actions: {
...defaultProps.actions,
editChannelName: jest.fn(),
},
};
renderWithContext(
<AboutAreaChannel
{...props}
/>,
initialState,
);
const editButtons = screen.getAllByLabelText('Edit');
fireEvent.click(editButtons[0]);
expect(props.actions.editChannelName).toHaveBeenCalled();
});
});

View file

@ -7,17 +7,36 @@ import styled from 'styled-components';
import type {Channel} from '@mattermost/types/channels';
import CopyButton from 'components/copy_button';
import Markdown from 'components/markdown';
import EditableArea from './components/editable_area';
import LineLimiter from './components/linelimiter';
const ChannelId = styled.div`
const ChannelName = styled.div`
margin-bottom: 12px;
font-size: 20px;
font-family: Metropolis, sans-serif;
font-weight: 600;
letter-spacing: -0.01em;
`;
const ChannelId = styled.div`
padding: 4px 0;
margin-bottom: 8px;
font-size: 11px;
line-height: 16px;
letter-spacing: 0.02em;
color: rgba(var(--center-channel-color-rgb), 0.75);
&:not(:last-child) {
margin-bottom: 0px;
}
.post-code__clipboard {
opacity: 0;
}
&:hover .post-code__clipboard {
opacity: 1;
}
`;
const ChannelPurpose = styled.div`
@ -29,23 +48,36 @@ const ChannelPurpose = styled.div`
const ChannelDescriptionHeading = styled.div`
color: rgba(var(--center-channel-color-rgb), 0.75);
font-size: 12px;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 16px;
letter-spacing: 0.24px;
text-transform: uppercase;
padding: 6px 0px;
padding: 4px 0px;
`;
const ChannelHeader = styled.div`
margin-bottom: 12px;
`;
const SmallCopyButton = styled(CopyButton)`
i {
font-size: 14px;
margin-left: 4px;
color: rgba(var(--center-channel-color-rgb), 0.64);
&:hover {
color: rgba(var(--center-channel-color-rgb), 0.88);
}
}
`;
interface Props {
channel: Channel;
canEditChannelProperties: boolean;
actions: {
editChannelName: () => void;
editChannelPurpose: () => void;
editChannelHeader: () => void;
};
@ -56,6 +88,16 @@ const AboutAreaChannel = ({channel, canEditChannelProperties, actions}: Props) =
return (
<>
<ChannelName>
<EditableArea
editable={canEditChannelProperties}
content={<div>{channel.display_name}</div>}
onEdit={actions.editChannelName}
editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_name', defaultMessage: 'Rename channel'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_name', defaultMessage: 'Rename channel'})}
/>
</ChannelName>
{(channel.purpose || canEditChannelProperties) && (
<ChannelPurpose>
<ChannelDescriptionHeading>
@ -74,6 +116,7 @@ const AboutAreaChannel = ({channel, canEditChannelProperties, actions}: Props) =
</LineLimiter>
)}
onEdit={actions.editChannelPurpose}
editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_purpose', defaultMessage: 'Edit channel purpose'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_purpose', defaultMessage: 'Add a channel purpose'})}
/>
</ChannelPurpose>
@ -97,14 +140,27 @@ const AboutAreaChannel = ({channel, canEditChannelProperties, actions}: Props) =
)}
editable={canEditChannelProperties}
onEdit={actions.editChannelHeader}
editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_header', defaultMessage: 'Edit channel header'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_header', defaultMessage: 'Add a channel header'})}
/>
</ChannelHeader>
)}
<ChannelId>
{formatMessage({id: 'channel_info_rhs.about_area_id', defaultMessage: 'ID:'})} {channel.id}
{formatMessage({id: 'channel_info_rhs.about_area_handle', defaultMessage: 'Channel handle:'})} {channel.name}
<SmallCopyButton
content={channel.name}
isForText={true}
/>
</ChannelId>
<ChannelId>
{formatMessage({id: 'channel_info_rhs.about_area_id', defaultMessage: 'ID:'})} {channel.id}
<SmallCopyButton
content={channel.id}
isForText={true}
/>
</ChannelId>
</>
);
};

View file

@ -128,6 +128,7 @@ const AboutAreaDM = ({channel, dmUser, actions}: Props) => {
)}
editable={true}
onEdit={actions.editChannelHeader}
editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_header', defaultMessage: 'Edit channel header'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_header', defaultMessage: 'Add a channel header'})}
/>
</ChannelHeader>

View file

@ -120,6 +120,7 @@ const AboutAreaGM = ({channel, gmUsers, actions}: Props) => {
)}
editable={true}
onEdit={actions.editChannelHeader}
editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_header', defaultMessage: 'Edit channel header'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_header', defaultMessage: 'Add a channel header'})}
/>
</ChannelHeader>

View file

@ -8,6 +8,7 @@ import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import {act, renderWithContext} from 'tests/react_testing_utils';
import {ModalIdentifiers} from 'utils/constants';
import ChannelInfoRHS from './channel_info_rhs';
@ -48,6 +49,7 @@ describe('channel_info_rhs', () => {
beforeEach(() => {
props = {...OriginalProps};
mockAboutArea.mockClear();
});
describe('about area', () => {
@ -88,4 +90,27 @@ describe('channel_info_rhs', () => {
);
});
});
test('editChannelName opens Rename Channel modal', () => {
props.currentTeam = {name: 'team-1'} as Team;
renderWithContext(
<ChannelInfoRHS
{...props}
/>,
);
// Invoke the handler passed into the mocked AboutArea
const lastArgs = mockAboutArea.mock.calls[mockAboutArea.mock.calls.length - 1][0];
lastArgs.actions.editChannelName();
expect(props.actions.openModal).toHaveBeenCalledWith(
expect.objectContaining({
modalId: ModalIdentifiers.RENAME_CHANNEL,
dialogProps: expect.objectContaining({
channel: props.channel,
teamName: 'team-1',
}),
}),
);
});
});

View file

@ -2,18 +2,24 @@
// See LICENSE.txt for license information.
import React, {memo} from 'react';
import {FormattedMessage} from 'react-intl';
import styled from 'styled-components';
import type {Channel, ChannelStats} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import {Permissions} from 'mattermost-redux/constants';
import ChannelInviteModal from 'components/channel_invite_modal';
import ChannelNotificationsModal from 'components/channel_notifications_modal';
import Scrollbars from 'components/common/scrollbars';
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
import EditChannelPurposeModal from 'components/edit_channel_purpose_modal';
import MoreDirectChannels from 'components/more_direct_channels';
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
import RenameChannelModal from 'components/rename_channel_modal';
import UnarchiveChannelModal from 'components/unarchive_channel_modal';
import Constants, {ModalIdentifiers} from 'utils/constants';
import {getSiteURL} from 'utils/url';
@ -25,12 +31,44 @@ import Header from './header';
import Menu from './menu';
import TopButtons from './top_buttons';
const Container = styled.div`
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
`;
const Divider = styled.div`
width: 88%;
border: 1px solid rgba(var(--center-channel-color-rgb), 0.04);
margin: 0 auto;
`;
const ArchivedNoticeContainer = styled.div`
margin: 24px 24px 0 24px;
`;
const ArchivedNotice = styled.div`
.sectionNoticeIcon {
width: 24px;
height: 24px;
}
.sectionNoticeTitle {
color: rgba(var(--center-channel-color-rgb), 0.88);
display: inline;
align-items: center;
gap: 8px;
}
.sectionNoticeTitle .sectionNoticeButton {
margin: 0;
padding: 0;
display: inline;
margin: 0 0 2px 4px;
}
`;
export interface DMUser {
user: UserProfile;
display_name: string;
@ -129,12 +167,24 @@ const ChannelInfoRhs = ({
dialogProps: {channel},
});
const editChannelName = () => actions.openModal({
modalId: ModalIdentifiers.RENAME_CHANNEL,
dialogType: RenameChannelModal,
dialogProps: {channel, teamName: currentTeam.name},
});
const openNotificationSettings = () => actions.openModal({
modalId: ModalIdentifiers.CHANNEL_NOTIFICATIONS,
dialogType: ChannelNotificationsModal,
dialogProps: {channel, currentUser, focusOriginElement: 'channelInfoRHSNotificationSettings'},
});
const openUnarchiveChannel = () => actions.openModal({
modalId: ModalIdentifiers.UNARCHIVE_CHANNEL,
dialogType: UnarchiveChannelModal,
dialogProps: {channel},
});
const gmUsers = channelMembers.filter((user) => {
return user.id !== currentUser.id;
});
@ -148,45 +198,83 @@ const ChannelInfoRhs = ({
>
<Header
channel={channel}
isArchived={isArchived}
isMobile={isMobile}
onClose={actions.closeRightHandSide}
/>
<Scrollbars
color='--center-channel-color-rgb'
>
<TopButtons
channelType={channel.type}
channelURL={channelURL}
isFavorite={isFavorite}
isMuted={isMuted}
isInvitingPeople={isInvitingPeople}
canAddPeople={canManageMembers}
actions={{toggleFavorite, toggleMute, addPeople}}
/>
<AboutArea
channel={channel}
dmUser={dmUser}
gmUsers={gmUsers}
canEditChannelProperties={canEditChannelProperties}
actions={{
editChannelHeader,
editChannelPurpose,
}}
/>
<Divider/>
<Menu
channel={channel}
channelStats={channelStats}
isArchived={isArchived}
actions={{
openNotificationSettings,
showChannelFiles: actions.showChannelFiles,
showPinnedPosts: actions.showPinnedPosts,
showChannelMembers: actions.showChannelMembers,
getChannelStats: actions.getChannelStats,
}}
/>
<Container>
{isArchived && (
<ArchivedNoticeContainer className='sectionNoticeContainer warning'>
<ArchivedNotice className='sectionNoticeContent'>
<i className='icon icon-archive-outline sectionNoticeIcon'/>
<div className='sectionNoticeBody'>
<h4 className='sectionNoticeTitle'>
<FormattedMessage
id='channel_info_rhs.archived.title'
defaultMessage='This channel is archived.'
/>
{channel.name !== Constants.DEFAULT_CHANNEL && (
<ChannelPermissionGate
channelId={channel.id}
teamId={channel.team_id}
permissions={[Permissions.MANAGE_TEAM]}
>
<button
type='button'
className='sectionNoticeButton btn btn-link'
onClick={() => {
openUnarchiveChannel();
}}
>
<FormattedMessage
id='channel_info_rhs.archived.unarchive'
defaultMessage='Unarchive'
/>
</button>
</ChannelPermissionGate>
)}
</h4>
</div>
</ArchivedNotice>
</ArchivedNoticeContainer>
)}
<TopButtons
channelType={channel.type}
channelURL={channelURL}
isFavorite={isFavorite}
isMuted={isMuted}
isInvitingPeople={isInvitingPeople}
isArchived={isArchived}
canAddPeople={!isArchived && canManageMembers}
actions={{toggleFavorite, toggleMute, addPeople}}
/>
<AboutArea
channel={channel}
dmUser={dmUser}
gmUsers={gmUsers}
canEditChannelProperties={canEditChannelProperties}
actions={{
editChannelName,
editChannelHeader,
editChannelPurpose,
}}
/>
<Divider/>
<Menu
channel={channel}
channelStats={channelStats}
isArchived={isArchived}
actions={{
openNotificationSettings,
showChannelFiles: actions.showChannelFiles,
showPinnedPosts: actions.showPinnedPosts,
showChannelMembers: actions.showChannelMembers,
getChannelStats: actions.getChannelStats,
}}
/>
</Container>
</Scrollbars>
</div>
);

View file

@ -5,13 +5,17 @@ import React from 'react';
import {useIntl} from 'react-intl';
import styled from 'styled-components';
import WithTooltip from 'components/with_tooltip';
const EditButton = styled.button`
border: 0;
margin: 0px;
padding: 0px;
border-radius: 4px;
background: rgba(var(--center-channel-color-rgb), 0.04);
color: rgba(var(--center-channel-color-rgb), 0.75);
background: none;
position: relative;
top: -2px;
color: rgba(var(--center-channel-color-rgb), 0.64);
&:hover {
background: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), 0.75);
@ -47,9 +51,10 @@ interface EditableAreaProps {
emptyLabel: string;
onEdit: () => void;
className?: string;
editTooltip?: string;
}
const EditableAreaBase = ({editable, content, emptyLabel, onEdit, className}: EditableAreaProps) => {
const EditableAreaBase = ({editable, content, emptyLabel, onEdit, className, editTooltip}: EditableAreaProps) => {
const {formatMessage} = useIntl();
const allowEditArea = editable && content;
@ -70,12 +75,14 @@ const EditableAreaBase = ({editable, content, emptyLabel, onEdit, className}: Ed
</div>
<div className='EditableArea__edit'>
{allowEditArea ? (
<EditButton
onClick={onEdit}
aria-label={formatMessage({id: 'channel_info_rhs.edit_link', defaultMessage: 'Edit'})}
>
<i className='icon icon-pencil-outline'/>
</EditButton>
<WithTooltip title={editTooltip || formatMessage({id: 'channel_info_rhs.edit_link', defaultMessage: 'Edit'})}>
<EditButton
onClick={onEdit}
aria-label={formatMessage({id: 'channel_info_rhs.edit_link', defaultMessage: 'Edit'})}
>
<i className='icon icon-pencil-outline'/>
</EditButton>
</WithTooltip>
) : ''}
</div>
</div>
@ -90,14 +97,13 @@ const EditableArea = styled(EditableAreaBase)`
margin-bottom:0;
}
}
&:hover {
&>.EditableArea__edit {
visibility: visible;
}
&:hover > .EditableArea__edit,
&:focus-within > .EditableArea__edit {
opacity: 1;
}
&>.EditableArea__edit {
visibility: hidden;
opacity: 0;
width: 24px;
}
`;

View file

@ -10,16 +10,16 @@ import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import Header from './header';
describe('channel_info_rhs/header', () => {
test('should the current channel name', () => {
test('renders the header title', () => {
renderWithContext(
<Header
channel={{display_name: 'my channel title'} as Channel}
isMobile={false}
isArchived={false}
onClose={() => {}}
/>,
);
expect(screen.getByText('Info')).toBeInTheDocument();
expect(screen.getByText('my channel title')).toBeInTheDocument();
});
test('should call onClose when clicking on the close icon', async () => {
@ -29,7 +29,6 @@ describe('channel_info_rhs/header', () => {
<Header
channel={{display_name: 'my channel title'} as Channel}
isMobile={false}
isArchived={false}
onClose={onClose}
/>,
);
@ -45,7 +44,6 @@ describe('channel_info_rhs/header', () => {
<Header
channel={{display_name: 'my channel title'} as Channel}
isMobile={true}
isArchived={false}
onClose={onClose}
/>,
);
@ -54,28 +52,4 @@ describe('channel_info_rhs/header', () => {
expect(onClose).toHaveBeenCalled();
});
test('should have archived icon when channel is archived', () => {
const {container} = renderWithContext(
<Header
channel={{display_name: 'my channel title'} as Channel}
isMobile={false}
isArchived={true}
onClose={() => {}}
/>,
);
expect(container.querySelector('i.icon-archive-outline')).toBeInTheDocument();
});
test('should not have archived icon when channel is archived', () => {
const {container} = renderWithContext(
<Header
channel={{display_name: 'my channel title'} as Channel}
isMobile={false}
isArchived={false}
onClose={() => {}}
/>,
);
expect(container.querySelector('i.icon-archive-outline')).not.toBeInTheDocument();
});
});

View file

@ -11,20 +11,15 @@ import WithTooltip from 'components/with_tooltip';
interface Props {
channel: Channel;
isArchived: boolean;
isMobile: boolean;
onClose: () => void;
}
const Icon = styled.i`
font-size:12px;
`;
const HeaderTitle = styled.span`
line-height: 2.4rem;
`;
const Header = ({channel, isArchived, isMobile, onClose}: Props) => {
const Header = ({channel, isMobile, onClose}: Props) => {
const {formatMessage} = useIntl();
return (
@ -50,12 +45,10 @@ const Header = ({channel, isArchived, isMobile, onClose}: Props) => {
defaultMessage='Info'
/>
</HeaderTitle>
{channel.display_name &&
<span
className='style--none sidebar--right__title__subtitle'
>
{isArchived && (<Icon className='icon icon-archive-outline'/>)}
{channel.display_name}
</span>
}

View file

@ -5,16 +5,30 @@ import React from 'react';
import type {Channel, ChannelStats} from '@mattermost/types/channels';
import {openModal} from 'actions/views/modals';
import {canAccessChannelSettings} from 'selectors/views/channel_settings';
import {
act,
renderWithContext,
screen,
userEvent,
fireEvent,
} from 'tests/react_testing_utils';
import Constants from 'utils/constants';
import Constants, {ModalIdentifiers} from 'utils/constants';
jest.mock('selectors/views/channel_settings', () => ({
canAccessChannelSettings: jest.fn(),
}));
jest.mock('actions/views/modals', () => ({
openModal: jest.fn(() => ({type: 'OPEN_MODAL'})),
}));
import Menu from './menu';
const mockedCanAccessChannelSettings = canAccessChannelSettings as unknown as jest.Mock;
const mockedOpenModal = openModal as unknown as jest.Mock;
describe('channel_info_rhs/menu', () => {
const defaultProps = {
channel: {type: Constants.OPEN_CHANNEL} as Channel,
@ -30,6 +44,8 @@ describe('channel_info_rhs/menu', () => {
};
beforeEach(() => {
mockedOpenModal.mockClear();
mockedCanAccessChannelSettings.mockReset();
defaultProps.actions = {
openNotificationSettings: jest.fn(),
showChannelFiles: jest.fn(),
@ -182,4 +198,90 @@ describe('channel_info_rhs/menu', () => {
const membersItem = screen.queryByText('Members');
expect(membersItem).not.toBeInTheDocument();
});
test('should display Channel Settings and open modal on click (non-DM/GM, not archived, permitted)', async () => {
mockedCanAccessChannelSettings.mockReturnValue(true);
const props = {...defaultProps};
renderWithContext(
<Menu
{...props}
/>,
);
await act(async () => {
props.actions.getChannelStats();
});
const settingsItem = screen.getByText('Channel Settings');
expect(settingsItem).toBeInTheDocument();
fireEvent.click(settingsItem);
expect(mockedOpenModal).toHaveBeenCalledWith(
expect.objectContaining({
modalId: ModalIdentifiers.CHANNEL_SETTINGS,
}),
);
});
test('should NOT display Channel Settings in DM', async () => {
mockedCanAccessChannelSettings.mockReturnValue(true);
const props = {
...defaultProps,
channel: {type: Constants.DM_CHANNEL} as Channel,
};
renderWithContext(
<Menu
{...props}
/>,
);
await act(async () => props.actions.getChannelStats());
expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
});
test('should NOT display Channel Settings in GM', async () => {
mockedCanAccessChannelSettings.mockReturnValue(true);
const props = {
...defaultProps,
channel: {type: Constants.GM_CHANNEL} as Channel,
};
renderWithContext(
<Menu
{...props}
/>,
);
await act(async () => props.actions.getChannelStats());
expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
});
test('should NOT display Channel Settings when archived', async () => {
mockedCanAccessChannelSettings.mockReturnValue(true);
const props = {
...defaultProps,
isArchived: true,
};
renderWithContext(
<Menu
{...props}
/>,
);
await act(async () => props.actions.getChannelStats());
expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
});
test('should NOT display Channel Settings without permission', async () => {
mockedCanAccessChannelSettings.mockReturnValue(false);
const props = {...defaultProps};
renderWithContext(
<Menu
{...props}
/>,
);
await act(async () => props.actions.getChannelStats());
expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
});
});

View file

@ -3,13 +3,20 @@
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux';
import styled from 'styled-components';
import type {Channel, ChannelStats} from '@mattermost/types/channels';
import {openModal} from 'actions/views/modals';
import {canAccessChannelSettings} from 'selectors/views/channel_settings';
import ChannelSettingsModal from 'components/channel_settings_modal/channel_settings_modal';
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
import {Constants} from 'utils/constants';
import {Constants, ModalIdentifiers} from 'utils/constants';
import type {GlobalState} from 'types/store';
const MenuContainer = styled.nav`
display: flex;
@ -116,6 +123,7 @@ interface MenuProps {
export default function Menu(props: MenuProps) {
const {formatMessage} = useIntl();
const dispatch = useDispatch();
const {
channel,
channelStats,
@ -128,7 +136,9 @@ export default function Menu(props: MenuProps) {
const showNotificationPreferences = channel.type !== Constants.DM_CHANNEL && !isArchived;
const showMembers = channel.type !== Constants.DM_CHANNEL;
const showChannelSettings = channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived;
const fileCount = channelStats?.files_count >= 0 ? channelStats?.files_count : 0;
const canAccessSettings = useSelector((state: GlobalState) => canAccessChannelSettings(state, channel.id));
useEffect(() => {
actions.getChannelStats(channel.id, true).then(() => {
@ -139,6 +149,20 @@ export default function Menu(props: MenuProps) {
};
}, [channel.id]);
const openChannelSettings = () => {
dispatch(
openModal({
modalId: ModalIdentifiers.CHANNEL_SETTINGS,
dialogType: ChannelSettingsModal,
dialogProps: {
channelId: channel.id,
focusOriginElement: 'channelInfoRHSChannelSettings',
isOpen: true,
},
}),
);
};
return (
<MenuContainer
className={className}
@ -148,6 +172,17 @@ export default function Menu(props: MenuProps) {
defaultMessage: 'Channel Info Actions',
})}
>
{showChannelSettings && canAccessSettings && (
<MenuItem
id='channelInfoRHSChannelSettings'
icon={<i className='icon icon-cog-outline'/>}
text={formatMessage({
id: 'channel_header.channel_settings',
defaultMessage: 'Channel Settings',
})}
onClick={openChannelSettings}
/>
)}
{showNotificationPreferences && (
<MenuItem
id='channelInfoRHSNotificationSettings'

View file

@ -83,6 +83,7 @@ export interface Props {
isFavorite: boolean;
isMuted: boolean;
isInvitingPeople: boolean;
isArchived?: boolean;
canAddPeople: boolean;
@ -99,6 +100,7 @@ export default function TopButtons({
isFavorite,
isMuted,
isInvitingPeople,
isArchived = false,
canAddPeople: propsCanAddPeople,
actions,
}: Props) {
@ -109,7 +111,7 @@ export default function TopButtons({
successCopyTimeout: 1000,
});
const canAddPeople = ([Constants.OPEN_CHANNEL, Constants.PRIVATE_CHANNEL].includes(channelType) && propsCanAddPeople) || channelType === Constants.GM_CHANNEL;
const canAddPeople = !isArchived && (([Constants.OPEN_CHANNEL, Constants.PRIVATE_CHANNEL].includes(channelType) && propsCanAddPeople) || channelType === Constants.GM_CHANNEL);
const canCopyLink = [Constants.OPEN_CHANNEL, Constants.PRIVATE_CHANNEL].includes(channelType);

View file

@ -747,21 +747,6 @@ export class DotMenuClass extends React.PureComponent<Props, State> {
/>
}
{shouldShowDelete && !isSystemMessage && <Menu.Separator/>}
{shouldShowDelete &&
<Menu.Item
id={`delete_post_${this.props.post.id}`}
data-testid={`delete_post_${this.props.post.id}`}
leadingElement={<TrashCanOutlineIcon size={18}/>}
trailingElements={<span>{'delete'}</span>}
labels={
<FormattedMessage
id='post_info.del'
defaultMessage='Delete'
/>}
onClick={this.handleDeleteMenuItemActivated}
isDestructive={true}
/>
}
{
this.props.canFlagContent &&
<Menu.Item
@ -779,6 +764,21 @@ export class DotMenuClass extends React.PureComponent<Props, State> {
isDestructive={true}
/>
}
{shouldShowDelete &&
<Menu.Item
id={`delete_post_${this.props.post.id}`}
data-testid={`delete_post_${this.props.post.id}`}
leadingElement={<TrashCanOutlineIcon size={18}/>}
trailingElements={<span>{'delete'}</span>}
labels={
<FormattedMessage
id='post_info.del'
defaultMessage='Delete'
/>}
onClick={this.handleDeleteMenuItemActivated}
isDestructive={true}
/>
}
</Menu.Container>
);
}

View file

@ -7,7 +7,7 @@ import {FormattedMessage} from 'react-intl';
import CodeBlock from 'components/code_block/code_block';
type Katex = typeof import('katex');
type Katex = typeof import('katex').default;
type Props = {
content: string;

View file

@ -5,7 +5,7 @@ import type {KatexOptions} from 'katex';
import React, {useState, useEffect} from 'react';
import {FormattedMessage} from 'react-intl';
type Katex = typeof import('katex');
type Katex = typeof import('katex').default;
type Props = {
content: string;

View file

@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux';
import {patchChannel} from 'mattermost-redux/actions/channels';
import RenameChannelModal from './rename_channel_modal';
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators({
patchChannel,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(RenameChannelModal);

View file

@ -0,0 +1,154 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, injectIntl} from 'react-intl';
import type {IntlShape} from 'react-intl';
import type {Channel} from '@mattermost/types/channels';
import type {ActionResult} from 'mattermost-redux/types/actions';
import ChannelNameFormField from 'components/channel_name_form_field/channel_name_form_field';
import {getHistory} from 'utils/browser_history';
import Constants from 'utils/constants';
type Actions = {
patchChannel: (channelId: string, patch: Partial<Channel>) => Promise<ActionResult>;
}
type Props = {
channel: Channel;
teamName: string;
onExited: () => void;
actions: Actions;
intl: IntlShape;
}
type State = {
show: boolean;
displayName: string;
channelUrl: string;
isSaving: boolean;
urlError: string;
}
export class RenameChannelModal extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
show: true,
displayName: props.channel.display_name,
channelUrl: props.channel.name,
isSaving: false,
urlError: '',
};
}
onHide = () => {
this.setState({show: false});
};
handleSave = async () => {
const {channel, actions: {patchChannel}} = this.props;
const {displayName, channelUrl} = this.state;
if (!channel || !displayName?.trim()) {
return;
}
// Validate min/max on display name
const trimmedDisplayName = displayName.trim();
if (trimmedDisplayName.length < Constants.MIN_CHANNELNAME_LENGTH ||
trimmedDisplayName.length > Constants.MAX_CHANNELNAME_LENGTH) {
return;
}
this.setState({isSaving: true});
const {data, error} = await patchChannel(channel.id, {
display_name: trimmedDisplayName,
name: channelUrl.trim(),
});
this.setState({isSaving: false});
if (data && !error) {
this.onHide();
// Use the actual channel name from the response, as the server may have sanitized it
const updatedChannelName = data.name || channelUrl.trim();
const path = `/${this.props.teamName}/channels/${updatedChannelName}`;
getHistory().push(path);
}
};
render() {
const {formatMessage} = this.props.intl;
return (
<Modal
dialogClassName='a11y__modal'
show={this.state.show}
onHide={this.onHide}
onExited={this.props.onExited}
role='dialog'
aria-labelledby='renameChannelModalLabel'
>
<Modal.Header closeButton={true}>
<Modal.Title
componentClass='h1'
id='renameChannelModalLabel'
>
<FormattedMessage
id='rename_channel.title'
defaultMessage='Rename Channel'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<ChannelNameFormField
value={this.state.displayName}
name='rename-channel'
placeholder={formatMessage({
id: 'rename_channel.displayNameHolder',
defaultMessage: 'Enter display name',
})}
onDisplayNameChange={(name) => this.setState({displayName: name})}
onURLChange={(url) => this.setState({channelUrl: url})}
currentUrl={this.state.channelUrl}
readOnly={false}
isEditingExistingChannel={true}
onErrorStateChange={(isError, errorMsg) => this.setState({urlError: isError ? (errorMsg || '') : ''})}
urlError={this.state.urlError}
/>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-tertiary cancel-button'
onClick={this.onHide}
>
<FormattedMessage
id='generic_btn.cancel'
defaultMessage='Cancel'
/>
</button>
<button
type='button'
className='btn btn-primary'
disabled={this.state.isSaving}
onClick={this.handleSave}
>
<FormattedMessage
id='generic_btn.save'
defaultMessage='Save'
/>
</button>
</Modal.Footer>
</Modal>
);
}
}
export default injectIntl(RenameChannelModal);

View file

@ -7,9 +7,15 @@ import type {ComponentProps, RefObject} from 'react';
import type {MessageDescriptor} from 'react-intl';
import {FormattedMessage, defineMessages} from 'react-intl';
import {components} from 'react-select';
import type {InputActionMeta, SelectInstance, MultiValue, GroupBase, MultiValueRemoveProps} from 'react-select';
import type {
InputActionMeta,
SelectInstance,
MultiValue,
GroupBase,
MultiValueRemoveProps,
SelectComponentsConfig,
} from 'react-select';
import AsyncSelect from 'react-select/async';
import type {SelectComponents} from 'react-select/dist/declarations/src/components';
import type {Channel} from '@mattermost/types/channels';
@ -152,7 +158,7 @@ export default class ChannelsInput<T extends Channel> extends React.PureComponen
</div>);
};
components: Partial<SelectComponents<T, true, GroupBase<T>>> = {
components: Partial<SelectComponentsConfig<T, true, GroupBase<T>>> = {
NoOptionsMessage: this.NoOptionsMessage,
MultiValueRemove: this.MultiValueRemove,
IndicatorsContainer: () => null,

View file

@ -2857,7 +2857,6 @@
"admin.site.fileSharingDownloads": "Абмен файламі і загрузкі",
"admin.site.localization": "Лакалізацыя",
"admin.site.localization.autoTranslationInfo": "Аўтапераклад таксама павінен быць уключаны ў кожным канале, дзе ён патрэбны.",
"admin.site.localization.autoTranslationInfoSecondary": "Калі выяўлена некалькі моў, карыстальнікам будзе прапанавана ўключыць аўтапераклад. [Даведацца больш](https://docs.mattermost.com/).",
"admin.site.localization.autoTranslationProviderDescription": "<strong>УВАГА:</strong> Калі вы карыстаецеся знешнімі службамі перакладу (напрыклад, воблачнымі),{br}даныя паведамлення могуць апрацоўвацца па-за межамі вашага асяроддзя.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "Калі ваш сервер LibreTranslate патрабуе ключа API, увядзіце яго тут. У адваротным выпадку пакіньце гэтае поле пустым. Глядзіце <link>дакументацыю LibreTranslate</link> для кіравання ключамі API.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "Увядзіце ключ API LibreTranslate",

View file

@ -2854,7 +2854,6 @@
"admin.site.fileSharingDownloads": "Dateifreigabe und Downloads",
"admin.site.localization": "Lokalisierung",
"admin.site.localization.autoTranslationInfo": "Die automatische Übersetzung muss außerdem in jedem Kanal aktiviert werden, in dem sie benötigt wird.",
"admin.site.localization.autoTranslationInfoSecondary": "Wenn mehrere Sprachen erkannt werden, werden die Nutzer aufgefordert, die automatische Übersetzung zu aktivieren. [Mehr erfahren](https://docs.mattermost.com/).",
"admin.site.localization.autoTranslationProviderDescription": "<strong>HINWEIS:</strong> Wenn du externe Übersetzungsdienste (z. B. in der Cloud) nutzt, können die Daten der Nachricht{br}außerhalb deiner Umgebung verarbeitet werden.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "Wenn dein LibreTranslate-Server einen API-Schlüssel benötigt, gib ihn hier ein. Andernfalls lässt du dieses Feld leer. Siehe <link>LibreTranslate-Dokumente</link> für die Verwaltung von API-Schlüsseln.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "LibreTranslate API Schlüssel eingeben",

View file

@ -2849,7 +2849,6 @@
"admin.site.fileSharingDownloads": "File Sharing and Downloads",
"admin.site.localization": "Localisation",
"admin.site.localization.autoTranslationInfo": "Auto-translation must also be enabled in each channel where it's needed.",
"admin.site.localization.autoTranslationInfoSecondary": "When multiple languages are detected, users will be prompted to enable auto-translation. [Learn more](https://docs.mattermost.com/).",
"admin.site.localization.autoTranslationProviderDescription": "<strong>NOTE:</strong> If using external translation services (e.g. cloud based),{br}message data may be processed outside of your environment.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "If your LibreTranslate server requires an API key, enter it here. Otherwise, leave this field blank. View <link>LibreTranslate docs</link> for API Key management.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "Enter LibreTranslate API Key",

View file

@ -3709,6 +3709,7 @@
"channel_header.unmute": "Unmute Channel",
"channel_header.unmuteConversation": "Unmute",
"channel_header.userHelpGuide": "Help",
"channel_info_rhs.about_area_handle": "Channel handle:",
"channel_info_rhs.about_area_id": "ID:",
"channel_info_rhs.about_area.add_channel_header": "Add a channel header",
"channel_info_rhs.about_area.add_channel_purpose": "Add a channel purpose",
@ -3718,6 +3719,11 @@
"channel_info_rhs.about_area.channel_purpose.heading": "Channel Purpose",
"channel_info_rhs.about_area.channel_purpose.line_limiter.less": "less",
"channel_info_rhs.about_area.channel_purpose.line_limiter.more": "more",
"channel_info_rhs.about_area.edit_channel_header": "Edit channel header",
"channel_info_rhs.about_area.edit_channel_name": "Rename channel",
"channel_info_rhs.about_area.edit_channel_purpose": "Edit channel purpose",
"channel_info_rhs.archived.title": "This channel is archived.",
"channel_info_rhs.archived.unarchive": "Unarchive",
"channel_info_rhs.edit_link": "Edit",
"channel_info_rhs.header.title": "Info",
"channel_info_rhs.menu.files": "Files",
@ -5698,6 +5704,8 @@
"removed_channel.someone": "Someone",
"rename_category_modal.rename": "Rename",
"rename_category_modal.renameCategory": "Rename Category",
"rename_channel.displayNameHolder": "Enter display name",
"rename_channel.title": "Rename Channel",
"restricted_indicator.tooltip.mesage": "During your trial you are able to use this feature.",
"restricted_indicator.tooltip.message.blocked": "This is a paid feature, available with a free {trialLength}-day trial",
"restricted_indicator.tooltip.title": "{minimumPlanRequiredForFeature} feature",

View file

@ -14,7 +14,7 @@
"about.date": "Bygget:",
"about.dbversion": "Versjon av databaseskjema:",
"about.desktopVersion": "Desktop-versjon:",
"about.enterpriseEditionLearn": "Finn ut mer om Mattermost {planName} på ",
"about.enterpriseEditionLearn": "Les mer om Enterprise Edition på {link}",
"about.enterpriseEditionSst": "En meldingsløsning for bedriften, som du kan stole på",
"about.enterpriseEditionSt": "Moderne kommunikasjon bak brannmuren din.",
"about.hash": "Hash for dette bygget:",

View file

@ -2857,7 +2857,6 @@
"admin.site.fileSharingDownloads": "Bestanden Delen en Downloaden",
"admin.site.localization": "Lokalisatie",
"admin.site.localization.autoTranslationInfo": "Automatische vertaling moet ook worden ingeschakeld in elk kanaal waar het nodig is.",
"admin.site.localization.autoTranslationInfoSecondary": "Als er meerdere talen worden gedetecteerd, worden gebruikers gevraagd om automatische vertaling in te schakelen. [Meer informatie](https://docs.mattermost.com/).",
"admin.site.localization.autoTranslationProviderDescription": "<strong>OPMERKING:</strong> Als je externe vertaaldiensten gebruikt (bijvoorbeeld in de cloud), kunnen{br}berichtgegevens buiten je omgeving worden verwerkt.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "Als jouw LibreTranslate server een API-sleutel vereist, voer die dan hier in. Laat dit veld anders leeg. Bekijk <link>LibreTranslate docs</link> voor het beheer van API-sleutels.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "LibreTranslate API sleutel invoeren",

View file

@ -928,7 +928,7 @@
"admin.database.search_backend.title": "Active Search Backend:",
"admin.database.title": "Baza danych",
"admin.developer.title": "Ustawienia programisty",
"admin.elasticsearch.backendDescription": "Typ zaplecza wyszukiwania.",
"admin.elasticsearch.backendDescription": "Typ zaplecza wyszukiwania. Zmiana tego ustawienia wymaga restartu serwera, zanim zacznie obowiązywać.",
"admin.elasticsearch.backendExample": "Na przykład: \"elasticsearch\"",
"admin.elasticsearch.backendTitle": "Typ zaplecza:",
"admin.elasticsearch.bulkIndexingTitle": "Indeksowanie zbiorcze:",
@ -1484,8 +1484,8 @@
"admin.ldap.adminFilterFilterDescHover": "Użytkownicy wybrani przez zapytanie będą mieli dostęp do Twojego serwera Mattermost jako administratorzy systemu. Domyślnie administratorzy systemu mają pełny dostęp do konsoli systemu Mattermost. Istniejący członkowie, którzy są identyfikowani przez ten atrybut, zostaną awansowani z członka do administratora systemu przy następnym logowaniu. Następne logowanie jest oparte na długości sesji ustawionej w Konsoli systemu > Długość sesji. Zaleca się ręczne zdegradowanie użytkowników do członków w Konsoli systemu > Zarządzanie użytkownikami, aby zapewnić natychmiastowe ograniczenie dostępu.\n \nUwaga: Jeśli ten filtr zostanie usunięty/zmieniony, administratorzy systemu, którzy zostali awansowani za pomocą tego filtra, zostaną zdegradowani do członków i nie zachowają dostępu do Konsoli systemu. Gdy ten filtr nie jest używany, administratorzy systemu mogą być ręcznie awansowani/degradowani w Konsoli systemu > Zarządzanie użytkownikami.",
"admin.ldap.adminFilterTitle": "Filtr administratora:",
"admin.ldap.attributeTestFailed": "Test Atrybutu nie powiódł się{showError, select, true {: {error}} other {}}",
"admin.ldap.attributeTestSuccess": "Test Atrybutów zakończony powodzeniem: {countReturned, number} result{countReturned, plural, one {} other {}} znaleziony spośród {totalCount} user{totalCount, plural, one {} other {}} zwróconych przez filtr użytkownika",
"admin.ldap.attributeTestWarning": "Atrybut nie został znaleziony w żadnym z {totalCount} user{totalCount, plural, one {} other {}} zwróconych przez filtr użytkownika",
"admin.ldap.attributeTestSuccess": "Test Atrybutów zakończony powodzeniem:{countReturned, number} {countReturned, plural, one {wynik} other {wyniki}} znalezione spośród {totalCount} {totalCount, plural, one {użytkownika} other {użytlowników}} zwróconych przez filtr użytkownika",
"admin.ldap.attributeTestWarning": "Atrybut nie został znaleziony w żadnym z {totalCount} {totalCount, plural, one {użytkowniku} other {użytkowników}} zwróconych przez filtr użytkownika",
"admin.ldap.baseDesc": "Baza DN jest rozpoznawalną nazwą lokalizacji, w której Mattermost powinien rozpocząć wyszukiwanie obiektów użytkowników i grup w drzewie AD/LDAP.",
"admin.ldap.baseEx": "Np. \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "Base DN:",
@ -1498,13 +1498,13 @@
"admin.ldap.emailAttrDesc": "Atrybut na serwerze AD/LDAP który będzie używany do wypełnienia pola adresu email użytkowników w Mattermost.",
"admin.ldap.emailAttrEx": "Np. \"mail\" lub \"userPrincipalName\"",
"admin.ldap.emailAttrTitle": "Atrybut Email:",
"admin.ldap.enableAdminFilterTitle": "Włącz filtr Administratora",
"admin.ldap.enableAdminFilterTitle": "Włącz filtr administratora:",
"admin.ldap.enableDesc": "Jeśli włączone, Mattermost umożliwi logowanie za pomocą AD/LDAP",
"admin.ldap.enableSyncDesc": "Jeśli włączone, Mattermost periodycznie synchronizuje użytkowników z serwera AD/LDAP. Jeśli wyłączone, atrybuty użytkownika są aktualizowane z serwera AD/LDAP tylko podczas logowania użytkownika.",
"admin.ldap.enableSyncTitle": "Włącz Synchronizacje z AD/LDAP:",
"admin.ldap.enableTitle": "Włącz logowanie za pomocą AD/LDAP:",
"admin.ldap.filterTestFailed": "Test filtra nie powiódł się{showTestValue, select, true {. Użyta wartość: {testValue}} other {}}{showError, select, true {: {error}} other {}}",
"admin.ldap.filterTestSuccess": "Test filtru zakończony powodzeniem: {countReturned, number} result{countReturned, plural, one {} other {}} found{showTestValue, select, true {. Użyta wartość: {testValue}} other {}}",
"admin.ldap.filterTestSuccess": "Test filtru zakończony powodzeniem: {countReturned, number} {countReturned, plural, one {wynik} other {wyników}} znaleziono {showTestValue, select, true {. Value used: {testValue}} other {}}",
"admin.ldap.filterTestWarning": "Test filtra powiódł się, ale nie znaleziono żadnych wyników. Twój filtr może być zbyt restrykcyjny.{showTestValue, select, true { Użyta wartość: {testValue}} other {}}",
"admin.ldap.firstnameAttrDesc": "(Opcjonalnie) Atrybut w serwerze AD/LDAP używany do wypełnienia Nazwy użytkownika w Mattermost.",
"admin.ldap.firstnameAttrDescHover": "Gdy ta opcja jest włączona, użytkownicy nie mogą edytować swojej nazwy, ponieważ jest ona synchronizowana z serwerem LDAP. Jeśli pozostanie puste, użytkownicy mogą ustawić swoje imię w Menu konta > Ustawienia konta > Profil.",
@ -2856,7 +2856,6 @@
"admin.site.fileSharingDownloads": "Udostępnianie plików i ściąganie",
"admin.site.localization": "Języki",
"admin.site.localization.autoTranslationInfo": "Automatyczne tłumaczenie musi być również włączone w każdym kanale, w którym jest potrzebne.",
"admin.site.localization.autoTranslationInfoSecondary": "W przypadku wykrycia wielu języków, użytkownik zostanie poproszony o włączenie automatycznego tłumaczenia. [Dowiedz się więcej](https://docs.mattermost.com/).",
"admin.site.localization.autoTranslationProviderDescription": "<strong>UWAGA:</strong> Jeśli korzystasz z zewnętrznych usług tłumaczeniowych (np. opartych na chmurze), dane wiadomości{br}mogą być przetwarzane poza Twoim środowiskiem.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "Jeśli serwer LibreTranslate wymaga klucza API, wprowadź go tutaj. W przeciwnym razie pozostaw to pole puste. Zapoznaj się z dokumentacją <link>LibreTranslate</link> dotyczącą zarządzania kluczami API.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "Wprowadź klucz API LibreTranslate",

View file

@ -2752,7 +2752,6 @@
"admin.site.fileSharingDownloads": "Dosya paylaşımı ve indirmeler",
"admin.site.localization": "Yerelleştirme",
"admin.site.localization.autoTranslationInfo": "Otomatik çeviri, gerek duyulan her kanalda açılmış olmalıdır.",
"admin.site.localization.autoTranslationInfoSecondary": "Birden fazla dil algılandığında, kullanıcılardan otomatik çeviriyi açmaları istenir. [Ayrıntılı bilgi alın](https://docs.mattermost.com/).",
"admin.site.localization.autoTranslationProviderDescription": "<strong>NOT:</strong> Dış çeviri hizmetleri (bulut tabanlı gibi) kullanılıyorsa,{br}ileti verileri ortamınızın dışında işleniyor olabilir.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "LibreTranslate sunucunuz için bir API anahtarı gerekiyorsa, buraya yazın. Gerekmiyorsa bu alanı boş bırakın. API anahtarı yönetimi için <link>LibreTranslate belgelerine</link> bakabilirsiniz.",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "LibreTranslate API anahtarını yazın",

View file

@ -2843,7 +2843,6 @@
"admin.site.fileSharingDownloads": "文件共享与下载",
"admin.site.localization": "本地化",
"admin.site.localization.autoTranslationInfo": "还需要在每个需要的频道中启用自动翻译功能。",
"admin.site.localization.autoTranslationInfoSecondary": "当检测到多种语言时,用户将收到启用自动翻译的提示。[了解更多](https://docs.mattermost.com/)。",
"admin.site.localization.autoTranslationProviderDescription": "<strong>注意:</strong>如果使用外部翻译服务(如基于云的服务),{br}消息数据可能会在您的环境之外被处理。",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "如果您的 LibreTranslate 服务器需要 API 密钥,请在此处输入。否则,请留空此字段。查看<link>LibreTranslate 文档</link>以了解 API 密钥管理。",
"admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "请输入 LibreTranslate API 密钥",

View file

@ -121,9 +121,10 @@ export function checkCWSAvailability(): ActionFuncAsync {
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_REQUEST});
try {
await Client4.cwsAvailabilityCheck();
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_SUCCESS, data: 'available'});
return {data: 'available'};
const response = await Client4.cwsAvailabilityCheck();
const status = response.status;
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_SUCCESS, data: status});
return {data: status};
} catch (error) {
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_FAILURE});
return {data: 'unavailable'};

View file

@ -21,7 +21,7 @@
.modal-body {
overflow: auto;
max-height: calc(90vh - 80px);
padding: 2px 32px;
padding: 4px 32px;
&.overflow--visible {
overflow: visible;

View file

@ -943,6 +943,7 @@
.channel-header-archived-icon {
position: relative;
top: -1px;
margin-right: 5px;
fill: var(--center-channel-color);
}

View file

@ -129,6 +129,7 @@
font-family: Metropolis, sans-serif;
font-size: 1.6rem;
font-weight: 600;
line-height: 1;
@include mixins.clearfix;
h2 {

View file

@ -1,68 +1,70 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
declare module 'highlight.js/lib/languages/1c.js';
declare module 'highlight.js/lib/languages/actionscript.js';
declare module 'highlight.js/lib/languages/applescript.js';
declare module 'highlight.js/lib/languages/bash.js';
declare module 'highlight.js/lib/languages/clojure.js';
declare module 'highlight.js/lib/languages/coffeescript.js';
declare module 'highlight.js/lib/languages/cpp.js';
declare module 'highlight.js/lib/languages/cs.js';
declare module 'highlight.js/lib/languages/css.js';
declare module 'highlight.js/lib/languages/d.js';
declare module 'highlight.js/lib/languages/dart.js';
declare module 'highlight.js/lib/languages/delphi.js';
declare module 'highlight.js/lib/languages/diff.js';
declare module 'highlight.js/lib/languages/django.js';
declare module 'highlight.js/lib/languages/dockerfile.js';
declare module 'highlight.js/lib/languages/elixir.js';
declare module 'highlight.js/lib/languages/erlang.js';
declare module 'highlight.js/lib/languages/fortran.js';
declare module 'highlight.js/lib/languages/fsharp.js';
declare module 'highlight.js/lib/languages/gcode.js';
declare module 'highlight.js/lib/languages/go.js';
declare module 'highlight.js/lib/languages/groovy.js';
declare module 'highlight.js/lib/languages/handlebars.js';
declare module 'highlight.js/lib/languages/haskell.js';
declare module 'highlight.js/lib/languages/haxe.js';
declare module 'highlight.js/lib/languages/java.js';
declare module 'highlight.js/lib/languages/javascript.js';
declare module 'highlight.js/lib/languages/json.js';
declare module 'highlight.js/lib/languages/julia.js';
declare module 'highlight.js/lib/languages/kotlin.js';
declare module 'highlight.js/lib/languages/less.js';
declare module 'highlight.js/lib/languages/lisp.js';
declare module 'highlight.js/lib/languages/lua.js';
declare module 'highlight.js/lib/languages/makefile.js';
declare module 'highlight.js/lib/languages/markdown.js';
declare module 'highlight.js/lib/languages/matlab.js';
declare module 'highlight.js/lib/languages/objectivec.js';
declare module 'highlight.js/lib/languages/ocaml.js';
declare module 'highlight.js/lib/languages/perl.js';
declare module 'highlight.js/lib/languages/pgsql.js';
declare module 'highlight.js/lib/languages/php.js';
declare module 'highlight.js/lib/languages/powershell.js';
declare module 'highlight.js/lib/languages/puppet.js';
declare module 'highlight.js/lib/languages/python.js';
declare module 'highlight.js/lib/languages/r.js';
declare module 'highlight.js/lib/languages/ruby.js';
declare module 'highlight.js/lib/languages/rust.js';
declare module 'highlight.js/lib/languages/scala.js';
declare module 'highlight.js/lib/languages/scheme.js';
declare module 'highlight.js/lib/languages/scss.js';
declare module 'highlight.js/lib/languages/smalltalk.js';
declare module 'highlight.js/lib/languages/sql.js';
declare module 'highlight.js/lib/languages/stylus.js';
declare module 'highlight.js/lib/languages/swift.js';
declare module 'highlight.js/lib/languages/tex.js';
declare module 'highlight.js/lib/languages/typescript.js';
declare module 'highlight.js/lib/languages/vbnet.js';
declare module 'highlight.js/lib/languages/vbscript.js';
declare module 'highlight.js/lib/languages/verilog.js';
declare module 'highlight.js/lib/languages/vhdl.js';
declare module 'highlight.js/lib/languages/xml.js';
declare module 'highlight.js/lib/languages/yaml.js';
declare module 'highlight.js/lib/languages/1c';
declare module 'highlight.js/lib/languages/actionscript';
declare module 'highlight.js/lib/languages/applescript';
declare module 'highlight.js/lib/languages/bash';
declare module 'highlight.js/lib/languages/clojure';
declare module 'highlight.js/lib/languages/coffeescript';
declare module 'highlight.js/lib/languages/cpp';
declare module 'highlight.js/lib/languages/csharp';
declare module 'highlight.js/lib/languages/css';
declare module 'highlight.js/lib/languages/d';
declare module 'highlight.js/lib/languages/dart';
declare module 'highlight.js/lib/languages/delphi';
declare module 'highlight.js/lib/languages/diff';
declare module 'highlight.js/lib/languages/django';
declare module 'highlight.js/lib/languages/dockerfile';
declare module 'highlight.js/lib/languages/elixir';
declare module 'highlight.js/lib/languages/erlang';
declare module 'highlight.js/lib/languages/fortran';
declare module 'highlight.js/lib/languages/fsharp';
declare module 'highlight.js/lib/languages/gcode';
declare module 'highlight.js/lib/languages/go';
declare module 'highlight.js/lib/languages/groovy';
declare module 'highlight.js/lib/languages/handlebars';
declare module 'highlight.js/lib/languages/haskell';
declare module 'highlight.js/lib/languages/haxe';
declare module 'highlight.js/lib/languages/java';
declare module 'highlight.js/lib/languages/javascript';
declare module 'highlight.js/lib/languages/json';
declare module 'highlight.js/lib/languages/julia';
declare module 'highlight.js/lib/languages/kotlin';
declare module 'highlight.js/lib/languages/latex';
declare module 'highlight.js/lib/languages/less';
declare module 'highlight.js/lib/languages/lisp';
declare module 'highlight.js/lib/languages/lua';
declare module 'highlight.js/lib/languages/makefile';
declare module 'highlight.js/lib/languages/markdown';
declare module 'highlight.js/lib/languages/matlab';
declare module 'highlight.js/lib/languages/objectivec';
declare module 'highlight.js/lib/languages/ocaml';
declare module 'highlight.js/lib/languages/perl';
declare module 'highlight.js/lib/languages/pgsql';
declare module 'highlight.js/lib/languages/php';
declare module 'highlight.js/lib/languages/plaintext';
declare module 'highlight.js/lib/languages/powershell';
declare module 'highlight.js/lib/languages/puppet';
declare module 'highlight.js/lib/languages/python';
declare module 'highlight.js/lib/languages/r';
declare module 'highlight.js/lib/languages/ruby';
declare module 'highlight.js/lib/languages/rust';
declare module 'highlight.js/lib/languages/scala';
declare module 'highlight.js/lib/languages/scheme';
declare module 'highlight.js/lib/languages/scss';
declare module 'highlight.js/lib/languages/smalltalk';
declare module 'highlight.js/lib/languages/sql';
declare module 'highlight.js/lib/languages/stylus';
declare module 'highlight.js/lib/languages/swift';
declare module 'highlight.js/lib/languages/tex';
declare module 'highlight.js/lib/languages/typescript';
declare module 'highlight.js/lib/languages/vbnet';
declare module 'highlight.js/lib/languages/vbscript';
declare module 'highlight.js/lib/languages/verilog';
declare module 'highlight.js/lib/languages/vhdl';
declare module 'highlight.js/lib/languages/xml';
declare module 'highlight.js/lib/languages/yaml';
declare module 'highlight.js/styles/*.css' {
const url: string;

View file

@ -15,7 +15,7 @@
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"module": "es2020",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"noEmit": true,
"experimentalDecorators": true,

View file

@ -4242,7 +4242,7 @@ export default class Client4 {
};
cwsAvailabilityCheck = () => {
return this.doFetchWithResponse(
return this.doFetch<{status: string}>(
`${this.getCloudRoute()}/check-cws-connection`,
{method: 'get'},
);

View file

@ -755,6 +755,7 @@ export type LocalizationSettings = {
export type AutoTranslationSettings = {
Enable: boolean;
TargetLanguages: string[];
Workers: number;
Provider: '' | 'libretranslate' | 'agents';
LibreTranslate: {
URL: string;