MM-66653: migrate i18n extraction from mmjstool to @formatjs/cli (#34498)

* feat(webapp): migrate i18n extraction from mmjstool to @formatjs/cli

Replace custom mmjstool with industry-standard @formatjs/cli for i18n
message extraction. Adds support for localizeMessage as a recognized
extraction function and enables comprehensive ESLint rules for i18n.

Changes by area:

Dependencies (package.json):
- Add @formatjs/cli v6.7.4
- Remove @mattermost/mmjstool dependency
- Replace i18n-extract script with formatjs command
- Remove mmjstool-specific scripts (clean-empty, check-empty-src)
- Add i18n-extract:check for diff validation

Code (utils.tsx):
- Refactor localizeMessage to accept MessageDescriptor format
- Support {id, defaultMessage, description} parameters
- Maintain Redux store access (non-React context compatible)
- Add comprehensive JSDoc documentation

Linting (.eslintrc.json):
- Configure formatjs settings with additionalFunctionNames
- Enable 9 formatjs ESLint rules including enforce-id (error)
- Ensure all messages require explicit manual IDs

CI/CD (webapp-ci.yml):
- Simplify i18n check to formatjs extraction + diff
- Remove mmjstool and mobile-dir references

Context: Second attempt at PR #25830. Uses manual IDs only with
[id] placeholder pattern to enforce explicit ID requirements.
ESLint will catch missing IDs during development.

* chore(webapp): add temporary i18n reconciliation tooling

Add helper script to analyze conflicts between en.json and
defaultMessage values in code. This tooling will be reverted
after initial migration cleanup is complete.

Tooling added:
- scripts/i18n-reconcile-conflicts.js: Analyzes conflicts
  - Detects spelling corrections
  - Identifies placeholder mismatches
  - Flags significant content changes
  - Provides recommendations on which version to keep

- npm script: i18n-reconcile

Usage:
  npm run i18n-reconcile

This will be reverted after reconciling the ~50 duplicate message
warnings and determining correct versions for conflicting messages.

* chore(webapp): upgrade eslint-plugin-formatjs and centralize formatjs deps

Move @formatjs/cli to root webapp package.json alongside other formatjs
dependencies for better monorepo organization. Upgrade eslint-plugin-formatjs
from 4.12.2 to 5.4.2 for latest features and bug fixes.

Changes:
- Upgrade eslint-plugin-formatjs: 4.12.2 → 5.4.2
- Move @formatjs/cli to webapp/package.json (from channels)
- Centralize formatjs tooling at monorepo root level

This provides:
- Better dependency management in monorepo
- Latest formatjs ESLint rules and features
- Consistent tooling across workspaces

* feat(webapp): make localizeMessage fully compatible with formatMessage API

Extend localizeMessage to accept a values parameter for placeholder
interpolation, making it fully compatible with react-intl's formatMessage API.
Remove the now-redundant localizeAndFormatMessage function.

Changes to localizeMessage:
- Add values parameter for {placeholder} interpolation
- Use getIntl() for proper ICU message formatting
- Support full formatMessage feature parity outside React contexts
- Update JSDoc with interpolation examples
- Mark as deprecated (prefer useIntl in React components)

Code cleanup:
- Remove localizeAndFormatMessage function (redundant)
- Migrate all localizeAndFormatMessage usages to localizeMessage
- Remove unused imports: getCurrentLocale, getTranslations
- Add getIntl import

Files migrated:
- notification_actions.tsx: notification.crt message
- app_command_parser_dependencies.ts: intlShim formatMessage

This consolidates i18n logic and provides a single, feature-complete
localization function for non-React contexts.

Example usage:
  // Before (old function)
  localizeAndFormatMessage(
    {id: 'welcome', defaultMessage: 'Hello {name}'},
    {name: 'John'}
  )

  // After (consolidated)
  localizeMessage(
    {id: 'welcome', defaultMessage: 'Hello {name}'},
    {name: 'John'}
  )

* refactor(i18n): remove intlShim abstraction, use IntlShape directly

Remove intlShim wrapper and use react-intl's IntlShape type throughout.

Key Changes:
- Remove intlShim constant from app_command_parser_dependencies.ts
- Update errorMessage() signature to accept IntlShape parameter
- Use IntlShape for intl properties in AppCommandParser and ParsedCommand
- Call getIntl() at function start in actions (command.ts, marketplace.ts)
- Pass IntlShape to AppCommandParser and doAppSubmit consistently

Files Modified:
- app_command_parser_dependencies.ts: Remove intlShim, update errorMessage
- app_command_parser.ts: Use IntlShape type
- command.ts, marketplace.ts: Call getIntl() early, pass to parser
- app_provider.tsx, command_provider.tsx: Use getIntl() in constructor
- apps.ts: Update doAppSubmit signature

Benefits: Better type safety, standard react-intl patterns, eliminates
unnecessary abstraction.

Context: Part of migration to @formatjs/cli tooling.

* refactor(webapp): remove deprecated t() function and refactor login validation

Remove the deprecated t() function that was used as a marker for the old
mmjstool extraction. With @formatjs/cli, this is no longer needed as
extraction happens directly from formatMessage calls.

Changes:
- Remove t() function definition from utils/i18n.tsx
- Remove t() import and marker calls from login.tsx
- Refactor login validation logic from multiple if statements to a clean
  switch(true) pattern that evaluates boolean combinations directly
- Add helpful comments documenting the structure of login method combinations
- Add default case as safety fallback

The switch(true) pattern eliminates the need to build a key string and makes
the login method combination logic clearer and more maintainable.

Prompt: Remove deprecated t() translation marker function used by old mmjstool.
Refactor login validation from if blocks to switch(true) with boolean cases.

* refactor(i18n): share IntlProvider's intl instance with getIntl()

Make getIntl() return the same IntlShape instance that IntlProvider creates,
ensuring consistency between React components (using useIntl()) and non-React
contexts (using getIntl()).

Changes:
- Add intlInstance storage in utils/i18n.tsx
- Export setIntl() function to store the intl instance
- Modify getIntl() to return stored instance if available, with fallback to
  createIntl() for tests and early initialization
- Add IntlCapture component in IntlProvider that uses useIntl() hook to
  capture and store the intl instance via setIntl()

Benefits:
- Single source of truth: Both React and non-React code use the same IntlShape
- Consistent translations: No duplicate translation loading or potential desync
- Leverages IntlProvider's translation loading lifecycle automatically
- Maintains fallback for edge cases where IntlProvider hasn't mounted yet

Prompt: Refactor getIntl() to reuse IntlProvider's intl instance instead of
creating a separate instance, ensuring both React and non-React contexts use
the same translations and intl configuration.

* 1

* exclude test files

* reset en.json

* refactor(i18n): inline message ID constants for formatjs extraction

Replace dynamic ID references with literal strings to enable proper formatjs
extraction. Formatjs can only extract from static message IDs, not from
variables or computed IDs.

Changes:
- configuration_bar.tsx: Inline AnnouncementBarMessages constants to literal
  string IDs ('announcement_bar.error.past_grace', 'announcement_bar.error.preview_mode')
- system_analytics.test.tsx: Replace variable IDs (totalPlaybooksID,
  totalPlaybookRunsID) with literal strings ('total_playbooks', 'total_playbook_runs')
- password.tsx: Use full message descriptor from passwordErrors object instead
  of dynamic ID with hardcoded defaultMessage. The errorId is still built
  dynamically using template strings, but the actual message is looked up from
  the passwordErrors defineMessages block where formatjs can extract it.

This eliminates all "[id]" placeholder entries from extraction output and
ensures all messages have proper extractable definitions.

Prompt: Fix dynamic message ID references that formatjs extraction can't parse.
Inline constants and use message descriptor lookups to enable clean extraction
without [id] placeholders.

* fix(i18n): add missing defaultMessage to drafts.tooltipText

The drafts.tooltipText FormattedMessage had an empty defaultMessage, causing
formatjs extraction to output an empty string for this message.

Added the proper defaultMessage with plural formatting for draft and scheduled
post counts.

Prompt: Fix empty defaultMessage in drafts tooltip causing extraction issues.

* feat(i18n): add --preserve-whitespace flag to formatjs extraction

Add the --preserve-whitespace flag to ensure formatjs extraction maintains
exact whitespace from defaultMessage values. This is important for messages
with intentional formatting like tabs, multiple spaces, or line breaks.

Prompt: Add --preserve-whitespace to i18n extraction to maintain exact formatting.

* feat(i18n): add custom formatjs formatter matching mmjstool ordering

Create custom formatter that replicates mmjstool's sorting behavior:
- Case-insensitive alphabetical sorting
- Underscore (_) sorts before dot (.) to match existing en.json ordering
- Simple key-value format (extracts defaultMessage)

The underscore-before-dot collation matches mmjstool's actual behavior and
reduces diff size by 50% compared to standard alphabetical sorting.

Changes:
- scripts/formatter.js: Custom compareMessages, format, and compile functions
- package.json: Use --format scripts/formatter.js instead of --format simple

Prompt: Create custom formatjs formatter matching mmjstool's exact sorting
behavior including underscore-before-dot collation to minimize migration diff.

* feat(i18n): enhance reconciliation script with git history metadata

Add git log lookups to show when each message was last modified in both
en.json and code files. This enables data-driven decisions about which
version to keep when resolving conflicts.

Enhancements:
- getEnJsonLastModified(): Find when an en.json entry last changed
- findCodeFile(): Locate source file for a message ID (handles id= and id:)
- getCodeLastModified(): Get last modification date for source files
- Caching for performance (minimizes git operations)

Output includes:
  📅 en.json:  [date] | [hash] | [commit message]
  📅 code:     [date] | [hash] | [commit message]
  📂 file:     [source file path]

Analysis shows en.json entries from 2023 while code actively updated
2024-2025, indicating code is more authoritative.

Prompt: Add git history to reconciliation report showing modification dates
for data-driven conflict resolution decisions.

* feat(i18n): add optional duplicate message ID check script

Add i18n-extract:check-duplicates script that fails if the same message ID
has different defaultMessages in multiple locations. This is kept as an
optional standalone script rather than auto-chained with i18n-extract to
allow the migration to proceed while duplicates are addressed separately.

Usage:
  npm run i18n-extract:check-duplicates

The script runs formatjs extract to /dev/null and checks stderr for
"Duplicate message id" warnings, failing if any are found.

Prompt: Add optional duplicate ID check as standalone npm script for gradual
migration without blocking on existing duplicate message IDs.

* about.enterpriseEditionLearn

* admin.accesscontrol.title

* eslint-plugin-formatjs version to eslint 8 compat

* admin.channel_settings.channel_detail.access_control_policy_title

* admin.connectionSecurityTls
- now: admin.connectionSecurityTls.title

* admin.customProfileAttribDesc
now:
- admin.customProfileAttribDesc.ldap
- admin.customProfileAttribDesc.saml

* admin.gitlab.clientSecretExample

* package-lock

* admin.google.EnableMarkdownDesc
- fix err `]` in admin.google.EnableMarkdownDesc.openid
- now:
  - admin.google.EnableMarkdownDesc.oauth
  - admin.google.EnableMarkdownDesc.openid

* admin.image.amazonS3BucketExample
- now:
  - admin.image.amazonS3BucketExample
  - admin.image.amazonS3BucketExampleExport

* admin.license.trialCard.contactSales

* admin.log.AdvancedLoggingJSONDescription
- now:
  - admin.log.AdvancedLoggingJSONDescription
  - admin.log.AdvancedAuditLoggingJSONDescription

* admin.rate.noteDescription
- now
  - admin.rate.noteDescription
  - admin.info_banner.restart_required.desc (3)

* admin.system_properties.user_properties.title
- now:
  - admin.system_properties.user_properties.title
  - admin.accesscontrol.user_properties.link.label

* admin.system_users.filters.team.allTeams, admin.system_users.filters.team.noTeams

* admin.user_item.email_title

* announcement_bar.error.preview_mode

* channel_settings.error_purpose_length

* deactivate_member_modal.desc
- now:
  - deactivate_member_modal.desc
  - deactivate_member_modal.desc_with_confirmation

* deleteChannelModal.canViewArchivedChannelsWarning

* edit_channel_header_modal.error

* filtered_channels_list.search
- now:
  - filtered_channels_list.search
  - filtered_channels_list.search.label

* flag_post.flag

* generic_icons.collapse

* installed_outgoing_oauth_connections.header

* intro_messages.group_message

* intro_messages.notificationPreferences
- now:
  - intro_messages.notificationPreferences
  - intro_messages.notificationPreferences.label

* katex.error

* multiselect.placeholder
- now:
  - multiselect.placeholder
  - multiselect.placeholder.addMembers

* post_info.pin, post_info.unpin, rhs_root.mobile.flag

take en.json

* texteditor.rewrite.rewriting

* user_groups_modal.addPeople

split to user_groups_modal.addPeople.field_title

* user.settings.general.validImage

take en.json

* userSettings.adminMode.modal_header

take en.json

* webapp.mattermost.feature.start_call

split to user_profile.call.start

* admin.general.localization.enableExperimentalLocalesDescription

take en.json

* login.contact_admin.title, login.contact_admin.detail

take en.json

* fix: correct spelling of "complementary" in accessibility sections

* Revert "about.enterpriseEditionLearn"

This reverts commit 23ababe1ce.

* about.enterpriseEditionLearn, about.planNameLearn

* fix: improve clarity and consistency in activity log and command descriptions

* fix: update emoji upload limits and improve help text for emoji creation

* fix easy round 1

* fix: improve messaging for free trial notifications and sales contact

take en.json

* admin.billing.subscription.planDetails.features.limitedFileStorage

take en.json

* admin.billing.subscription.updatePaymentInfo

take en.json

* fix easy round 2

* admin.complianceExport.exportFormat.globalrelay

take en.json

* fix round 3

* fix round 4

* fix round 5

* fix round 6

* fix closing tag, newlines, and popout title

* fix linting, +

* update snapshots

* test(webapp): update tests to match formatjs migration message changes

Update test assertions to reflect message text changes from the
mmjstool → @formatjs/cli migration. All changes align with the
corresponding i18n message updates in the codebase.

Changes:
- limits.test.tsx: Update capitalization (Message History → Message history,
  File Storage → File storage)
- emoji_picker.test.tsx: Update label (Recent → Recently Used)
- command.test.js: Add period to error message
- channel_activity_warning_modal.test.tsx: Update warning text assertion
- channel_settings_archive_tab.test.tsx: Update archive warning text
- feature_discovery.test.tsx: Update agreement name (Software and Services
  License Agreement → Software Evaluation Agreement)
- flag_post_modal.test.tsx: Update placeholder text (Select a reason for
  flagging → Select a reason)
- channel_intro_message.test.tsx: Remove trailing space from assertion
- elasticsearch_settings.tsx: Fix searchableStrings placeholder names
  (documentationLink → link) to match updated message format

All 983 test suites passing (12 tests fixed, 9088 tests total).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(i18n): improve cross-platform compatibility and cleanup temporary tooling

Simplify i18n check scripts to reuse npm run i18n-extract, improving
cross-platform compatibility and reducing duplication.

Changes:
- Refactor i18n-extract:check to reuse base extraction command
  - Uses project-local temp file instead of /tmp (Windows compatible)
  - Simplified from duplicated formatjs command to npm run reuse
  - Add --throws flag to fail fast on extraction errors
  - Add helpful error message: "To update: npm run i18n-extract"

- Add i18n-extract:check to main check script
  - Now runs alongside ESLint and Stylelint checks
  - Ensures en.json stays in sync with code changes

- Remove i18n-extract:check-duplicates script
  - Redundant - duplicates are caught by i18n-extract automatically
  - Avoided grep dependency (not cross-platform)

- Remove temporary reconciliation tooling
  - Deleted scripts/i18n-reconcile-conflicts.js (served its purpose)
  - Removed i18n-reconcile npm script

- Add .i18n-check.tmp.json to .gitignore

i18n-extract:check now works on Windows, macOS, and Linux.
Requires only diff command (available via Git on Windows).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* ci(webapp): simplify i18n check using npm script

Replace custom i18n check in CI with npm run i18n-extract:check for
consistency with local development workflow.

Changes:
- Update check-i18n job to use npm run i18n-extract:check
- Remove manual cp/extract/diff steps (3 lines → 1 line)
- Consistent behavior between CI and local development
- Cross-platform compatible (no /tmp dependency)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(i18n): add i18n extraction targets to Makefile and workspace scripts

Add Make targets and npm workspace scripts for i18n message extraction
to provide consistent tooling access across the project.

Changes:
- Add make i18n-extract target to webapp/Makefile
  - Extracts i18n messages from code to en.json

- Add make i18n-extract-check target to webapp/Makefile
  - Checks if en.json is in sync with code

- Add i18n-extract workspace script to webapp/package.json
  - Runs extraction across all workspaces

- Add i18n-extract:check workspace script to webapp/package.json
  - Runs sync check across all workspaces

Usage:
  make i18n-extract         # Extract messages
  make i18n-extract-check   # Check if en.json needs update
  npm run i18n-extract      # From webapp root
  npm run i18n-extract:check

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(i18n): add cross-platform cleanup utility for temp files

Add Node.js cleanup script to handle temporary file removal in a
cross-platform manner, replacing platform-specific rm commands.

Changes:
- Add webapp/scripts/cleanup.js
  - Cross-platform file deletion utility
  - Silently handles missing files
  - Works on Windows/Mac/Linux

- Update i18n-extract:check to use cleanup script
  - Removes .i18n-check.tmp.json after diff
  - Cleans up on both success and failure paths

Usage:
  node scripts/cleanup.js <file1> [file2] [...]

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(i18n): remove 'defineMessage' from additionalFunctionNames in ESLint config

* chore(i18n): update i18n-extract check script and remove unused cleanup utility

context: WSL is already required on windows

* chore(i18n): remove ESLint rule disabling for formatjs placeholders in admin definition

* chore(i18n): admin_definition
- re-add defineMessage to eslint additionalfunctionnames
- mark additional individual placeholder lines in admin_definition

* fix(i18n): correct typo in enterprise edition link message

* chore(i18n): fix escape sequences

* revert emoji.ts changes

* fix snapshots

* fix admin.google.EnableMarkdownDesc
- keep en.json but fix linkAPI and <strong>Create</strong>

* restore newlines, and final corrections check

* update snapshots

* fix: i18n

* chore(e2e): admin.complianceExport.exportJobStartTime.title

* chore(e2e): emoji_picker.travel-places

* chore(e2e): user.settings.general.incorrectPassword

* chore(e2e): signup_user_completed.create

* chore(e2e): fix Invite People

* chore(fix): admin console email heading

* chore(e2e): fix edit_channel_header_modal.placeholder

* chore(e2e): fix Create account

* chore(e2e): fix Create account

* chore(e2e): correct success message text in password update alert

* fix: admin guide label

* chore(e2e): update role name in invite people combobox

* chore(e2e): more "Create account"

* chore(e2e): fix authentication_spec

* lint

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
Caleb Roseland 2026-01-12 17:22:04 -06:00 committed by GitHub
parent c6a44406e0
commit db55f9fa43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
232 changed files with 1214 additions and 1541 deletions

View file

@ -41,18 +41,10 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: ci/setup
uses: ./.github/actions/webapp-setup
- name: ci/lint
- name: ci/i18n-extract
working-directory: webapp/channels
run: |
cp src/i18n/en.json /tmp/en.json
mkdir -p /tmp/fake-mobile-dir/assets/base/i18n/
echo '{}' > /tmp/fake-mobile-dir/assets/base/i18n/en.json
npm run mmjstool -- i18n extract-webapp --webapp-dir ./src --mobile-dir /tmp/fake-mobile-dir
diff /tmp/en.json src/i18n/en.json
# Address weblate behavior which does not remove whole translation item when translation string is set to empty
npm run mmjstool -- i18n clean-empty --webapp-dir ./src --mobile-dir /tmp/fake-mobile-dir --check
npm run mmjstool -- i18n check-empty-src --webapp-dir ./src --mobile-dir /tmp/fake-mobile-dir
rm -rf tmp
npm run i18n-extract:check
check-types:
needs: check-lint

View file

@ -93,7 +93,6 @@
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@ -1071,7 +1070,6 @@
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -1114,6 +1112,7 @@
"integrity": "sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
"eslint-visitor-keys": "^2.1.0",
@ -1150,7 +1149,6 @@
"integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.0",
"@babel/types": "^7.28.0",
@ -1168,7 +1166,6 @@
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/compat-data": "^7.27.2",
"@babel/helper-validator-option": "^7.27.1",
@ -1186,7 +1183,6 @@
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -1197,7 +1193,6 @@
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1"
@ -1212,7 +1207,6 @@
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
@ -1231,7 +1225,6 @@
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -1252,7 +1245,6 @@
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -1263,7 +1255,6 @@
"integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.2"
@ -1278,7 +1269,6 @@
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/types": "^7.28.0"
},
@ -1305,7 +1295,6 @@
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/parser": "^7.27.2",
@ -1321,7 +1310,6 @@
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
@ -1341,7 +1329,6 @@
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
@ -1813,7 +1800,6 @@
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
@ -1825,7 +1811,6 @@
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.0.0"
}
@ -1836,7 +1821,6 @@
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
@ -1847,8 +1831,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.30",
@ -1856,7 +1839,6 @@
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@ -1884,6 +1866,7 @@
"integrity": "sha512-2795KUkp2EkuJ9NVohPkJmrgKunt6OZiLyo8zUoIWPJjxQ0upjiWJz/KenABx38v8+QfpSEN8tZSBN3lsZCueg==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"typescript": "^4.3.0 || ^5.0.0"
},
@ -2866,7 +2849,6 @@
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@ -2878,7 +2860,6 @@
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
@ -3179,6 +3160,7 @@
"integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.39.1",
"@typescript-eslint/types": "8.39.1",
@ -3397,7 +3379,6 @@
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
@ -3408,24 +3389,21 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
@ -3433,7 +3411,6 @@
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
@ -3445,8 +3422,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
@ -3454,7 +3430,6 @@
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@ -3468,7 +3443,6 @@
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
@ -3479,7 +3453,6 @@
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
@ -3489,8 +3462,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
@ -3498,7 +3470,6 @@
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@ -3516,7 +3487,6 @@
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
@ -3531,7 +3501,6 @@
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@ -3545,7 +3514,6 @@
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
@ -3561,7 +3529,6 @@
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
@ -3572,16 +3539,14 @@
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true
"license": "BSD-3-Clause"
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true
"license": "Apache-2.0"
},
"node_modules/accepts": {
"version": "2.0.0",
@ -3603,6 +3568,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3616,7 +3582,6 @@
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.13.0"
},
@ -3671,7 +3636,6 @@
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
@ -3690,7 +3654,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@ -3707,8 +3670,7 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/ally.js": {
"version": "1.4.1",
@ -4340,8 +4302,7 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/byte-length": {
"version": "1.0.2",
@ -4462,8 +4423,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "CC-BY-4.0",
"peer": true
"license": "CC-BY-4.0"
},
"node_modules/caseless": {
"version": "0.12.0",
@ -4558,7 +4518,6 @@
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.0"
}
@ -4848,8 +4807,7 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.7.2",
@ -4935,6 +4893,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@cypress/request": "^3.0.9",
"@cypress/xvfb": "^1.2.4",
@ -5423,8 +5382,7 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz",
"integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==",
"dev": true,
"license": "ISC",
"peer": true
"license": "ISC"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
@ -5473,6 +5431,7 @@
"integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-colors": "^4.1.1",
"strip-ansi": "^6.0.1"
@ -5603,8 +5562,7 @@
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
@ -5702,6 +5660,7 @@
"integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@ -5903,6 +5862,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -6535,8 +6495,7 @@
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause",
"peer": true
"license": "BSD-3-Clause"
},
"node_modules/fast-xml-parser": {
"version": "5.2.5",
@ -6916,7 +6875,6 @@
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -7083,8 +7041,7 @@
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true
"license": "BSD-2-Clause"
},
"node_modules/glob/node_modules/minimatch": {
"version": "10.0.3",
@ -8046,7 +8003,6 @@
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@ -8062,7 +8018,6 @@
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@ -8079,6 +8034,7 @@
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@ -8116,7 +8072,6 @@
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jsesc": "bin/jsesc"
},
@ -8136,8 +8091,7 @@
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/json-schema": {
"version": "0.4.0",
@ -8173,7 +8127,6 @@
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"json5": "lib/cli.js"
},
@ -8449,7 +8402,6 @@
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.11.5"
}
@ -8652,7 +8604,6 @@
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"yallist": "^3.0.2"
}
@ -8874,6 +8825,7 @@
"integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"browser-stdout": "^1.3.1",
"chokidar": "^4.0.1",
@ -9417,8 +9369,7 @@
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/node-ensure": {
"version": "0.0.0",
@ -9432,8 +9383,7 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/notp": {
"version": "2.0.3",
@ -10619,7 +10569,6 @@
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -10852,7 +10801,6 @@
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
@ -10891,7 +10839,6 @@
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
@ -10904,8 +10851,7 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/semver": {
"version": "6.3.1",
@ -10972,6 +10918,7 @@
"integrity": "sha512-b0IrY3b1gVMsWvJppCf19g1p3JSnS0hQi6xu4Hi40CIhf0Lx8pQHcvBL+xunShpmOiQzg1NOia812NAWdSaShw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@servie/events": "^1.0.0",
"byte-length": "^1.0.2",
@ -11229,7 +11176,6 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -11240,7 +11186,6 @@
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@ -11659,7 +11604,6 @@
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.14.0",
@ -11679,7 +11623,6 @@
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
@ -11714,8 +11657,7 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/thirty-two": {
"version": "0.0.2",
@ -12038,6 +11980,7 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -12106,6 +12049,7 @@
"integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.41.0",
"@typescript-eslint/types": "8.41.0",
@ -12394,7 +12338,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
@ -12513,7 +12456,6 @@
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@ -12528,7 +12470,6 @@
"integrity": "sha512-rHY3vHXRbkSfhG6fH8zYQdth/BtDgXXuR2pHF++1f/EBkI8zkgM5XWfsC3BvOoW9pr1CvZ1qQCxhCEsbNgT50g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@ -12578,7 +12519,6 @@
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.13.0"
}
@ -12589,7 +12529,6 @@
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@ -12604,7 +12543,6 @@
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
@ -12615,7 +12553,6 @@
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"mime-db": "1.52.0"
},
@ -12629,7 +12566,6 @@
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
}
@ -12906,8 +12842,7 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
"license": "ISC",
"peer": true
"license": "ISC"
},
"node_modules/yargs": {
"version": "17.7.2",

View file

@ -50,7 +50,7 @@ describe('Verify Accessibility Support in Popovers', () => {
{ariaLabel: 'People & Body', header: 'People & Body'},
{ariaLabel: 'Animals & Nature', header: 'Animals & Nature'},
{ariaLabel: 'Food & Drink', header: 'Food & Drink'},
{ariaLabel: 'Travel Places', header: 'Travel Places'},
{ariaLabel: 'Travel & Places', header: 'Travel & Places'},
{ariaLabel: 'Activities', header: 'Activities'},
{ariaLabel: 'Objects', header: 'Objects'},
{ariaLabel: 'Symbols', header: 'Symbols'},

View file

@ -85,7 +85,7 @@ describe('Profile > Profile Settings > Email', () => {
cy.get('#primaryEmail').should('be.visible').click().blur();
// * Check that the correct error message is shown.
cy.get('#error_primaryEmail').should('be.visible').should('have.text', 'Please enter a valid email address');
cy.get('#error_primaryEmail').should('be.visible').should('have.text', 'Please enter a valid email address.');
});
it('MM-T2067 email address already taken error', () => {

View file

@ -61,7 +61,7 @@ describe('Authentication', () => {
cy.get('#input_name').clear().type(`test${getRandomId()}`);
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// * Make sure account was created successfully and we are at the select team page
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
@ -113,7 +113,7 @@ describe('Authentication', () => {
cy.get('#input_name').clear().type(`test${getRandomId()}`);
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// * Make sure account was not created successfully
cy.get('.AlertBanner__title').scrollIntoView().should('be.visible');
@ -146,7 +146,7 @@ describe('Authentication', () => {
cy.get('#input_name').clear().type(username);
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// * Make sure account was created successfully and we are on the team joining page
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');

View file

@ -61,14 +61,14 @@ describe('Authentication', () => {
cy.get('#input_password-input').clear().type('less');
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// * Assert the error is what is expected;
cy.findByText('Your password must be 7-72 characters long.').should('be.visible');
cy.get('#input_password-input').clear().type('greaterthan7');
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// * Assert that we are not shown an MFA screen and instead a Teams You Can join page
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
@ -114,7 +114,7 @@ describe('Authentication', () => {
['NOLOWERCASE123!', 'noupppercase123!', 'NoNumber!', 'NoSymbol123'].forEach((option) => {
cy.get('#input_password-input').clear().type(option);
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// * Assert the error is what is expected;
cy.findByText('Your password must be 5-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible');

View file

@ -49,7 +49,7 @@ describe('Verify Accessibility Support in different input fields', () => {
// * Verify Accessibility Support in Add or Invite People input field
cy.get('.users-emails-input__control').should('be.visible').within(() => {
cy.get('input').should('have.attr', 'aria-label', 'Add or Invite People').and('have.attr', 'aria-autocomplete', 'list');
cy.get('input').should('have.attr', 'aria-label', 'Invite People').and('have.attr', 'aria-autocomplete', 'list');
cy.get('.users-emails-input__placeholder').should('have.text', 'Enter a name or email address');
});
@ -58,7 +58,7 @@ describe('Verify Accessibility Support in different input fields', () => {
// * Verify Accessibility Support in Invite People input field
cy.get('.users-emails-input__control').should('be.visible').within(() => {
cy.get('input').should('have.attr', 'aria-label', 'Add or Invite People').and('have.attr', 'aria-autocomplete', 'list');
cy.get('input').should('have.attr', 'aria-label', 'Invite People').and('have.attr', 'aria-autocomplete', 'list');
cy.get('.users-emails-input__placeholder').should('have.text', 'Enter a name or email address');
});

View file

@ -58,7 +58,7 @@ describe('Authentication', () => {
cy.get('#input_name').clear().type(`Test${getRandomId()}`);
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// * Make sure account was not created successfully
cy.get('.AlertBanner__title').scrollIntoView().should('be.visible');

View file

@ -61,7 +61,7 @@ describe('Multi-user group header', () => {
cy.get('#editChannelHeaderModalLabel').should('be.visible').wait(TIMEOUTS.ONE_SEC);
// # Add the header in the modal
cy.findByPlaceholderText('Edit the Channel Header...').should('be.visible').type(`${header}{enter}`);
cy.findByPlaceholderText('Enter the Channel Header').should('be.visible').type(`${header}{enter}`);
// # Wait for modal to disappear
cy.waitUntil(() => cy.get('#editChannelHeaderModalLabel').should('not.be.visible'));

View file

@ -32,7 +32,7 @@ function signupWithEmail(name, pw) {
cy.get('#input_password-input').type(pw);
// # Click on Create Account button
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
}
describe('Cloud Onboarding', () => {

View file

@ -71,7 +71,7 @@ describe('Onboarding', () => {
cy.get('#input_email').should('be.focused').and('be.visible').type(email);
cy.get('#input_name').should('be.visible').type(username);
cy.get('#input_password-input').should('be.visible').type(password);
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
cy.findByText('Youre almost done!').should('be.visible');

View file

@ -71,8 +71,8 @@ describe('Onboarding', () => {
cy.get('#input_name').should('be.visible').type(username);
cy.get('#input_password-input').should('be.visible').type(password);
// # Attempt to create an account by clicking on the 'Create Account' button
cy.findByText('Create Account').click();
// # Attempt to create an account by clicking on the 'Create account' button
cy.findByText('Create account').click();
cy.wait(TIMEOUTS.HALF_SEC);

View file

@ -91,7 +91,7 @@ describe('Signin/Authentication', () => {
cy.url().should('contain', '/login?extra=password_change');
// * Should show that the password is updated successfully
cy.get('.AlertBanner.success').should('be.visible').and('have.text', ' Password updated successfully');
cy.get('.AlertBanner.success').should('be.visible').and('have.text', 'Password updated successfully');
// # Type email and new password, then click login button
cy.get('#input_loginId').should('be.visible').type(testUser.username);

View file

@ -85,7 +85,7 @@ describe('Team Settings', () => {
cy.get('.InviteAs').findByTestId('inviteMembersLink').click();
}
cy.findByRole('combobox', {name: 'Add or Invite People'}).type(email, {force: true}).wait(TIMEOUTS.HALF_SEC).type('{enter}', {force: true});
cy.findByRole('combobox', {name: 'Invite People'}).type(email, {force: true}).wait(TIMEOUTS.HALF_SEC).type('{enter}', {force: true});
cy.findByTestId('inviteButton').click();
// # Wait for a while to ensure that email notification is sent and logout from sysadmin account
@ -115,7 +115,7 @@ describe('Team Settings', () => {
cy.get('#input_password-input').type(password);
// # Attempt to create an account by clicking on the 'Create Account' button
cy.findByText('Create Account').click();
cy.findByText('Create account').click();
// # Close the onboarding tutorial
cy.uiCloseOnboardingTaskList();

View file

@ -87,8 +87,8 @@ describe('Team Settings', () => {
cy.get('#input_name').type(username);
cy.get('#input_password-input').type(password);
// # Attempt to create an account by clicking on the 'Create Account' button
cy.findByText('Create Account').click();
// # Attempt to create an account by clicking on the 'Create account' button
cy.findByText('Create account').click();
// * Assert that the expected error message from creating an account with an email not from the allowed email domain exists and is visible
cy.findByText(errorMessage).should('be.visible');

View file

@ -6,7 +6,7 @@ import * as TIMEOUTS from '../../fixtures/timeouts';
Cypress.Commands.add('uiEnableComplianceExport', (exportFormat = 'csv') => {
// * Verify that the page is loaded
cy.findByText('Enable Compliance Export:').should('be.visible');
cy.findByText('Compliance Export time:').should('be.visible');
cy.findByText('Compliance Export Time:').should('be.visible');
cy.findByText('Export Format:').should('be.visible');
// # Enable compliance export

View file

@ -241,7 +241,7 @@ export const adminConsoleNavigation = [
},
{
type: ['team', 'e20', 'cloud_enterprise'],
header: 'Email Authentication',
header: 'Email',
sidebar: 'Email',
url: 'admin_console/authentication/email',
},

View file

@ -63,6 +63,18 @@ check-types: node_modules ## Checks TS file for TypeScript confirmity
npm run check-types
.PHONY: i18n-extract
i18n-extract: node_modules ## Extracts i18n messages from code to en.json
@echo Extracting i18n messages
npm run i18n-extract
.PHONY: i18n-extract-check
i18n-extract-check: node_modules ## Checks if en.json is in sync with code
@echo Checking i18n message sync
npm run i18n-extract:check
.PHONY: dist
dist: node_modules ## Builds all web app packages
@echo Packaging Mattermost Web App

View file

@ -8,7 +8,10 @@
"no-only-tests"
],
"settings": {
"import/resolver": "webpack"
"import/resolver": "webpack",
"formatjs": {
"additionalFunctionNames": ["localizeMessage", "defineMessage"]
}
},
"rules": {
"react/prop-types": [
@ -32,7 +35,16 @@
"react/style-prop-object": [2, {
"allow": ["Timestamp"]
}],
"formatjs/enforce-default-message": 2,
"formatjs/enforce-id": 2,
"formatjs/enforce-placeholders": 2,
"formatjs/no-invalid-icu": 2,
"formatjs/no-multiple-plurals": 1,
"formatjs/no-multiple-whitespaces": 2,
"formatjs/no-literal-string-in-jsx": 1,
"formatjs/prefer-formatted-message": 1,
"formatjs/no-useless-message": 1,
"formatjs/prefer-pound-in-plural": 0,
"react/jsx-fragments": ["error", "syntax"]
},
"overrides": [

View file

@ -24,3 +24,6 @@ Session.vim
*~
.eslintcache
.stylelintcache
# i18n temporary check file
.i18n-check.tmp.json

View file

@ -99,7 +99,6 @@
"@deanwhillier/jest-matchmedia-mock": "1.2.0",
"@mattermost/calls-common": "0.27.0",
"@mattermost/eslint-plugin": "*",
"@mattermost/mmjstool": "github:mattermost/mattermost-utilities#7b63833d208d482ba4a1c12230bb3e68dd9c5e5e",
"@stylistic/stylelint-plugin": "3.1.2",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.8.0",
@ -188,10 +187,8 @@
"test-ci": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --ci --maxWorkers=100% --forceExit --detectOpenHandles --logHeapUsage --coverage",
"clean": "rm -rf dist node_modules .eslintcache .stylelintcache tsconfig.tsbuildinfo",
"stats": "cross-env NODE_ENV=production webpack --profile --json > webpack_stats.json",
"mmjstool": "mmjstool",
"i18n-extract": "npm run mmjstool -- i18n extract-webapp --webapp-dir ./src",
"i18n-clean-empty": "npm run mmjstool -- i18n clean-empty --webapp-dir ./src",
"i18n-check-empty-src": "npm run mmjstool -- i18n check-empty-src --webapp-dir ./src",
"i18n-extract": "formatjs extract 'src/**/*.{js,jsx,ts,tsx}' --additional-function-names localizeMessage --ignore 'src/**/*.d.ts' --ignore 'src/**/*.test.{js,jsx,ts,tsx}' --out-file src/i18n/en.json --format scripts/formatter.js --id-interpolation-pattern '[sha512:contenthash:base64:6]' --preserve-whitespace",
"i18n-extract:check": "npm run i18n-extract -- --throws --out-file .i18n-check.tmp.json && diff src/i18n/en.json .i18n-check.tmp.json && rm -f .i18n-check.tmp.json || (rm -f .i18n-check.tmp.json && echo '\nen.json is out of sync with code. \nTo update: npm run i18n-extract' && exit 1)",
"check-types": "tsc -b",
"make-emojis": "node --experimental-json-modules build/emoji/make_emojis.mjs"
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/**
* Custom formatjs formatter matching mmjstool's behavior
*
* Based on formatjs simple formatter with case-insensitive sorting
* to match mmjstool's sortJson({ignoreCase: true})
*/
/**
* Format function - extract defaultMessage from message objects
* Same as formatjs simple formatter
*/
module.exports.format = (msgs) => {
return Object.keys(msgs).reduce((all, k) => {
all[k] = msgs[k].defaultMessage;
return all;
}, {});
};
/**
* Compile function - pass through (identity)
* Same as formatjs simple formatter
*/
module.exports.compile = (msgs) => msgs;
/**
* Custom comparator for case-insensitive alphabetical sorting
* with underscore before dot (to match existing en.json ordering)
*/
module.exports.compareMessages = (el1, el2) => {
// Normalize keys: replace _ with a character that sorts before .
// Use \x00 (null char) which sorts before all printable characters
const key1 = el1.key.toLowerCase().replace(/_/g, '\x00');
const key2 = el2.key.toLowerCase().replace(/_/g, '\x00');
if (key1 < key2) {
return -1;
}
if (key1 > key2) {
return 1;
}
return 0;
};

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {IntlShape} from 'react-intl';
import type {AnyAction} from 'redux';
import type {AppCallResponse, AppForm, AppCallRequest, AppContext, AppBinding} from '@mattermost/types/apps';
@ -76,7 +77,7 @@ export function handleBindingClick<Res=unknown>(binding: AppBinding, context: Ap
};
}
export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: IntlShape): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
return async () => {
try {
const call: AppCallRequest = {
@ -98,7 +99,7 @@ export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): Thu
if (!res.form?.submit) {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.form.no_form',
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
defaultMessage: 'Response type is `form`, but no form was included in response.',
});
return {error: makeCallErrorResponse(errMsg)};
}
@ -153,7 +154,7 @@ export function doAppFetchForm<Res=unknown>(call: AppCallRequest, intl: any): Th
if (!res.form?.submit) {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.form.no_form',
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
defaultMessage: 'Response type is `form`, but no form was included in response.',
});
return {error: makeCallErrorResponse(errMsg)};
}

View file

@ -168,7 +168,7 @@ describe('executeCommand', () => {
expect(result).toEqual({
error: {
message: 'Keyboard shortcuts are not supported on your device',
message: 'Keyboard shortcuts are not supported on your device.',
},
});
});

View file

@ -25,14 +25,14 @@ import KeyboardShortcutsModal from 'components/keyboard_shortcuts/keyboard_short
import LeaveChannelModal from 'components/leave_channel_modal';
import MarketplaceModal from 'components/plugin_marketplace/marketplace_modal';
import {AppCommandParser} from 'components/suggestion/command_provider/app_command_parser/app_command_parser';
import {intlShim} from 'components/suggestion/command_provider/app_command_parser/app_command_parser_dependencies';
import UserSettingsModal from 'components/user_settings/modal';
import {getHistory} from 'utils/browser_history';
import {Constants, ModalIdentifiers} from 'utils/constants';
import {getIntl} from 'utils/i18n';
import {isUrlSafe, getSiteURL} from 'utils/url';
import * as UserAgent from 'utils/user_agent';
import {localizeMessage, getUserIdFromChannelName} from 'utils/utils';
import {getUserIdFromChannelName} from 'utils/utils';
import type {ActionFuncAsync} from 'types/store';
@ -47,6 +47,7 @@ export type ExecuteCommandReturnType = {
export function executeCommand(message: string, args: CommandArgs): ActionFuncAsync<ExecuteCommandReturnType> {
return async (dispatch, getState) => {
const intl = getIntl();
const state = getState();
let msg = message;
@ -64,7 +65,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
return {data: {frontendHandled: true}};
case '/shortcuts':
if (UserAgent.isMobile()) {
const error = {message: localizeMessage({id: 'create_post.shortcutsNotSupported', defaultMessage: 'Keyboard shortcuts are not supported on your device'})};
const error = {message: intl.formatMessage({id: 'create_post.shortcutsNotSupported', defaultMessage: 'Keyboard shortcuts are not supported on your device.'})};
return {error};
}
@ -118,12 +119,12 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
case '/marketplace':
// check if user has permissions to access the read plugins
if (!haveICurrentTeamPermission(state, Permissions.SYSCONSOLE_WRITE_PLUGINS)) {
return {error: {message: localizeMessage({id: 'marketplace_command.no_permission', defaultMessage: 'You do not have the appropriate permissions to access the marketplace.'})}};
return {error: {message: intl.formatMessage({id: 'marketplace_command.no_permission', defaultMessage: 'You do not have the appropriate permissions to access the marketplace.'})}};
}
// check config to see if marketplace is enabled
if (!isMarketplaceEnabled(state)) {
return {error: {message: localizeMessage({id: 'marketplace_command.disabled', defaultMessage: 'The marketplace is disabled. Please contact your System Administrator for details.'})}};
return {error: {message: intl.formatMessage({id: 'marketplace_command.disabled', defaultMessage: 'The marketplace is disabled. Please contact your System Administrator for details.'})}};
}
dispatch(openModal({modalId: ModalIdentifiers.PLUGIN_MARKETPLACE, dialogType: MarketplaceModal}));
@ -139,7 +140,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
const createErrorMessage = (errMessage: string) => {
return {error: {message: errMessage}};
};
const parser = new AppCommandParser({dispatch, getState: getGlobalState} as any, intlShim, args.channel_id, args.team_id, args.root_id);
const parser = new AppCommandParser({dispatch, getState: getGlobalState} as any, intl, args.channel_id, args.team_id, args.root_id);
if (parser.isAppCommand(msg)) {
try {
const {creq, errorMessage} = await parser.composeCommandSubmitCall(msg);
@ -147,11 +148,11 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
return createErrorMessage(errorMessage!);
}
const res = await dispatch(doAppSubmit(creq, intlShim));
const res = await dispatch(doAppSubmit(creq, intl));
if (res.error) {
const errorResponse = res.error;
return createErrorMessage(errorResponse.text || intlShim.formatMessage({
return createErrorMessage(errorResponse.text || intl.formatMessage({
id: 'apps.error.unknown',
defaultMessage: 'Unknown error occurred.',
}));
@ -172,7 +173,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
case AppCallResponseTypes.NAVIGATE:
return {data: {appResponse: callResp}};
default:
return createErrorMessage(intlShim.formatMessage(
return createErrorMessage(intl.formatMessage(
{
id: 'apps.error.responses.unknown_type',
defaultMessage: 'App response type not supported. Response type: {type}.',
@ -183,7 +184,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
));
}
} catch (err: any) {
const message = err.message || intlShim.formatMessage({
const message = err.message || intl.formatMessage({
id: 'apps.error.unknown',
defaultMessage: 'Unknown error occurred.',
});

View file

@ -121,6 +121,7 @@ export function sendMembersInvites(teamId: string, users: UserProfile[], emails:
email,
reason: defineMessage({
id: 'admin.environment.smtp.smtpFailure',
// eslint-disable-next-line formatjs/enforce-placeholders -- a placeholder provided via messageWithLink when path is set
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
}),
path: ConsolePages.SMTP,
@ -229,6 +230,7 @@ export async function sendGuestInviteForUser(
user,
reason: defineMessage({
id: 'invite.guests.new-member',
// eslint-disable-next-line formatjs/enforce-placeholders -- count provided via values property, consumed by FormattedMessage in result_table
defaultMessage: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
values: {
count: channels.length,
@ -301,6 +303,7 @@ export function sendGuestsInvites(
email: res.email,
reason: defineMessage({
id: 'admin.environment.smtp.smtpFailure',
// eslint-disable-next-line formatjs/enforce-placeholders -- a placeholder provided via messageWithLink when path is set
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
}),
path: ConsolePages.SMTP,
@ -445,6 +448,7 @@ export function sendMembersInvitesToChannels(
email,
reason: defineMessage({
id: 'admin.environment.smtp.smtpFailure',
// eslint-disable-next-line formatjs/enforce-placeholders -- a placeholder provided via messageWithLink when path is set
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
}),
path: ConsolePages.SMTP,

View file

@ -12,11 +12,11 @@ import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getFilter, getPlugin} from 'selectors/views/marketplace';
import {intlShim} from 'components/suggestion/command_provider/app_command_parser/app_command_parser_dependencies';
import type {DoAppCallResult} from 'components/suggestion/command_provider/app_command_parser/app_command_parser_dependencies';
import {createCallContext, createCallRequest} from 'utils/apps';
import {ActionTypes} from 'utils/constants';
import {getIntl} from 'utils/i18n';
import type {ActionFuncAsync, ThunkActionFunc} from 'types/store';
@ -125,6 +125,7 @@ export function installPlugin(id: string): ThunkActionFunc<void> {
// On success, it also requests the current state of the apps to reflect the newly installed app.
export function installApp(id: string): ThunkActionFunc<Promise<boolean>> {
return async (dispatch, getState) => {
const intl = getIntl();
dispatch({
type: ActionTypes.INSTALLING_MARKETPLACE_ITEM,
id,
@ -155,7 +156,7 @@ export function installApp(id: string): ThunkActionFunc<Promise<boolean>> {
const creq = createCallRequest(call, context, expand, values);
const res = await dispatch(doAppSubmit(creq, intlShim)) as DoAppCallResult;
const res = await dispatch(doAppSubmit(creq, intl)) as DoAppCallResult;
if (res.error) {
const errorResponse = res.error;

View file

@ -190,7 +190,7 @@ const getNotificationTitle = (channel: Pick<Channel, 'type' | 'display_name'>, m
}
if (isCrtReply) {
title = Utils.localizeAndFormatMessage({id: 'notification.crt', defaultMessage: 'Reply in {title}'}, {title});
title = Utils.localizeMessage({id: 'notification.crt', defaultMessage: 'Reply in {title}'}, {title});
}
return title;

View file

@ -136,16 +136,20 @@ export default function AboutBuildModal(props: Props) {
learnMore = (
<div>
<FormattedMessage
id='about.enterpriseEditionLearn'
defaultMessage='Learn more about Mattermost {planName} at '
values={{planName: skuName}}
id='about.planNameLearn'
defaultMessage='Learn more about Mattermost {planName} at {link}'
values={{
planName: skuName,
link: (
<ExternalLink
location='about_build_modal'
href='https://mattermost.com/'
>
{'mattermost.com'}
</ExternalLink>
),
}}
/>
<ExternalLink
location='about_build_modal'
href='https://mattermost.com/'
>
{'mattermost.com'}
</ExternalLink>
</div>
);
licensee = (
@ -162,14 +166,18 @@ export default function AboutBuildModal(props: Props) {
<div>
<FormattedMessage
id='about.enterpriseEditionLearn'
defaultMessage='Learn more about Enterprise Edition at '
defaultMessage='Learn more about Enterprise Edition at {link}'
values={{
link: (
<ExternalLink
location='about_build_modal'
href='https://mattermost.com/'
>
{'mattermost.com'}
</ExternalLink>
),
}}
/>
<ExternalLink
location='about_build_modal'
href='https://mattermost.com/'
>
{'mattermost.com'}
</ExternalLink>
</div>
);
}

View file

@ -36,10 +36,10 @@ const AccessProblem = ({
<div className='AccessProblem__body'>
<AccessProblemSVG/>
<div className='AccessProblem__title'>
{formatMessage({id: 'login.contact_admin.title'})}
{formatMessage({id: 'login.contact_admin.title', defaultMessage: 'Contact your workspace admin'})}
</div>
<div className='AccessProblem__description'>
{formatMessage({id: 'login.contact_admin.detail'})}
{formatMessage({id: 'login.contact_admin.detail', defaultMessage: "To access your team's workspace, contact your workspace admin. If you've been invited already, check your email inbox for a Mattermost workspace invite."})}
</div>
</div>
);

View file

@ -24,7 +24,7 @@ const title = (
const screen = (
<FormattedMessage
id='post_info.actions.tutorialTip'
defaultMessage='Message actions that are provided\nthrough apps, integrations or plugins\nhave moved to this menu item.'
defaultMessage={'Message actions that are provided\nthrough apps, integrations or plugins\nhave moved to this menu item.'}
/>
);
const nextBtn = (

View file

@ -118,6 +118,7 @@ const AddUsersToGroupModal = (props: Props) => {
>
<FormattedMessage
id='user_groups_modal.addPeopleTitle'
// eslint-disable-next-line formatjs/enforce-placeholders -- group provided via titleValue memoized value
defaultMessage='Add people to {group}'
values={titleValue}
/>

View file

@ -221,7 +221,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}
@ -463,7 +463,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}

View file

@ -262,7 +262,7 @@ export class AddUsersToTeamModal extends React.PureComponent<Props, State> {
buttonSubmitLoadingText={buttonSubmitLoadingText}
saving={this.state.saving}
loading={this.state.loading}
placeholderText={defineMessage({id: 'multiselect.placeholder', defaultMessage: 'Search and add members'})}
placeholderText={defineMessage({id: 'multiselect.placeholder', defaultMessage: 'Search for people'})}
/>
</Modal.Body>
</Modal>

View file

@ -11,7 +11,7 @@ exports[`components/DatabaseSettings should match snapshot 1`] = `
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Database Settings"
defaultMessage="Database"
id="admin.database.title"
/>
</AdminHeader>

View file

@ -335,6 +335,7 @@ exports[`components/ElasticSearchSettings should match snapshot, disabled 1`] =
helpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="This purges the channels index and re-indexes all channels in the database, from oldest to newest. Channel autocomplete is available during indexing but search results may be incomplete until the indexing job is complete.
<b>Note- Please ensure no other indexing job is in progress in the table above.</b>"
id="admin.elasticsearch.rebuildChannelsIndex.helpText"
values={
@ -365,7 +366,7 @@ exports[`components/ElasticSearchSettings should match snapshot, disabled 1`] =
<RequestButton
buttonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Purge Index"
defaultMessage="Purge Indexes"
id="admin.elasticsearch.purgeIndexesButton"
/>
}
@ -827,6 +828,7 @@ exports[`components/ElasticSearchSettings should match snapshot, enabled 1`] = `
helpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="This purges the channels index and re-indexes all channels in the database, from oldest to newest. Channel autocomplete is available during indexing but search results may be incomplete until the indexing job is complete.
<b>Note- Please ensure no other indexing job is in progress in the table above.</b>"
id="admin.elasticsearch.rebuildChannelsIndex.helpText"
values={
@ -857,7 +859,7 @@ exports[`components/ElasticSearchSettings should match snapshot, enabled 1`] = `
<RequestButton
buttonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Purge Index"
defaultMessage="Purge Indexes"
id="admin.elasticsearch.purgeIndexesButton"
/>
}

View file

@ -108,7 +108,7 @@ exports[`components/MessageExportSettings should match snapshot, disabled, actia
"value": "csv",
},
Object {
"text": "GlobalRelay EML",
"text": "Global Relay EML",
"value": "globalrelay",
},
]
@ -270,7 +270,7 @@ exports[`components/MessageExportSettings should match snapshot, disabled, globa
"value": "csv",
},
Object {
"text": "GlobalRelay EML",
"text": "Global Relay EML",
"value": "globalrelay",
},
]
@ -546,7 +546,7 @@ exports[`components/MessageExportSettings should match snapshot, enabled, actian
"value": "csv",
},
Object {
"text": "GlobalRelay EML",
"text": "Global Relay EML",
"value": "globalrelay",
},
]
@ -708,7 +708,7 @@ exports[`components/MessageExportSettings should match snapshot, enabled, global
"value": "csv",
},
Object {
"text": "GlobalRelay EML",
"text": "Global Relay EML",
"value": "globalrelay",
},
]

View file

@ -148,14 +148,14 @@ exports[`components/PushSettings should match snapshot, licensed 1`] = `
<AdminTextSetting
helpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance."
defaultMessage="The maximum number of users that can be mentioned using @here, @all, or @channel. Above this limit, mentions and typing indicators are disabled, and users will see a system message if they try to use them."
id="admin.team.maxNotificationsPerChannelDescription"
/>
}
id="maxNotificationsPerChannel"
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Max Notifications Per Channel:"
defaultMessage="Large Channel Notification Threshold:"
id="admin.team.maxNotificationsPerChannelTitle"
/>
}
@ -279,14 +279,14 @@ exports[`components/PushSettings should match snapshot, unlicensed 1`] = `
<AdminTextSetting
helpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance."
defaultMessage="The maximum number of users that can be mentioned using @here, @all, or @channel. Above this limit, mentions and typing indicators are disabled, and users will see a system message if they try to use them."
id="admin.team.maxNotificationsPerChannelDescription"
/>
}
id="maxNotificationsPerChannel"
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Max Notifications Per Channel:"
defaultMessage="Large Channel Notification Threshold:"
id="admin.team.maxNotificationsPerChannelTitle"
/>
}

View file

@ -173,7 +173,7 @@ const SearchableSyncJobChannelList = (props: Props) => {
<FormattedMessage
id='more_channels.noMore'
tagName='strong'
defaultMessage='No results for {text}'
defaultMessage='No results for "{text}"'
values={{text: channelSearchValue}}
/>
);
@ -254,7 +254,7 @@ const SearchableSyncJobChannelList = (props: Props) => {
clearable={true}
onClear={handleClear}
value={channelSearchValue}
aria-label={props.intl.formatMessage({id: 'filtered_channels_list.search', defaultMessage: 'Search Channels'})}
aria-label={props.intl.formatMessage({id: 'filtered_channels_list.search', defaultMessage: 'Search channels'})}
/>
</div>
);
@ -306,7 +306,7 @@ const messages = defineMessages({
},
noMore: {
id: 'more_channels.noMore',
defaultMessage: 'No results for {text}',
defaultMessage: 'No results for "{text}"',
},
});

View file

@ -680,12 +680,12 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'AccessControlSettings.EnableAttributeBasedAccessControl',
label: defineMessage({id: 'admin.accesscontrol.enableTitle', defaultMessage: 'Allow attribute based access controls on this server'}),
help_text: defineMessage({id: 'admin.accesscontrol.enableDesc', defaultMessage: 'Allow access restrictions based on user attributes using custom access policies. To effectively use this feature, you must define user attributes in the {userAttributes} section.'}),
help_text: defineMessage({id: 'admin.accesscontrol.enableDesc', defaultMessage: 'Allow access restrictions based on user attributes using custom access policies. To effectively use this feature, you must define user attributes in the {userAttributes} section.'}), // eslint-disable-line formatjs/enforce-placeholders -- userAttributes provided via help_text_values
help_text_values: {
userAttributes: (
<a href='../system_attributes/user_attributes'>
<FormattedMessage
id='admin.system_properties.user_properties.title'
id='admin.accesscontrol.user_properties.link.label'
defaultMessage='User Attributes'
/>
</a>
@ -736,7 +736,7 @@ const AdminDefinition: AdminDefinitionType = {
),
schema: {
id: 'AttributeBasedAccessControl',
name: defineMessage({id: 'admin.accesscontrol.title', defaultMessage: 'Attribute-Based Access (Beta)'}),
name: defineMessage({id: 'admin.accesscontrol.title', defaultMessage: 'Attribute-Based Access'}),
settings: [
{
type: 'custom',
@ -773,14 +773,14 @@ const AdminDefinition: AdminDefinitionType = {
settings: [
{
type: 'banner',
label: defineMessage({id: 'admin.rate.noteDescription', defaultMessage: 'Changing properties in this section will require a server restart before taking effect.'}),
label: defineMessage({id: 'admin.info_banner.restart_required.desc', defaultMessage: 'Changing properties in this section will require a server restart before taking effect.'}),
banner_type: 'info',
},
{
type: 'text',
key: 'ServiceSettings.SiteURL',
label: defineMessage({id: 'admin.service.siteURL', defaultMessage: 'Site URL:'}),
help_text: defineMessage({id: 'admin.service.siteURLDescription', defaultMessage: 'The URL that users will use to access Mattermost. Standard ports, such as 80 and 443, can be omitted, but non-standard ports are required. For example: http://example.com:8065. This setting is required. Mattermost may be hosted at a subpath. For example: http://example.com:8065/company/mattermost. A restart is required before the server will work correctly.'}),
help_text: defineMessage({id: 'admin.service.siteURLDescription', defaultMessage: 'The URL that users will use to access Mattermost. Standard ports, such as 80 and 443, can be omitted, but non-standard ports are required. For example: http://example.com:8065. This setting is required.\n \nMattermost may be hosted at a subpath. For example: http://example.com:8065/company/mattermost. A restart is required before the server will work correctly.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
help_text_markdown: true,
placeholder: defineMessage({id: 'admin.service.siteURLExample', defaultMessage: 'E.g.: "http://example.com:8065"'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.WEB_SERVER)),
@ -791,7 +791,7 @@ const AdminDefinition: AdminDefinitionType = {
action: testSiteURL,
label: defineMessage({id: 'admin.service.testSiteURL', defaultMessage: 'Test Live URL'}),
loading: defineMessage({id: 'admin.service.testSiteURLTesting', defaultMessage: 'Testing...'}),
error_message: defineMessage({id: 'admin.service.testSiteURLFail', defaultMessage: 'Test unsuccessful: {error}'}),
error_message: defineMessage({id: 'admin.service.testSiteURLFail', defaultMessage: 'Test unsuccessful: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime
success_message: defineMessage({id: 'admin.service.testSiteURLSuccess', defaultMessage: 'Test successful. This is a valid URL.'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.WEB_SERVER)),
},
@ -808,7 +808,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'ServiceSettings.Forward80To443',
label: defineMessage({id: 'admin.service.forward80To443', defaultMessage: 'Forward port 80 to 443:'}),
help_text: defineMessage({id: 'admin.service.forward80To443Description', defaultMessage: 'Forwards all insecure traffic from port 80 to secure port 443. Not recommended when using a proxy server.'}),
disabled_help_text: defineMessage({id: 'admin.service.forward80To443Description.disabled', defaultMessage: 'Forwards all insecure traffic from port 80 to secure port 443. Not recommended when using a proxy server. This setting cannot be enabled until your server is [listening](#ServiceSettings.ListenAddress) on port 443.'}),
disabled_help_text: defineMessage({id: 'admin.service.forward80To443Description.disabled', defaultMessage: 'Forwards all insecure traffic from port 80 to secure port 443. Not recommended when using a proxy server.\n \nThis setting cannot be enabled until your server is [listening](#ServiceSettings.ListenAddress) on port 443.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
disabled_help_text_markdown: true,
isDisabled: it.any(
it.cloudLicensed,
@ -858,7 +858,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'ServiceSettings.UseLetsEncrypt',
label: defineMessage({id: 'admin.service.useLetsEncrypt', defaultMessage: 'Use Let\'s Encrypt:'}),
help_text: defineMessage({id: 'admin.service.useLetsEncryptDescription', defaultMessage: 'Enable the automatic retrieval of certificates from Let\'s Encrypt. The certificate will be retrieved when a client attempts to connect from a new domain. This will work with multiple domains.'}),
disabled_help_text: defineMessage({id: 'admin.service.useLetsEncryptDescription.disabled', defaultMessage: "Enable the automatic retrieval of certificates from Let's Encrypt. The certificate will be retrieved when a client attempts to connect from a new domain. This will work with multiple domains. This setting cannot be enabled unless the [Forward port 80 to 443](#ServiceSettings.Forward80To443) setting is set to true."}),
disabled_help_text: defineMessage({id: 'admin.service.useLetsEncryptDescription.disabled', defaultMessage: "Enable the automatic retrieval of certificates from Let's Encrypt. The certificate will be retrieved when a client attempts to connect from a new domain. This will work with multiple domains.\n \nThis setting cannot be enabled unless the [Forward port 80 to 443](#ServiceSettings.Forward80To443) setting is set to true."}), // eslint-disable-line formatjs/no-multiple-whitespaces
disabled_help_text_markdown: true,
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.WEB_SERVER)),
@ -928,7 +928,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'ServiceSettings.ManagedResourcePaths',
label: defineMessage({id: 'admin.service.managedResourcePaths', defaultMessage: 'Managed Resource Paths:'}),
help_text: defineMessage({id: 'admin.service.managedResourcePathsDescription', defaultMessage: 'A comma-separated list of paths on the Mattermost server that are managed by another service. See <link>here</link> for more information.'}),
help_text: defineMessage({id: 'admin.service.managedResourcePathsDescription', defaultMessage: 'A comma-separated list of paths on the Mattermost server that are managed by another service. See <link>here</link> for more information.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -947,7 +947,7 @@ const AdminDefinition: AdminDefinitionType = {
action: reloadConfig,
key: 'ReloadConfigButton',
label: defineMessage({id: 'admin.reload.button', defaultMessage: 'Reload Configuration From Disk'}),
help_text: defineMessage({id: 'admin.reload.reloadDescription', defaultMessage: 'Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating "config.json" to the new desired configuration and using the {featureName} feature to load the new settings while the server is running. The administrator should then use the {recycleDatabaseConnections} feature to recycle the database connections based on the new settings.'}),
help_text: defineMessage({id: 'admin.reload.reloadDescription', defaultMessage: 'Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating "config.json" to the new desired configuration and using the {featureName} feature to load the new settings while the server is running. The administrator should then use the {recycleDatabaseConnections} feature to recycle the database connections based on the new settings.'}), // eslint-disable-line formatjs/enforce-placeholders -- featureName, recycleDatabaseConnections provided via help_text_values
help_text_values: {
featureName: (
<b>
@ -968,7 +968,7 @@ const AdminDefinition: AdminDefinitionType = {
</a>
),
},
error_message: defineMessage({id: 'admin.reload.reloadFail', defaultMessage: 'Reload unsuccessful: {error}'}),
error_message: defineMessage({id: 'admin.reload.reloadFail', defaultMessage: 'Reload unsuccessful: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.WEB_SERVER)),
},
{
@ -977,7 +977,7 @@ const AdminDefinition: AdminDefinitionType = {
action: invalidateAllCaches,
label: defineMessage({id: 'admin.purge.button', defaultMessage: 'Purge All Caches'}),
help_text: defineMessage({id: 'admin.purge.purgeDescription', defaultMessage: 'This will purge all the in-memory caches for things like sessions, accounts, channels, etc. Deployments using High Availability will attempt to purge all the servers in the cluster. Purging the caches may adversely impact performance.'}),
error_message: defineMessage({id: 'admin.purge.purgeFail', defaultMessage: 'Purging unsuccessful: {error}'}),
error_message: defineMessage({id: 'admin.purge.purgeFail', defaultMessage: 'Purging unsuccessful: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.WEB_SERVER)),
},
],
@ -1028,7 +1028,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'dropdown',
key: 'FileSettings.DriverName',
label: defineMessage({id: 'admin.image.storeTitle', defaultMessage: 'File Storage System:'}),
help_text: defineMessage({id: 'admin.image.storeDescription', defaultMessage: 'Storage system where files and image attachments are saved. Selecting "Amazon S3" enables fields to enter your Amazon credentials and bucket details. Selecting "Local File System" enables the field to specify a local file directory.'}),
help_text: defineMessage({id: 'admin.image.storeDescription', defaultMessage: 'Storage system where files and image attachments are saved.\n \nSelecting "Amazon S3" enables fields to enter your Amazon credentials and bucket details.\n \nSelecting "Local File System" enables the field to specify a local file directory.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
help_text_markdown: true,
options: [
{
@ -1067,7 +1067,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'FileSettings.ExtractContent',
label: defineMessage({id: 'admin.image.extractContentTitle', defaultMessage: 'Enable document search by content:'}),
help_text: defineMessage({id: 'admin.image.extractContentDescription', defaultMessage: 'When enabled, supported document types are searchable by their content. Search results for existing documents may be incomplete <link>until a data migration is executed</link>.'}),
help_text: defineMessage({id: 'admin.image.extractContentDescription', defaultMessage: 'When enabled, supported document types are searchable by their content. Search results for existing documents may be incomplete <link>until a data migration is executed</link>.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -1087,7 +1087,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'FileSettings.ArchiveRecursion',
label: defineMessage({id: 'admin.image.archiveRecursionTitle', defaultMessage: 'Enable searching content of documents within ZIP files:'}),
help_text: defineMessage({id: 'admin.image.archiveRecursionDescription', defaultMessage: 'When enabled, content of documents within ZIP files will be returned in search results. This may have an impact on server performance for large files. '}),
help_text: defineMessage({id: 'admin.image.archiveRecursionDescription', defaultMessage: 'When enabled, content of documents within ZIP files will be returned in search results. This may have an impact on server performance for large files.'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
it.configIsFalse('FileSettings', 'ExtractContent'),
@ -1109,7 +1109,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'FileSettings.AmazonS3PathPrefix',
label: defineMessage({id: 'admin.image.amazonS3PathPrefixTitle', defaultMessage: 'Amazon S3 Path Prefix:'}),
help_text: defineMessage({id: 'admin.image.amazonS3PathPrefixDescription', defaultMessage: 'Prefix you selected for your S3 bucket in AWS.'}),
placeholder: defineMessage({id: 'admin.image.amazonS3PathPrefixExample', defaultMessage: 'E.g.: "subdir1/" or you can leave it .'}),
placeholder: defineMessage({id: 'admin.image.amazonS3PathPrefixExample', defaultMessage: 'E.g.: "subdir1" or you can leave it empty.'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_S3)),
@ -1130,7 +1130,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'FileSettings.AmazonS3AccessKeyId',
label: defineMessage({id: 'admin.image.amazonS3IdTitle', defaultMessage: 'Amazon S3 Access Key ID:'}),
help_text: defineMessage({id: 'admin.image.amazonS3IdDescription', defaultMessage: '(Optional) Only required if you do not want to authenticate to S3 using an <link>IAM role</link>. Enter the Access Key ID provided by your Amazon EC2 administrator.'}),
help_text: defineMessage({id: 'admin.image.amazonS3IdDescription', defaultMessage: '(Optional) Only required if you do not want to authenticate to S3 using an <link>IAM role</link>. Enter the Access Key ID provided by your Amazon EC2 administrator.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -1184,7 +1184,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'FileSettings.AmazonS3SSE',
label: defineMessage({id: 'admin.image.amazonS3SSETitle', defaultMessage: 'Enable Server-Side Encryption for Amazon S3:'}),
help_text: defineMessage({id: 'admin.image.amazonS3SSEDescription', defaultMessage: 'When true, encrypt files in Amazon S3 using server-side encryption with Amazon S3-managed keys. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.image.amazonS3SSEDescription', defaultMessage: 'When true, encrypt files in Amazon S3 using server-side encryption with Amazon S3-managed keys. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -1229,7 +1229,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'TestS3Connection',
label: defineMessage({id: 'admin.s3.connectionS3Test', defaultMessage: 'Test Connection'}),
loading: defineMessage({id: 'admin.s3.testing', defaultMessage: 'Testing...'}),
error_message: defineMessage({id: 'admin.s3.s3Fail', defaultMessage: 'Connection unsuccessful: {error}'}),
error_message: defineMessage({id: 'admin.s3.s3Fail', defaultMessage: 'Connection unsuccessful: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime
success_message: defineMessage({id: 'admin.s3.s3Success', defaultMessage: 'Connection was successful'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
},
@ -1284,7 +1284,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'FileSettings.ExportAmazonS3AccessKeyId',
label: defineMessage({id: 'admin.image.amazonS3IdTitle', defaultMessage: 'Amazon S3 Access Key ID:'}),
help_text: defineMessage({id: 'admin.image.amazonS3IdDescription', defaultMessage: '(Optional) Only required if you do not want to authenticate to S3 using an <link>IAM role</link>. Enter the Access Key ID provided by your Amazon EC2 administrator.'}),
help_text: defineMessage({id: 'admin.image.amazonS3IdDescription', defaultMessage: '(Optional) Only required if you do not want to authenticate to S3 using an <link>IAM role</link>. Enter the Access Key ID provided by your Amazon EC2 administrator.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -1320,7 +1320,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'FileSettings.ExportAmazonS3Bucket',
label: defineMessage({id: 'admin.image.amazonS3BucketTitle', defaultMessage: 'Amazon S3 Bucket:'}),
help_text: defineMessage({id: 'admin.image.amazonS3BucketDescription', defaultMessage: 'Name you selected for your S3 bucket in AWS.'}),
placeholder: defineMessage({id: 'admin.image.amazonS3BucketExample', defaultMessage: 'E.g.: "mattermost-export"'}),
placeholder: defineMessage({id: 'admin.image.amazonS3BucketExampleExport', defaultMessage: 'E.g.: "mattermost-export"'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
it.stateEquals('FileSettings.DedicatedExportStore', false),
@ -1332,7 +1332,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'FileSettings.ExportAmazonS3PathPrefix',
label: defineMessage({id: 'admin.image.amazonS3PathPrefixTitle', defaultMessage: 'Amazon S3 Path Prefix:'}),
help_text: defineMessage({id: 'admin.image.amazonS3PathPrefixDescription', defaultMessage: 'Prefix you selected for your S3 bucket in AWS.'}),
placeholder: defineMessage({id: 'admin.image.amazonS3PathPrefixExample', defaultMessage: 'E.g.: "subdir1/" or you can leave it .'}),
placeholder: defineMessage({id: 'admin.image.amazonS3PathPrefixExample', defaultMessage: 'E.g.: "subdir1" or you can leave it empty.'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
it.stateEquals('FileSettings.DedicatedExportStore', false),
@ -1389,7 +1389,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'FileSettings.ExportAmazonS3SSE',
label: defineMessage({id: 'admin.image.amazonS3SSETitle', defaultMessage: 'Enable Server-Side Encryption for Amazon S3:'}),
help_text: defineMessage({id: 'admin.image.amazonS3SSEDescription', defaultMessage: 'When true, encrypt files in Amazon S3 using server-side encryption with Amazon S3-managed keys. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.image.amazonS3SSEDescription', defaultMessage: 'When true, encrypt files in Amazon S3 using server-side encryption with Amazon S3-managed keys. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -1424,7 +1424,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'TestS3Connection',
label: defineMessage({id: 'admin.s3.connectionS3Test', defaultMessage: 'Test Connection'}),
loading: defineMessage({id: 'admin.s3.testing', defaultMessage: 'Testing...'}),
error_message: defineMessage({id: 'admin.s3.s3Fail', defaultMessage: 'Connection unsuccessful: {error}'}),
error_message: defineMessage({id: 'admin.s3.s3Fail', defaultMessage: 'Connection unsuccessful: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime
success_message: defineMessage({id: 'admin.s3.s3Success', defaultMessage: 'Connection was successful'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
@ -1454,7 +1454,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'dropdown',
key: 'ImageProxySettings.ImageProxyType',
label: defineMessage({id: 'admin.image.proxyType', defaultMessage: 'Image Proxy Type:'}),
help_text: defineMessage({id: 'admin.image.proxyTypeDescription', defaultMessage: 'Configure an image proxy to load all Markdown images through a proxy. The image proxy prevents users from making insecure image requests, provides caching for increased performance, and automates image adjustments such as resizing. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.image.proxyTypeDescription', defaultMessage: 'Configure an image proxy to load all Markdown images through a proxy. The image proxy prevents users from making insecure image requests, provides caching for increased performance, and automates image adjustments such as resizing. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -1589,7 +1589,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'TestSmtpConnection',
label: defineMessage({id: 'admin.environment.smtp.connectionSmtpTest', defaultMessage: 'Test Connection'}),
loading: defineMessage({id: 'admin.environment.smtp.testing', defaultMessage: 'Testing...'}),
error_message: defineMessage({id: 'admin.environment.smtp.smtpFail', defaultMessage: 'Connection unsuccessful: {error}'}),
error_message: defineMessage({id: 'admin.environment.smtp.smtpFail', defaultMessage: 'Connection unsuccessful: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
success_message: defineMessage({id: 'admin.environment.smtp.smtpSuccess', defaultMessage: 'No errors were reported while sending an email. Please check your inbox to make sure.'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.SMTP)),
},
@ -1667,7 +1667,7 @@ const AdminDefinition: AdminDefinitionType = {
settings: [
{
type: 'banner',
label: defineMessage({id: 'admin.rate.noteDescription', defaultMessage: 'Changing properties in this section will require a server restart before taking effect.'}),
label: defineMessage({id: 'admin.info_banner.restart_required.desc', defaultMessage: 'Changing properties in this section will require a server restart before taking effect.'}),
banner_type: 'info',
},
{
@ -1747,7 +1747,7 @@ const AdminDefinition: AdminDefinitionType = {
settings: [
{
type: 'banner',
label: defineMessage({id: 'admin.rate.noteDescription', defaultMessage: 'Changing properties other than Site URL in this section will require a server restart before taking effect.'}),
label: defineMessage({id: 'admin.rate.noteDescription', defaultMessage: 'Changing properties in this section will require a server restart before taking effect.'}),
banner_type: 'info',
},
{
@ -1785,7 +1785,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'RateLimitSettings.MemoryStoreSize',
label: defineMessage({id: 'admin.rate.memoryTitle', defaultMessage: 'Memory Store Size:'}),
placeholder: defineMessage({id: 'admin.rate.memoryExample', defaultMessage: 'E.g.: "10000"'}),
help_text: defineMessage({id: 'admin.rate.memoryDescription', defaultMessage: 'Maximum number of users sessions connected to the system as determined by "Vary rate limit by remote address" and "Vary rate limit by HTTP header".'}),
help_text: defineMessage({id: 'admin.rate.memoryDescription', defaultMessage: 'Maximum number of users sessions connected to the system as determined by "Vary rate limit by remote address" and "Vary rate limit by HTTP header" settings below.'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.RATE_LIMITING)),
it.stateEquals('RateLimitSettings.Enable', false),
@ -1805,7 +1805,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'RateLimitSettings.VaryByUser',
label: defineMessage({id: 'admin.rate.varyByUser', defaultMessage: 'Vary rate limit by user:'}),
help_text: defineMessage({id: 'admin.rate.varyByUserDescription', defaultMessage: 'When true, rate limit API access by user athentication token.'}),
help_text: defineMessage({id: 'admin.rate.varyByUserDescription', defaultMessage: 'When true, rate limit API access by user authentication token.'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.RATE_LIMITING)),
it.stateEquals('RateLimitSettings.Enable', false),
@ -1908,7 +1908,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'LogSettings.EnableWebhookDebugging',
label: defineMessage({id: 'admin.log.enableWebhookDebugging', defaultMessage: 'Enable Webhook Debugging:'}),
help_text: defineMessage({id: 'admin.log.enableWebhookDebuggingDescription', defaultMessage: 'When true, sends webhook debug messages to the server logs. To also output the request body of incoming webhooks, set {boldedLogLevel} to "DEBUG".'}),
help_text: defineMessage({id: 'admin.log.enableWebhookDebuggingDescription', defaultMessage: 'When true, sends webhook debug messages to the server logs. To also output the request body of incoming webhooks, set {boldedLogLevel} to "DEBUG".'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
boldedLogLevel: (
<strong>
@ -1925,7 +1925,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'LogSettings.EnableDiagnostics',
label: defineMessage({id: 'admin.log.enableDiagnostics', defaultMessage: 'Enable Diagnostics and Error Reporting:'}),
help_text: defineMessage({id: 'admin.log.enableDiagnosticsDescription', defaultMessage: 'Enable this feature to improve the quality and performance of Mattermost by sending error reporting and diagnostic information to Mattermost, Inc. Read our <link>privacy policy</link> to learn more.'}),
help_text: defineMessage({id: 'admin.log.enableDiagnosticsDescription', defaultMessage: 'Enable this feature to improve the quality and performance of Mattermost by sending error reporting and diagnostic information to Mattermost, Inc. Read our <link>privacy policy</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -1946,7 +1946,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'longtext',
key: 'LogSettings.AdvancedLoggingJSON',
label: defineMessage({id: 'admin.log.AdvancedLoggingJSONTitle', defaultMessage: 'Advanced Logging:'}),
help_text: defineMessage({id: 'admin.log.AdvancedLoggingJSONDescription', defaultMessage: 'The JSON configuration for Advanced Logging. Please see <link>documentation</link> to learn more about Advanced Logging and the JSON format it uses.'}),
help_text: defineMessage({id: 'admin.log.AdvancedLoggingJSONDescription', defaultMessage: 'The JSON configuration for Advanced Logging. Please see <link>documentation</link> to learn more about Advanced Logging and the JSON format it uses.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -2014,7 +2014,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'MetricsSettings.Enable',
label: defineMessage({id: 'admin.metrics.enableTitle', defaultMessage: 'Enable Performance Monitoring:'}),
help_text: defineMessage({id: 'admin.metrics.enableDescription', defaultMessage: 'When true, Mattermost will enable performance monitoring collection and profiling. Please see <link>documentation</link> to learn more about configuring performance monitoring for Mattermost.'}),
help_text: defineMessage({id: 'admin.metrics.enableDescription', defaultMessage: 'When true, Mattermost will enable performance monitoring collection and profiling. Please see <link>documentation</link> to learn more about configuring performance monitoring for Mattermost.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -2032,7 +2032,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'MetricsSettings.EnableClientMetrics',
label: defineMessage({id: 'admin.metrics.enableClientMetricsTitle', defaultMessage: 'Enable Client Performance Monitoring:'}),
help_text: defineMessage({id: 'admin.metrics.enableClientMetricsDescription', defaultMessage: 'When true, Mattermost will enable performance monitoring collection for web and desktop app users. Please see <link>documentation</link> to learn more about configuring performance monitoring for Mattermost.'}),
help_text: defineMessage({id: 'admin.metrics.enableClientMetricsDescription', defaultMessage: 'When true, Mattermost will enable performance monitoring collection for web and desktop app users. Please see <link>documentation</link> to learn more about configuring performance monitoring for Mattermost.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -2107,7 +2107,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'ServiceSettings.AllowedUntrustedInternalConnections',
label: defineMessage({id: 'admin.service.internalConnectionsTitle', defaultMessage: 'Allow untrusted internal connections to: '}),
placeholder: defineMessage({id: 'admin.service.internalConnectionsEx', defaultMessage: 'webhooks.internal.example.com 127.0.0.1 10.0.16.0/28'}),
help_text: defineMessage({id: 'admin.service.internalConnectionsDesc', defaultMessage: 'A whitelist of local network addresses that can be requested by the Mattermost server on behalf of a client. Care should be used when configuring this setting to prevent unintended access to your local network. See <link>documentation</link> to learn more. Changing this requires a server restart before taking effect.'}),
help_text: defineMessage({id: 'admin.service.internalConnectionsDesc', defaultMessage: 'A whitelist of local network addresses that can be requested by the Mattermost server on behalf of a client. Care should be used when configuring this setting to prevent unintended access to your local network. See <link>documentation</link> to learn more. Changing this requires a server restart before taking effect.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -2162,7 +2162,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'NativeAppSettings.MobileEnableSecureFilePreview',
label: defineMessage({id: 'admin.mobileSecurity.secureFilePreviewTitle', defaultMessage: 'Enable Secure File Preview Mode:'}),
help_text: defineMessage({id: 'admin.mobileSecurity.secureFilePreviewDescription', defaultMessage: "Prevents file downloads, previews, and sharing for most file types, even if {mobileAllowDownloads} is enabled. Allows in-app previews for PDFs, videos, and images only. Files are stored temporarily in the app's cache and cannot be exported or shared."}),
help_text: defineMessage({id: 'admin.mobileSecurity.secureFilePreviewDescription', defaultMessage: "Prevents file downloads, previews, and sharing for most file types, even if {mobileAllowDownloads} is enabled. Allows in-app previews for PDFs, videos, and images only. Files are stored temporarily in the app's cache and cannot be exported or shared."}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
mobileAllowDownloads: (
<a href='../site_config/file_sharing_downloads'>
@ -2374,7 +2374,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'SupportSettings.TermsOfServiceLink',
label: defineMessage({id: 'admin.support.termsTitle', defaultMessage: 'Terms of Use Link:'}),
help_text: defineMessage({id: 'admin.support.termsDesc', defaultMessage: 'Link to the terms under which users may use your online service. By default, this includes the "Mattermost Conditions of Use (End Users)" explaining the terms under which Mattermost software is provided to end users. If you change the default link to add your own terms for using the service you provide, your new terms must include a link to the default terms so end users are aware of the Mattermost Conditions of Use (End User) for Mattermost software.'}),
help_text: defineMessage({id: 'admin.support.termsDesc', defaultMessage: 'Link to the terms under which users may use your online service. By default, this includes the "Mattermost Acceptable Use Policy" explaining the terms under which Mattermost software is provided to end users. If you change the default link to add your own terms for using the service you provide, your new terms must include a link to the default terms so end users are aware of the Mattermost Acceptable Use Policy for Mattermost software.'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.CUSTOMIZATION)),
isHidden: it.configIsTrue('ExperimentalSettings', 'RestrictSystemAdmin'),
},
@ -2603,7 +2603,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'dropdown',
key: 'TeamSettings.RestrictDirectMessage',
label: defineMessage({id: 'admin.team.restrictDirectMessage', defaultMessage: 'Enable users to open Direct Message channels with:'}),
help_text: defineMessage({id: 'admin.team.restrictDirectMessageDesc', defaultMessage: '"Any user on the Mattermost server" enables users to open a Direct Message channel with any user on the server, even if they are not on any teams together. "Any member of the team" limits the ability in the Direct Messages "More" menu to only open Direct Message channels with users who are in the same team.'}),
help_text: defineMessage({id: 'admin.team.restrictDirectMessageDesc', defaultMessage: "'Any user on the Mattermost server' enables users to open a Direct Message channel with any user on the server, even if they are not on any teams together. 'Any member of the team' limits the ability in the Direct Messages 'More' menu to only open Direct Message channels with users who are in the same team."}),
options: [
{
value: 'any',
@ -2641,7 +2641,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'TeamSettings.LockTeammateNameDisplay',
label: defineMessage({id: 'admin.lockTeammateNameDisplay', defaultMessage: 'Lock Teammate Name Display for all users: '}),
help_text: defineMessage({id: 'admin.lockTeammateNameDisplayHelpText', defaultMessage: 'When true, disables users\' ability to change settings under <strong>Account Menu > Account Settings > Display > Teammate Name Display</strong>.'}),
help_text: defineMessage({id: 'admin.lockTeammateNameDisplayHelpText', defaultMessage: "When true, disables users' ability to change settings under <strong>Settings > Display > Teammate Name Display</strong>."}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
strong: (msg: string) => <strong>{msg}</strong>,
},
@ -2756,7 +2756,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'dropdown',
key: 'EmailSettings.EmailNotificationContentsType',
label: defineMessage({id: 'admin.environment.notifications.contents.label', defaultMessage: 'Email Notification Contents:'}),
help_text: defineMessage({id: 'admin.environment.notifications.contents.help', defaultMessage: '**Send full message contents** - Sender name and channel are included in email notifications. **Send generic description with only sender name** - Only the name of the person who sent the message, with no information about channel name or message contents are included in email notifications. Typically used for compliance reasons if Mattermost contains confidential information and policy dictates it cannot be stored in email.'}),
help_text: defineMessage({id: 'admin.environment.notifications.contents.help', defaultMessage: '**Send full message contents** - Sender name and channel are included in email notifications.\n **Send generic description with only sender name** - Only the name of the person who sent the message, with no information about channel name or message contents are included in email notifications. Typically used for compliance reasons if Mattermost contains confidential information and policy dictates it cannot be stored in email.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
help_text_markdown: true,
options: [
{
@ -2831,7 +2831,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'dropdown',
key: 'EmailSettings.PushNotificationContents',
label: defineMessage({id: 'admin.environment.notifications.pushContents.label', defaultMessage: 'Push Notification Contents:'}),
help_text: defineMessage({id: 'admin.environment.notifications.pushContents.help', defaultMessage: "**Generic description with only sender name** - Includes only the name of the person who sent the message in push notifications, with no information about channel name or message contents. **Generic description with sender and channel names** - Includes the name of the person who sent the message and the channel it was sent in, but not the message contents. **Full message content sent in the notification payload** - Includes the message contents in the push notification payload that is relayed through Apple's Push Notification Service (APNS) or Google's Firebase Cloud Messaging (FCM). It is **highly recommended** this option only be used with an \"https\" protocol to encrypt the connection and protect confidential information sent in messages."}),
help_text: defineMessage({id: 'admin.environment.notifications.pushContents.help', defaultMessage: '**Generic description with only sender name** - Includes only the name of the person who sent the message in push notifications, with no information about channel name or message contents.\n **Generic description with sender and channel names** - Includes the name of the person who sent the message and the channel it was sent in, but not the message contents.\n **Full message content sent in the notification payload** - Includes the message contents in the push notification payload that is relayed through Apple\'s Push Notification Service (APNS) or Google\'s Firebase Cloud Messaging (FCM). It is **highly recommended** this option only be used with an "https" protocol to encrypt the connection and protect confidential information sent in messages.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
help_text_markdown: true,
options: [
{
@ -2854,7 +2854,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'dropdown',
key: 'EmailSettings.PushNotificationContents',
label: defineMessage({id: 'admin.environment.notifications.pushContents.label', defaultMessage: 'Push Notification Contents:'}),
help_text: defineMessage({id: 'admin.environment.notifications.pushContents.withIdLoaded.help', defaultMessage: "**Generic description with only sender name** - Includes only the name of the person who sent the message in push notifications, with no information about channel name or message contents. **Generic description with sender and channel names** - Includes the name of the person who sent the message and the channel it was sent in, but not the message contents. **Full message content sent in the notification payload** - Includes the message contents in the push notification payload that is relayed through Apple's Push Notification Service (APNS) or Google's Firebase Cloud Messaging (FCM). It is **highly recommended** this option only be used with an \"https\" protocol to encrypt the connection and protect confidential information sent in messages. **Full message content fetched from the server on receipt** - The notification payload relayed through APNS or FCM contains no message content, instead it contains a unique message ID used to fetch message content from the server when a push notification is received by a device. If the server cannot be reached, a generic notification will be displayed."}),
help_text: defineMessage({id: 'admin.environment.notifications.pushContents.withIdLoaded.help', defaultMessage: "**Generic description with only sender name** - Includes only the name of the person who sent the message in push notifications, with no information about channel name or message contents.\n **Generic description with sender and channel names** - Includes the name of the person who sent the message and the channel it was sent in, but not the message contents.\n **Full message content sent in the notification payload** - Includes the message contents in the push notification payload that is relayed through Apple's Push Notification Service (APNS) or Google's Firebase Cloud Messaging (FCM). It is **highly recommended** this option only be used with an \"https\" protocol to encrypt the connection and protect confidential information sent in messages.\n**Full message content fetched from the server on receipt** - The notification payload relayed through APNS or FCM contains no message content, instead it contains a unique message ID used to fetch message content from the server when a push notification is received by a device. If the server cannot be reached, a generic notification will be displayed."}), // eslint-disable-line formatjs/no-multiple-whitespaces
help_text_markdown: true,
options: [
{
@ -3022,7 +3022,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'dropdown',
key: 'ServiceSettings.CollapsedThreads',
label: defineMessage({id: 'admin.experimental.collapsedThreads.title', defaultMessage: 'Threaded Discussions'}),
help_text: defineMessage({id: 'admin.experimental.collapsedThreads.desc', defaultMessage: 'When enabled (default off), users must enable Threaded Discussions in Settings. When disabled, users cannot access Threaded Discussions. Please review our <linkKnownIssues>documentation for known issues</linkKnownIssues> and help provide feedback in our <linkCommunityChannel>Community Channel</linkCommunityChannel>.'}),
help_text: defineMessage({id: 'admin.experimental.collapsedThreads.desc', defaultMessage: 'When enabled (default off), users have the option to enable Threaded Discussions in Account Settings. When enabled (default on), users see Threaded Discussions by default and have the option to disable it in Account Settings. When always on, users are required to use Threaded Discussions and cannot disable it.'}),
help_text_values: {
linkKnownIssues: (msg: string) => (
<ExternalLink
@ -3096,7 +3096,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.PostPriority',
label: defineMessage({id: 'admin.posts.postPriority.title', defaultMessage: 'Message Priority'}),
help_text: defineMessage({id: 'admin.posts.postPriority.desc', defaultMessage: 'When enabled, users can configure a visual indicator to communicate messages that are important or urgent. Learn more about message priority in our <link>documentation</link>.'}),
help_text: defineMessage({id: 'admin.posts.postPriority.desc', defaultMessage: 'When enabled, users can configure a visual indicator to communicate messages that are important or urgent. Learn more about message priority in our <link>documentation</link>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3114,7 +3114,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.AllowPersistentNotifications',
label: defineMessage({id: 'admin.posts.persistentNotifications.title', defaultMessage: 'Persistent Notifications'}),
help_text: defineMessage({id: 'admin.posts.persistentNotifications.desc', defaultMessage: 'When enabled, users can trigger repeating notifications for the recipients of urgent messages. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}),
help_text: defineMessage({id: 'admin.posts.persistentNotifications.desc', defaultMessage: 'When enabled, users can trigger repeating notifications for the recipients of urgent messages. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3133,7 +3133,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'number',
key: 'ServiceSettings.PersistentNotificationMaxRecipients',
label: defineMessage({id: 'admin.posts.persistentNotificationsMaxRecipients.title', defaultMessage: 'Maximum number of recipients for persistent notifications'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsMaxRecipients.desc', defaultMessage: 'Configure the maximum number of recipients to which users may send persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsMaxRecipients.desc', defaultMessage: 'Configure the maximum number of recipients to which users may send persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3155,7 +3155,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'number',
key: 'ServiceSettings.PersistentNotificationIntervalMinutes',
label: defineMessage({id: 'admin.posts.persistentNotificationsInterval.title', defaultMessage: 'Frequency of persistent notifications'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsInterval.desc', defaultMessage: 'Configure the number of minutes between repeated notifications for urgent messages send with persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsInterval.desc', defaultMessage: 'Configure the number of minutes between repeated notifications for urgent messages send with persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3172,13 +3172,13 @@ const AdminDefinition: AdminDefinitionType = {
it.configIsFalse('ServiceSettings', 'PostPriority'),
it.configIsFalse('ServiceSettings', 'AllowPersistentNotifications'),
),
validate: validators.minValue(2, defineMessage({id: 'admin.posts.persistentNotificationsInterval.minValue', defaultMessage: 'Frequency cannot not be set to less than 2 minutes'})),
validate: validators.minValue(2, defineMessage({id: 'admin.posts.persistentNotificationsInterval.minValue', defaultMessage: 'Frequency must be at least two minutes'})),
},
{
type: 'number',
key: 'ServiceSettings.PersistentNotificationMaxCount',
label: defineMessage({id: 'admin.posts.persistentNotificationsMaxCount.title', defaultMessage: 'Total number of persistent notification per post'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsMaxCount.desc', defaultMessage: 'Configure the maximum number of times users may receive persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsMaxCount.desc', defaultMessage: 'Configure the maximum number of times users may receive persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3200,7 +3200,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.AllowPersistentNotificationsForGuests',
label: defineMessage({id: 'admin.posts.persistentNotificationsGuests.title', defaultMessage: 'Allow guests to send persistent notifications'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsGuests.desc', defaultMessage: 'Whether a guest is able to require persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}),
help_text: defineMessage({id: 'admin.posts.persistentNotificationsGuests.desc', defaultMessage: 'Whether a guest is able to require persistent notifications. Learn more about message priority and persistent notifications in our <link>documentation</link>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3358,7 +3358,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnablePermalinkPreviews',
label: defineMessage({id: 'admin.customization.enablePermalinkPreviewsTitle', defaultMessage: 'Enable message link previews:'}),
help_text: defineMessage({id: 'admin.customization.enablePermalinkPreviewsDesc', defaultMessage: 'When enabled, links to Mattermost messages will generate a preview for any users that have access to the original message. Please review our <link>documentation</link> for details.'}),
help_text: defineMessage({id: 'admin.customization.enablePermalinkPreviewsDesc', defaultMessage: 'When enabled, links to Mattermost messages will generate a preview for any users that have access to the original message. Please review our <link>documentation</link> for details.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3376,21 +3376,21 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableSVGs',
label: defineMessage({id: 'admin.customization.enableSVGsTitle', defaultMessage: 'Enable SVGs:'}),
help_text: defineMessage({id: 'admin.customization.enableSVGsDesc', defaultMessage: 'Enable previews for SVG file attachments and allow them to appear in messages. Enabling SVGs is not recommended in environments where not all users are trusted.'}),
help_text: defineMessage({id: 'admin.customization.enableSVGsDesc', defaultMessage: 'Enable previews for SVG file attachments and allow them to appear in messages.\n\nEnabling SVGs is not recommended in environments where not all users are trusted.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.POSTS)),
},
{
type: 'bool',
key: 'ServiceSettings.EnableLatex',
label: defineMessage({id: 'admin.customization.enableLatexTitle', defaultMessage: 'Enable Latex Rendering:'}),
help_text: defineMessage({id: 'admin.customization.enableLatexDesc', defaultMessage: 'Enable rendering of Latex in code blocks. If false, Latex code will be highlighted only. Enabling Latex is not recommended in environments where not all users are trusted.'}),
help_text: defineMessage({id: 'admin.customization.enableLatexDesc', defaultMessage: 'Enable rendering of Latex in code blocks. If false, Latex code will be highlighted only.\n\nEnabling Latex is not recommended in environments where not all users are trusted.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.POSTS)),
},
{
type: 'bool',
key: 'ServiceSettings.EnableInlineLatex',
label: defineMessage({id: 'admin.customization.enableInlineLatexTitle', defaultMessage: 'Enable Inline Latex Rendering:'}),
help_text: defineMessage({id: 'admin.customization.enableInlineLatexDesc', defaultMessage: 'Enable rendering of inline Latex code. If false, Latex can only be rendered in a code block using syntax highlighting. Please review our <link>documentation</link> for details about text formatting.'}),
help_text: defineMessage({id: 'admin.customization.enableInlineLatexDesc', defaultMessage: 'Enable rendering of inline Latex code. If false, Latex can only be rendered in a code block using syntax highlighting. Please review our <link>documentation</link> for details about text formatting.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3418,7 +3418,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'ServiceSettings.GoogleDeveloperKey',
label: defineMessage({id: 'admin.service.googleTitle', defaultMessage: 'Google API Key:'}),
placeholder: defineMessage({id: 'admin.service.googleExample', defaultMessage: 'E.g.: "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"'}),
help_text: defineMessage({id: 'admin.service.googleDescription', defaultMessage: 'Set this key to enable the display of titles for embedded YouTube video previews. Without the key, YouTube previews will still be created based on hyperlinks appearing in messages or comments but they will not show the video title. View a <link>Google Developers Tutorial</link> for instructions on how to obtain a key and add YouTube Data API v3 as a service to your key.'}),
help_text: defineMessage({id: 'admin.service.googleDescription', defaultMessage: 'Set this key to enable the display of titles for embedded YouTube video previews. Without the key, YouTube previews will still be created based on hyperlinks appearing in messages or comments but they will not show the video title. View a <link>Google Developers Tutorial</link> for instructions on how to obtain a key and add YouTube Data API v3 as a service to your key.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3443,7 +3443,7 @@ const AdminDefinition: AdminDefinitionType = {
{
type: 'number',
key: 'DisplaySettings.MaxMarkdownNodes',
label: defineMessage({id: 'admin.customization.maxMarkdownNodesTitle', defaultMessage: 'Max Markdown Nodes:'}),
label: defineMessage({id: 'admin.customization.maxMarkdownNodesTitle', defaultMessage: 'Maximum Markdown Nodes:'}),
help_text: defineMessage({id: 'admin.customization.maxMarkdownNodesDesc', defaultMessage: 'When rendering Markdown text in the mobile app, controls the maximum number of Markdown elements (eg. emojis, links, table cells, etc) that can be in a single piece of text. If set to 0, a default limit will be used.'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.POSTS)),
},
@ -3499,7 +3499,7 @@ const AdminDefinition: AdminDefinitionType = {
isHidden: it.any(it.not(it.userHasReadPermissionOnResource(RESOURCE_KEYS.SITE.POSTS)), it.configIsFalse('FeatureFlags', 'MoveThreadsEnabled'), it.not(it.licensed)),
schema: {
id: 'WranglerSettings',
name: defineMessage({id: 'admin.site.move_thread', defaultMessage: 'Move Thread (Beta)'}),
name: defineMessage({id: 'admin.site.move_thread', defaultMessage: 'Move Thread'}),
settings: [
{
type: 'roles',
@ -3531,7 +3531,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'WranglerSettings.MoveThreadToAnotherTeamEnable',
label: defineMessage({id: 'admin.experimental.moveThreadToAnotherTeamEnable.title', defaultMessage: 'Enable Moving Threads To Different Teams'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadToAnotherTeamEnable.desc', defaultMessage: 'Control whether Wrangler is permitted to move message threads from one team to another or not.'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadToAnotherTeamEnable.desc', defaultMessage: 'Control whether move thread is permitted to move message threads from one team to another or not.'}),
help_text_markdown: false,
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)),
},
@ -3539,7 +3539,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'WranglerSettings.MoveThreadFromPrivateChannelEnable',
label: defineMessage({id: 'admin.experimental.moveThreadFromPrivateChannelEnable.title', defaultMessage: 'Enable Moving Threads From Private Channels'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadFromPrivateChannelEnable.desc', defaultMessage: 'Control whether Wrangler is permitted to move message threads from private channels or not.'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadFromPrivateChannelEnable.desc', defaultMessage: 'Control whether move thread is permitted to move message threads from private channels or not.'}),
help_text_markdown: false,
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)),
},
@ -3547,7 +3547,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'WranglerSettings.MoveThreadFromDirectMessageChannelEnable',
label: defineMessage({id: 'admin.experimental.moveThreadFromDirectMessageChannelEnable.title', defaultMessage: 'Enable Moving Threads From Direct Message Channels'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadFromDirectMessageChannelEnable.desc', defaultMessage: 'Control whether Wrangler is permitted to move message threads from direct message channels or not.'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadFromDirectMessageChannelEnable.desc', defaultMessage: 'Control whether move thread is permitted to move message threads from direct message channels or not.'}),
help_text_markdown: false,
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)),
},
@ -3555,7 +3555,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'WranglerSettings.MoveThreadFromGroupMessageChannelEnable',
label: defineMessage({id: 'admin.experimental.moveThreadFromGroupMessageChannelEnable.title', defaultMessage: 'Enable Moving Threads From Group Message Channels'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadFromGroupMessageChannelEnable.desc', defaultMessage: 'Control whether Wrangler is permitted to move message threads from group message channels or not.'}),
help_text: defineMessage({id: 'admin.experimental.moveThreadFromGroupMessageChannelEnable.desc', defaultMessage: 'Control whether move thread is permitted to move message threads from group message channels or not.'}),
help_text_markdown: false,
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)),
},
@ -3638,7 +3638,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'AnnouncementSettings.AdminNoticesEnabled',
label: defineMessage({id: 'admin.notices.enableAdminNoticesTitle', defaultMessage: 'Enable Admin Notices: '}),
help_text: defineMessage({id: 'admin.notices.enableAdminNoticesDescription', defaultMessage: 'When enabled, System Admins will receive notices about available server upgrades and relevant system administration features. <link>Learn more about notices</link> in our documentation.'}),
help_text: defineMessage({id: 'admin.notices.enableAdminNoticesDescription', defaultMessage: 'When enabled, System Admins will receive notices about available server upgrades and relevant system administration features. <link>Learn more about notices</link> in our documentation.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3656,7 +3656,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'AnnouncementSettings.UserNoticesEnabled',
label: defineMessage({id: 'admin.notices.enableEndUserNoticesTitle', defaultMessage: 'Enable End User Notices: '}),
help_text: defineMessage({id: 'admin.notices.enableEndUserNoticesDescription', defaultMessage: 'When enabled, all users will receive notices about available client upgrades and relevant end user features to improve user experience. <link>Learn more about notices</link> in our documentation.'}),
help_text: defineMessage({id: 'admin.notices.enableEndUserNoticesDescription', defaultMessage: 'When enabled, all users will receive notices about available client upgrades and relevant end user features to improve user experience. <link>Learn more about notices</link> in our documentation.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3737,18 +3737,6 @@ const AdminDefinition: AdminDefinitionType = {
help_text: defineMessage({id: 'admin.team.userCreationDescription', defaultMessage: 'When false, the ability to create accounts is disabled, and selecting Create Account displays an error. Applies to Email, OpenID Connect, and OAuth 2.0 user account authentication.'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.SIGNUP)),
},
{
type: 'text',
key: 'TeamSettings.RestrictCreationToDomains',
label: defineMessage({id: 'admin.team.restrictTitle', defaultMessage: 'Restrict new system and team members to specified email domains:'}),
help_text: defineMessage({id: 'admin.team.restrictDescription', defaultMessage: 'New user accounts are restricted to the above specified email domain (e.g. "mattermost.com") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.com"). New teams can only be created by users from the above domain(s). This setting only affects email login for users.'}),
placeholder: defineMessage({id: 'admin.team.restrictExample', defaultMessage: 'E.g.: "corp.mattermost.com, mattermost.com"'}),
isHidden: it.all(
it.licensed,
it.not(it.licensedForSku('starter')),
),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.SIGNUP)),
},
{
type: 'text',
key: 'TeamSettings.RestrictCreationToDomains',
@ -3782,7 +3770,7 @@ const AdminDefinition: AdminDefinitionType = {
action: invalidateAllEmailInvites,
label: defineMessage({id: 'admin.team.invalidateEmailInvitesTitle', defaultMessage: 'Invalidate pending email invites'}),
help_text: defineMessage({id: 'admin.team.invalidateEmailInvitesDescription', defaultMessage: 'This will invalidate active email invitations that have not been accepted by the user. By default email invitations expire after 48 hours.'}),
error_message: defineMessage({id: 'admin.team.invalidateEmailInvitesFail', defaultMessage: 'Unable to invalidate pending email invites: {error}'}),
error_message: defineMessage({id: 'admin.team.invalidateEmailInvitesFail', defaultMessage: 'Unable to invalidate pending email invites: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
success_message: defineMessage({id: 'admin.team.invalidateEmailInvitesSuccess', defaultMessage: 'Pending email invitations invalidated successfully'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.SIGNUP)),
},
@ -3850,7 +3838,7 @@ const AdminDefinition: AdminDefinitionType = {
settings: [
{
type: 'banner',
label: defineMessage({id: 'admin.mfa.bannerDesc', defaultMessage: '<link>Multi-factor authentication</link> is available for accounts with AD/LDAP or email login. If other login methods are used, MFA should be configured with the authentication provider.'}),
label: defineMessage({id: 'admin.mfa.bannerDesc', defaultMessage: '<link>Multi-factor authentication</link> is available for accounts with AD/LDAP or email login. If other login methods are used, MFA should be configured with the authentication provider.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
label_values: {
link: (msg: string) => (
<ExternalLink
@ -3867,14 +3855,14 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableMultifactorAuthentication',
label: defineMessage({id: 'admin.service.mfaTitle', defaultMessage: 'Enable Multi-factor Authentication:'}),
help_text: defineMessage({id: 'admin.service.mfaDesc', defaultMessage: 'When true, users with AD/LDAP or email login can add multi-factor authentication to their account using Google Authenticator.'}),
help_text: defineMessage({id: 'admin.service.mfaDesc', defaultMessage: 'When true, users with AD/LDAP or email login can add multi-factor authentication to their account using an authenticator app.'}),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.MFA)),
},
{
type: 'bool',
key: 'ServiceSettings.EnforceMultifactorAuthentication',
label: defineMessage({id: 'admin.service.enforceMfaTitle', defaultMessage: 'Enforce Multi-factor Authentication:'}),
help_text: defineMessage({id: 'admin.service.enforceMfaDesc', defaultMessage: 'When true, <link>multi-factor authentication</link> is required for login. New users will be required to configure MFA on signup. Logged in users without MFA configured are redirected to the MFA setup page until configuration is complete.\n \nIf your system has users with login methods other than AD/LDAP and email, MFA must be enforced with the authentication provider outside of Mattermost.'}),
help_text: defineMessage({id: 'admin.service.enforceMfaDesc', defaultMessage: 'When true, <link>multi-factor authentication</link> is required for login. New users will be required to configure MFA on signup. Logged in users without MFA configured are redirected to the MFA setup page until configuration is complete.\n \nIf your system has users with login methods other than AD/LDAP and email, MFA must be enforced with the authentication provider outside of Mattermost.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -3943,7 +3931,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'SamlSettings.Enable',
label: defineMessage({id: 'admin.saml.enableTitle', defaultMessage: 'Enable Login With SAML 2.0:'}),
help_text: defineMessage({id: 'admin.saml.enableDescription', defaultMessage: 'When true, Mattermost allows login using SAML 2.0. Please see <link>documentation</link> to learn more about configuring SAML for Mattermost.'}),
help_text: defineMessage({id: 'admin.saml.enableDescription', defaultMessage: 'When true, Mattermost allows login using SAML 2.0. Please see <link>documentation</link> to learn more about configuring SAML for Mattermost.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -3961,7 +3949,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'SamlSettings.EnableSyncWithLdap',
label: defineMessage({id: 'admin.saml.enableSyncWithLdapTitle', defaultMessage: 'Enable Synchronizing SAML Accounts With AD/LDAP:'}),
help_text: defineMessage({id: 'admin.saml.enableSyncWithLdapDescription', defaultMessage: 'When true, Mattermost periodically synchronizes SAML user attributes, including user deactivation and removal, from AD/LDAP. Enable and configure synchronization settings at <strong>Authentication > AD/LDAP</strong>. When false, user attributes are updated from SAML during user login. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.saml.enableSyncWithLdapDescription', defaultMessage: 'When true, Mattermost periodically synchronizes SAML user attributes, including user deactivation and removal, from AD/LDAP. Enable and configure synchronization settings at <strong>Authentication > AD/LDAP</strong>. When false, user attributes are updated from SAML during user login. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -3995,7 +3983,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'SamlSettings.EnableSyncWithLdapIncludeAuth',
label: defineMessage({id: 'admin.saml.enableSyncWithLdapIncludeAuthTitle', defaultMessage: 'Override SAML bind data with AD/LDAP information:'}),
help_text: defineMessage({id: 'admin.saml.enableSyncWithLdapIncludeAuthDescription', defaultMessage: 'When true, Mattermost will override the SAML ID attribute with the AD/LDAP ID attribute if configured or override the SAML Email attribute with the AD/LDAP Email attribute if SAML ID attribute is not present. This will allow you automatically migrate users from Email binding to ID binding to prevent creation of new users when an email address changes for a user. Moving from true to false, will remove the override from happening. <strong>Note:</strong> SAML IDs must match the LDAP IDs to prevent disabling of user accounts. Please review <link>documentation</link> for more information.'}),
help_text: defineMessage({id: 'admin.saml.enableSyncWithLdapIncludeAuthDescription', defaultMessage: 'When true, Mattermost will override the SAML ID attribute with the AD/LDAP ID attribute if configured or override the SAML Email attribute with the AD/LDAP Email attribute if SAML ID attribute is not present. This will allow you automatically migrate users from Email binding to ID binding to prevent creation of new users when an email address changes for a user. Moving from true to false, will remove the override from happening. <strong>Note:</strong> SAML IDs must match the LDAP IDs to prevent disabling of user accounts. Please review <link>documentation</link> for more information.'}), // eslint-disable-line formatjs/no-multiple-whitespaces, formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -4018,7 +4006,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'SamlSettings.IdpMetadataURL',
label: defineMessage({id: 'admin.saml.idpMetadataUrlTitle', defaultMessage: 'Identity Provider Metadata URL:'}),
help_text: defineMessage({id: 'admin.saml.idpMetadataUrlDesc', defaultMessage: 'The Metadata URL for the Identity Provider you use for SAML requests'}),
help_text: defineMessage({id: 'admin.saml.idpMetadataUrlDesc', defaultMessage: 'The URL where Mattermost sends a request to obtain metadata'}),
placeholder: defineMessage({id: 'admin.saml.idpMetadataUrlEx', defaultMessage: 'E.g.: "https://idp.example.org/SAML2/saml/metadata"'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.SAML)),
@ -4099,7 +4087,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'SamlSettings.AssertionConsumerServiceURL',
label: defineMessage({id: 'admin.saml.assertionConsumerServiceURLTitle', defaultMessage: 'Service Provider Login URL:'}),
help_text: defineMessage({id: 'admin.saml.assertionConsumerServiceURLPopulatedDesc', defaultMessage: 'This field is also known as the Assertion Consumer Service URL.'}),
placeholder: defineMessage({id: 'admin.saml.assertionConsumerServiceURLEx', defaultMessage: 'E.g.: "<urlChunk>your-mattermost-url</urlChunk>"'}),
placeholder: defineMessage({id: 'admin.saml.assertionConsumerServiceURLEx', defaultMessage: 'E.g.: "<urlChunk>your-mattermost-url</urlChunk>"'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
placeholder_values: {
urlChunk: (chunk: string) => `https://'<${chunk}>'/login/sso/saml`,
},
@ -4121,7 +4109,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'SamlSettings.ServiceProviderIdentifier',
label: defineMessage({id: 'admin.saml.serviceProviderIdentifierTitle', defaultMessage: 'Service Provider Identifier:'}),
help_text: defineMessage({id: 'admin.saml.serviceProviderIdentifierDesc', defaultMessage: 'The unique identifier for the Service Provider, usually the same as Service Provider Login URL. In ADFS, this MUST match the Relying Party Identifier.'}),
placeholder: defineMessage({id: 'admin.saml.serviceProviderIdentifierEx', defaultMessage: 'E.g.: "<urlChunk>your-mattermost-url</urlChunk>"'}),
placeholder: defineMessage({id: 'admin.saml.serviceProviderIdentifierEx', defaultMessage: 'E.g.: "<urlChunk>your-mattermost-url</urlChunk>"'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
placeholder_values: {
urlChunk: (chunk: string) => `https://'<${chunk}>'/login/sso/saml`,
},
@ -4275,7 +4263,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'SamlSettings.GuestAttribute',
label: defineMessage({id: 'admin.saml.guestAttrTitle', defaultMessage: 'Guest Attribute:'}),
placeholder: defineMessage({id: 'admin.saml.guestAttrEx', defaultMessage: 'E.g.: "usertype=Guest" or "isGuest=true"'}),
help_text: defineMessage({id: 'admin.saml.guestAttrDesc', defaultMessage: '(Optional) Requires Guest Access to be enabled before being applied. The attribute in the SAML Assertion that will be used to apply a guest role to users in Mattermost. Guests are prevented from accessing teams or channels upon logging in until they are assigned a team and at least one channel. Note: If this attribute is removed/changed from your guest user in SAML and the user is still active, they will not be promoted to a member and will retain their Guest role. Guests can be promoted in **System Console > User Management**. Existing members that are identified by this attribute as a guest will be demoted from a member to a guest when they are asked to login next. The next login is based upon Session lengths set in **System Console > Session Lengths**. It is highly recommend to manually demote users to guests in **System Console > User Management ** to ensure access is restricted immediately.'}),
help_text: defineMessage({id: 'admin.saml.guestAttrDesc', defaultMessage: '(Optional) Requires Guest Access to be enabled before being applied. The attribute in the SAML Assertion that will be used to apply a guest role to users in Mattermost. Guests are prevented from accessing teams or channels upon logging in until they are assigned a team and at least one channel.\n \nNote: If this attribute is removed/changed from your guest user in SAML and the user is still active, they will not be promoted to a member and will retain their Guest role. Guests can be promoted in **System Console > User Management**.\n \n \nExisting members that are identified by this attribute as a guest will be demoted from a member to a guest when they are asked to login next. The next login is based upon Session lengths set in **System Console > Session Lengths**. It is highly recommend to manually demote users to guests in **System Console > User Management ** to ensure access is restricted immediately.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
help_text_markdown: true,
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.SAML)),
@ -4297,7 +4285,7 @@ const AdminDefinition: AdminDefinitionType = {
key: 'SamlSettings.AdminAttribute',
label: defineMessage({id: 'admin.saml.adminAttrTitle', defaultMessage: 'Admin Attribute:'}),
placeholder: defineMessage({id: 'admin.saml.adminAttrEx', defaultMessage: 'E.g.: "usertype=Admin" or "isAdmin=true"'}),
help_text: defineMessage({id: 'admin.saml.adminAttrDesc', defaultMessage: '(Optional) The attribute in the SAML Assertion for designating System Admins. The users selected by the query will have access to your Mattermost server as System Admins. By default, System Admins have complete access to the Mattermost System Console. Existing members that are identified by this attribute will be promoted from member to System Admin upon next login. The next login is based upon Session lengths set in **System Console > Session Lengths.** It is highly recommend to manually demote users to members in **System Console > User Management** to ensure access is restricted immediately. Note: If this filter is removed/changed, System Admins that were promoted via this filter will be demoted to members and will not retain access to the System Console. When this filter is not in use, System Admins can be manually promoted/demoted in **System Console > User Management**.'}),
help_text: defineMessage({id: 'admin.saml.adminAttrDesc', defaultMessage: '(Optional) The attribute in the SAML Assertion for designating System Admins. The users selected by the query will have access to your Mattermost server as System Admins. By default, System Admins have complete access to the Mattermost System Console.\n \nExisting members that are identified by this attribute will be promoted from member to System Admin upon next login. The next login is based upon Session lengths set in **System Console > Session Lengths**. It is highly recommend to manually demote users to members in **System Console > User Management** to ensure access is restricted immediately.\n \nNote: If this filter is removed/changed, System Admins that were promoted via this filter will be demoted to members and will not retain access to the System Console. When this filter is not in use, System Admins can be manually promoted/demoted in **System Console > User Management**.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
help_text_markdown: true,
isDisabled: it.any(
it.not(it.isSystemAdmin),
@ -4486,7 +4474,7 @@ const AdminDefinition: AdminDefinitionType = {
{
value: Constants.GITLAB_SERVICE,
display_name: defineMessage({id: 'admin.oauth.gitlab', defaultMessage: 'GitLab'}),
help_text: defineMessage({id: 'admin.gitlab.EnableMarkdownDesc', defaultMessage: '1. Log in to your GitLab account and go to Profile Settings -> Applications.\n2. Enter Redirect URIs "<loginUrlChunk>your-mattermost-url</loginUrlChunk>" (example: http://localhost:8065/login/gitlab/complete) and "<signupUrlChunk>your-mattermost-url</signupUrlChunk>".\n3. Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.\n4. Complete the Endpoint URLs below.'}),
help_text: defineMessage({id: 'admin.gitlab.EnableMarkdownDesc', defaultMessage: '1. Log in to your GitLab account and go to Profile Settings -> Applications.\n2. Enter Redirect URIs "<loginUrlChunk>your-mattermost-url</loginUrlChunk>" (example: http://localhost:8065/login/gitlab/complete) and "<signupUrlChunk>your-mattermost-url</signupUrlChunk>".\n3. Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.\n4. Complete the Endpoint URLs below.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
loginUrlChunk: (chunk: string) => `<${chunk}>/login/gitlab/complete`,
signupUrlChunk: (chunk: string) => `<${chunk}>/signup/gitlab/complete`,
@ -4497,7 +4485,7 @@ const AdminDefinition: AdminDefinitionType = {
value: Constants.GOOGLE_SERVICE,
display_name: defineMessage({id: 'admin.oauth.google', defaultMessage: 'Google Apps'}),
isHidden: it.all(it.not(it.licensedForFeature('GoogleOAuth')), it.not(it.cloudLicensed)),
help_text: defineMessage({id: 'admin.google.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Google account.\n2. Go to <linkConsole>https://console.developers.google.com</linkConsole>, click <strong>Credentials</strong> in the left hand sidebar and enter "Mattermost - your-company-name" as the <strong>Project Name</strong>, then click <strong>Create</strong>.\n3. Click the <strong>OAuth consent screen</strong> header and enter "Mattermost" as the <strong>Product name shown to users</strong>, then click <strong>Save</strong>.\n4. Under the <strong>Credentials</strong> header, click <strong>Create credentials</strong>, choose <strong>OAuth client ID</strong> and select <strong>Web Application</strong>.\n5. Under <strong>Restrictions</strong> and <strong>Authorized redirect URIs</strong> enter <strong>"your-mattermost-url/signup/google/complete"</strong> (example: http://localhost:8065/signup/google/complete). Click <strong>Create</strong>.\n6. Paste the <strong>Client ID</strong> and <strong>Client Secret</strong> to the fields below, then click <strong>Save</strong>.\n7. Go to the <linkAPI>Google People API</linkAPI> and click <strong>Enable</strong>.'}),
help_text: defineMessage({id: 'admin.google.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Google account.\n2. Go to <linkConsole>https://console.developers.google.com</linkConsole>, click <strong>Credentials</strong> in the left hand side.\n 3. Under the <strong>Credentials</strong> header, click <strong>Create credentials</strong>, choose <strong>OAuth client ID</strong> and select <strong>Web Application</strong>.\n 4. Enter "Mattermost - your-company-name" as the <strong>Name</strong>.\n 5. Under <strong>Authorized redirect URIs</strong> enter <strong>"your-mattermost-url/signup/google/complete"</strong> (example: http://localhost:8065/signup/google/complete). Click <strong>Create</strong>.\n 6. Paste the <strong>Client ID</strong> and <strong>Client Secret</strong> to the fields below, then click <strong>Save</strong>.\n 7. Go to the <linkAPI>Google People API</linkAPI> and click <strong>Enable</strong>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
linkLogin: (msg: string) => (
@ -4531,7 +4519,7 @@ const AdminDefinition: AdminDefinitionType = {
value: Constants.OFFICE365_SERVICE,
display_name: defineMessage({id: 'admin.oauth.office365', defaultMessage: 'Entra ID'}),
isHidden: it.all(it.not(it.licensedForFeature('Office365OAuth')), it.not(it.cloudLicensed)),
help_text: defineMessage({id: 'admin.office365.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Microsoft account. \n2. In Microsoft, go to <strong>Applications</strong> and <strong>App Registrations</strong> in the left pane.\n3. Select <strong>New registration</strong>, then enter "Mattermost - your-company-name" as the <strong>Application Name</strong>. \n4. Under <strong>Redirect URI</strong>, select <strong>Web</strong>, and enter "your-mattermost-url/signup/office365/complete" as the <strong>Redirect URI</strong>. Select <strong>Register</strong>.\n5. Copy the Microsoft <strong>Application (client) ID</strong> value, and paste it below as the <strong>Client ID</strong> value. \n6. Copy the Microsoft <strong>Directory (tenant) ID</strong> value, and paste it below as the <strong>Directory (tenant) ID</strong> value. \n7. In Microsoft, create a new client secret. Copy the resulting client secret value, and paste it below as the <strong>Client Secret</strong> value. Select <strong>Save</strong>.'}),
help_text: defineMessage({id: 'admin.office365.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Microsoft account. \n2. In Microsoft, go to <strong>Applications</strong> and <strong>App Registrations</strong> in the left pane.\n3. Select <strong>New registration</strong>, then enter "Mattermost - your-company-name" as the <strong>Application Name</strong>. \n4. Under <strong>Redirect URI</strong>, select <strong>Web</strong>, and enter "your-mattermost-url/signup/office365/complete" as the <strong>Redirect URI</strong>. Select <strong>Register</strong>.\n5. Copy the Microsoft <strong>Application (client) ID</strong> value, and paste it below as the <strong>Client ID</strong> value. \n6. Copy the Microsoft <strong>Directory (tenant) ID</strong> value, and paste it below as the <strong>Directory (tenant) ID</strong> value. \n7. In Microsoft, create a new client secret. Copy the resulting client secret value, and paste it below as the <strong>Client Secret</strong> value. Select <strong>Save</strong>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
linkLogin: (msg: string) => (
@ -4825,7 +4813,7 @@ const AdminDefinition: AdminDefinitionType = {
{
value: Constants.GITLAB_SERVICE,
display_name: defineMessage({id: 'admin.openid.gitlab', defaultMessage: 'GitLab'}),
help_text: defineMessage({id: 'admin.gitlab.EnableMarkdownDesc', defaultMessage: '1. Log in to your GitLab account and go to Profile Settings -> Applications.\n2. Enter Redirect URIs "<loginUrlChunk>your-mattermost-url</loginUrlChunk>" (example: http://localhost:8065/login/gitlab/complete) and "<signupUrlChunk>your-mattermost-url</signupUrlChunk>".\n3. Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.\n4. Complete the Endpoint URLs below.'}),
help_text: defineMessage({id: 'admin.gitlab.EnableMarkdownDesc', defaultMessage: '1. Log in to your GitLab account and go to Profile Settings -> Applications.\n2. Enter Redirect URIs "<loginUrlChunk>your-mattermost-url</loginUrlChunk>" (example: http://localhost:8065/login/gitlab/complete) and "<signupUrlChunk>your-mattermost-url</signupUrlChunk>".\n3. Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.\n4. Complete the Endpoint URLs below.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
loginUrlChunk: (chunk: string) => `<${chunk}>/login/gitlab/complete`,
signupUrlChunk: (chunk: string) => `<${chunk}>/signup/gitlab/complete`,
@ -4835,7 +4823,7 @@ const AdminDefinition: AdminDefinitionType = {
{
value: Constants.GOOGLE_SERVICE,
display_name: defineMessage({id: 'admin.openid.google', defaultMessage: 'Google Apps'}),
help_text: defineMessage({id: 'admin.google.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Google account.\n2. Go to <linkConsole>https://console.developers.google.com]</linkConsole>, click <strong>Credentials</strong> in the left hand side.\n 3. Under the <strong>Credentials</strong> header, click <strong>Create credentials</strong>, choose <strong>OAuth client ID</strong> and select <strong>Web Application</strong>.\n 4. Enter "Mattermost - your-company-name" as the <strong>Name</strong>.\n 5. Under <strong>Authorized redirect URIs</strong> enter <strong>"your-mattermost-url/signup/google/complete"</strong> (example: http://localhost:8065/signup/google/complete). Click <strong>Create</strong>.\n 6. Paste the <strong>Client ID</strong> and <strong>Client Secret</strong> to the fields below, then click <strong>Save</strong>.\n 7. Go to the <linkAPI>Google People API</linkAPI> and click <strong>Enable</strong>.'}),
help_text: defineMessage({id: 'admin.google.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Google account.\n2. Go to <linkConsole>https://console.developers.google.com</linkConsole>, click <strong>Credentials</strong> in the left hand side.\n 3. Under the <strong>Credentials</strong> header, click <strong>Create credentials</strong>, choose <strong>OAuth client ID</strong> and select <strong>Web Application</strong>.\n 4. Enter "Mattermost - your-company-name" as the <strong>Name</strong>.\n 5. Under <strong>Authorized redirect URIs</strong> enter <strong>"your-mattermost-url/signup/google/complete"</strong> (example: http://localhost:8065/signup/google/complete). Click <strong>Create</strong>.\n 6. Paste the <strong>Client ID</strong> and <strong>Client Secret</strong> to the fields below, then click <strong>Save</strong>.\n 7. Go to the <linkAPI>Google People API</linkAPI> and click <strong>Enable</strong>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
linkLogin: (msg: string) => (
@ -4868,7 +4856,7 @@ const AdminDefinition: AdminDefinitionType = {
{
value: Constants.OFFICE365_SERVICE,
display_name: defineMessage({id: 'admin.openid.office365', defaultMessage: 'Entra ID'}),
help_text: defineMessage({id: 'admin.office365.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Microsoft account. \n2. In Microsoft, go to <strong>Applications</strong> and <strong>App Registrations</strong> in the left pane.\n3. Select <strong>New registration</strong>, then enter "Mattermost - your-company-name" as the <strong>Application Name</strong>. \n4. Under <strong>Redirect URI</strong>, select <strong>Web</strong>, and enter "your-mattermost-url/signup/office365/complete" as the <strong>Redirect URI</strong>. Select <strong>Register</strong>.\n5. Copy the Microsoft <strong>Application (client) ID</strong> value, and paste it below as the <strong>Client ID</strong> value. \n6. Copy the Microsoft <strong>Directory (tenant) ID</strong> value, and paste it below as the <strong>Directory (tenant) ID</strong> value. \n7. In Microsoft, create a new client secret. Copy the resulting client secret value, and paste it below as the <strong>Client Secret</strong> value. Select <strong>Save</strong>.'}),
help_text: defineMessage({id: 'admin.office365.EnableMarkdownDesc', defaultMessage: '1. <linkLogin>Log in</linkLogin> to your Microsoft account. \n2. In Microsoft, go to <strong>Applications</strong> and <strong>App Registrations</strong> in the left pane.\n3. Select <strong>New registration</strong>, then enter "Mattermost - your-company-name" as the <strong>Application Name</strong>. \n4. Under <strong>Redirect URI</strong>, select <strong>Web</strong>, and enter "your-mattermost-url/signup/office365/complete" as the <strong>Redirect URI</strong>. Select <strong>Register</strong>.\n5. Copy the Microsoft <strong>Application (client) ID</strong> value, and paste it below as the <strong>Client ID</strong> value. \n6. Copy the Microsoft <strong>Directory (tenant) ID</strong> value, and paste it below as the <strong>Directory (tenant) ID</strong> value. \n7. In Microsoft, create a new client secret. Copy the resulting client secret value, and paste it below as the <strong>Client Secret</strong> value. Select <strong>Save</strong>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
linkLogin: (msg: string) => (
@ -4935,7 +4923,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'GitLabSettings.Id',
label: defineMessage({id: 'admin.openid.clientIdTitle', defaultMessage: 'Client ID:'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.gitlab.clientIdExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'}),
isHidden: it.not(it.stateEquals('openidType', Constants.GITLAB_SERVICE)),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
@ -4944,8 +4932,8 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'GitLabSettings.Secret',
label: defineMessage({id: 'admin.openid.clientSecretTitle', defaultMessage: 'Client Secret:'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation'}),
placeholder: defineMessage({id: 'admin.gitlab.clientSecretExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx442pnqMxQY"'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.gitlab.clientSecretExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'}),
isHidden: it.not(it.stateEquals('openidType', Constants.GITLAB_SERVICE)),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
},
@ -4963,7 +4951,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'GoogleSettings.Id',
label: defineMessage({id: 'admin.openid.clientIdTitle', defaultMessage: 'Client ID:'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.google.clientIdExample', defaultMessage: 'E.g.: "7602141235235-url0fhs1mayfasbmop5qlfns8dh4.apps.googleusercontent.com"'}),
isHidden: it.not(it.stateEquals('openidType', Constants.GOOGLE_SERVICE)),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
@ -4972,7 +4960,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'GoogleSettings.Secret',
label: defineMessage({id: 'admin.openid.clientSecretTitle', defaultMessage: 'Client Secret:'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.google.clientSecretExample', defaultMessage: 'E.g.: "H8sz0Az-dDs2p15-7QzD231"'}),
isHidden: it.not(it.stateEquals('openidType', Constants.GOOGLE_SERVICE)),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
@ -5005,7 +4993,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'Office365Settings.Id',
label: defineMessage({id: 'admin.openid.clientIdTitle', defaultMessage: 'Client ID:'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.office365.clientIdExample', defaultMessage: 'E.g.: "adf3sfa2-ag3f-sn4n-ids0-sh1hdax192qq"'}),
isHidden: it.not(it.stateEquals('openidType', Constants.OFFICE365_SERVICE)),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
@ -5014,7 +5002,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'Office365Settings.Secret',
label: defineMessage({id: 'admin.openid.clientSecretTitle', defaultMessage: 'Client Secret:'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.office365.clientSecretExample', defaultMessage: 'E.g.: "shAieM47sNBfgl20f8ci294"'}),
isHidden: it.not(it.stateEquals('openidType', Constants.OFFICE365_SERVICE)),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
@ -5052,7 +5040,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'OpenIdSettings.Id',
label: defineMessage({id: 'admin.openid.clientIdTitle', defaultMessage: 'Client ID:'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation'}),
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.openid.clientIdExample', defaultMessage: 'E.g.: "adf3sfa2-ag3f-sn4n-ids0-sh1hdax192qq"'}),
isHidden: it.any(it.not(it.stateEquals('openidType', Constants.OPENID_SERVICE)), it.licensedForCloudStarter),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
@ -5061,7 +5049,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'text',
key: 'OpenIdSettings.Secret',
label: defineMessage({id: 'admin.openid.clientSecretTitle', defaultMessage: 'Client Secret:'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation'}),
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation.'}),
placeholder: defineMessage({id: 'admin.openid.clientSecretExample', defaultMessage: 'E.g.: "H8sz0Az-dDs2p15-7QzD231"'}),
isHidden: it.any(it.not(it.stateEquals('openidType', Constants.OPENID_SERVICE)), it.licensedForCloudStarter),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
@ -5179,7 +5167,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'GuestAccountsSettings.EnforceMultifactorAuthentication',
label: defineMessage({id: 'admin.guest_access.mfaTitle', defaultMessage: 'Enforce Multi-factor Authentication: '}),
help_text: defineMessage({id: 'admin.guest_access.mfaDescription', defaultMessage: 'When true, <link>multi-factor authentication</link> for guests is required for login. New guest users will be required to configure MFA on signup. Logged in guest users without MFA configured are redirected to the MFA setup page until configuration is complete.\n \nIf your system has guest users with login methods other than AD/LDAP and email, MFA must be enforced with the authentication provider outside of Mattermost.'}),
help_text: defineMessage({id: 'admin.guest_access.mfaDescription', defaultMessage: 'When true, <link>multi-factor authentication</link> for guests is required for login. New guest users will be required to configure MFA on signup. Logged in guest users without MFA configured are redirected to the MFA setup page until configuration is complete.\n \nIf your system has guest users with login methods other than AD/LDAP and email, MFA must be enforced with the authentication provider outside of Mattermost.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -5286,7 +5274,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableIncomingWebhooks',
label: defineMessage({id: 'admin.service.webhooksTitle', defaultMessage: 'Enable Incoming Webhooks: '}),
help_text: defineMessage({id: 'admin.service.webhooksDescription', defaultMessage: 'When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.service.webhooksDescription', defaultMessage: 'When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -5304,7 +5292,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableOutgoingWebhooks',
label: defineMessage({id: 'admin.service.outWebhooksTitle', defaultMessage: 'Enable Outgoing Webhooks: '}),
help_text: defineMessage({id: 'admin.service.outWebhooksDesc', defaultMessage: 'When true, outgoing webhooks will be allowed. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.service.outWebhooksDesc', defaultMessage: 'When true, outgoing webhooks will be allowed. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -5322,7 +5310,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableOutgoingOAuthConnections',
label: defineMessage({id: 'admin.service.outgoingOAuthConnectionsTitle', defaultMessage: 'Enable Outgoing OAuth Connections: '}),
help_text: defineMessage({id: 'admin.service.outgoingOAuthConnectionsDesc', defaultMessage: 'When true, outgoing webhooks and slash commands will use set up oauth connections to authenticate with third party services. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.service.outgoingOAuthConnectionsDesc', defaultMessage: 'When true, outgoing webhooks and slash commands will use set up oauth connections to authenticate with third party services. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (text: string) => (
<a href='https://mattermost.com/pl/outgoing-oauth-connections'>{text}</a>
@ -5335,7 +5323,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableCommands',
label: defineMessage({id: 'admin.service.cmdsTitle', defaultMessage: 'Enable Custom Slash Commands: '}),
help_text: defineMessage({id: 'admin.service.cmdsDesc', defaultMessage: 'When true, custom slash commands will be allowed. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.service.cmdsDesc', defaultMessage: 'When true, custom slash commands will be allowed. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -5353,7 +5341,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableOAuthServiceProvider',
label: defineMessage({id: 'admin.oauth.providerTitle', defaultMessage: 'Enable OAuth 2.0 Service Provider: '}),
help_text: defineMessage({id: 'admin.oauth.providerDescription', defaultMessage: 'When true, Mattermost can act as an OAuth 2.0 service provider allowing Mattermost to authorize API requests from external applications. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.oauth.providerDescription', defaultMessage: 'When true, Mattermost can act as an OAuth 2.0 service provider allowing Mattermost to authorize API requests from external applications. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -5384,7 +5372,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'number',
key: 'ServiceSettings.OutgoingIntegrationRequestsTimeout',
label: defineMessage({id: 'admin.service.integrationRequestTitle', defaultMessage: 'Integration request timeout: '}),
help_text: defineMessage({id: 'admin.service.integrationRequestDesc', defaultMessage: 'The number of seconds to wait for Integration requests. That includes <slashCommands>Slash Commands</slashCommands>, <outgoingWebhooks>Outgoing Webhooks</outgoingWebhooks>, <interactiveMessages>Interactive Messages</interactiveMessages> and <interactiveDialogs>Interactive Dialogs</interactiveDialogs>.'}),
help_text: defineMessage({id: 'admin.service.integrationRequestDesc', defaultMessage: 'The number of seconds to wait for Integration requests. That includes <slashCommands>Slash Commands</slashCommands>, <outgoingWebhooks>Outgoing Webhooks</outgoingWebhooks>, <interactiveMessages>Interactive Messages</interactiveMessages> and <interactiveDialogs>Interactive Dialogs</interactiveDialogs>.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
slashCommands: (msg: string) => (
<ExternalLink
@ -5439,8 +5427,8 @@ const AdminDefinition: AdminDefinitionType = {
{
type: 'bool',
key: 'ServiceSettings.EnableUserAccessTokens',
label: defineMessage({id: 'admin.service.userAccessTokensTitle', defaultMessage: 'Enable User Access Tokens: '}),
help_text: defineMessage({id: 'admin.service.userAccessTokensDescription', defaultMessage: 'When true, users can create <link>user access tokens</link> for integrations in <strong>Account Menu > Account Settings > Security</strong>. They can be used to authenticate against the API and give full access to the account.\n\n To manage who can create personal access tokens or to search users by token ID, go to the <strong>User Management > Users</strong> page.'}),
label: defineMessage({id: 'admin.service.userAccessTokensTitle', defaultMessage: 'Enable Personal Access Tokens:'}),
help_text: defineMessage({id: 'admin.service.userAccessTokensDescription', defaultMessage: 'When true, users can create <link>personal access tokens</link> for integrations in <strong>Profile > Security</strong>. They can be used to authenticate against the API and give full access to the account.\n\n To manage who can create personal access tokens or to search users by token ID, go to <strong>System Console > User Management > Users</strong>.'}), // eslint-disable-line formatjs/no-multiple-whitespaces, formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -5472,7 +5460,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.EnableBotAccountCreation',
label: defineMessage({id: 'admin.service.enableBotTitle', defaultMessage: 'Enable Bot Account Creation: '}),
help_text: defineMessage({id: 'admin.service.enableBotAccountCreation', defaultMessage: 'When true, System Admins can create bot accounts for integrations in <linkBots>Integrations > Bot Accounts</linkBots>. Bot accounts are similar to user accounts except they cannot be used to log in. See <linkDocumentation>documentation</linkDocumentation> to learn more.'}),
help_text: defineMessage({id: 'admin.service.enableBotAccountCreation', defaultMessage: 'When true, System Admins can create bot accounts for integrations in <linkBots>Integrations > Bot Accounts</linkBots>. Bot accounts are similar to user accounts except they cannot be used to log in. See <linkDocumentation>documentation</linkDocumentation> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
siteURL: getSiteURL(),
@ -5499,7 +5487,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.DisableBotsWhenOwnerIsDeactivated',
label: defineMessage({id: 'admin.service.disableBotOwnerDeactivatedTitle', defaultMessage: 'Disable bot accounts when owner is deactivated:'}),
help_text: defineMessage({id: 'admin.service.disableBotWhenOwnerIsDeactivated', defaultMessage: 'When a user is deactivated, disables all bot accounts managed by the user. To re-enable bot accounts, go to [Integrations > Bot Accounts]({siteURL}/_redirect/integrations/bots).'}),
help_text: defineMessage({id: 'admin.service.disableBotWhenOwnerIsDeactivated', defaultMessage: 'When a user is deactivated, disables all bot accounts managed by the user. To re-enable bot accounts, go to [Integrations > Bot Accounts]({siteURL}/_redirect/integrations/bots).'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: true,
help_text_values: {siteURL: getSiteURL()},
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.BOT_ACCOUNTS)),
@ -5732,7 +5720,7 @@ const AdminDefinition: AdminDefinitionType = {
settings: [
{
type: 'banner',
label: defineMessage({id: 'admin.compliance.newComplianceExportBanner', defaultMessage: 'This feature is replaced by a new <link>Compliance Export</link> feature, and will be removed in a future release. We recommend migrating to the new system.'}),
label: defineMessage({id: 'admin.compliance.newComplianceExportBanner', defaultMessage: 'This feature is replaced by a new <link>Compliance Export</link> feature, and will be removed in a future release. We recommend migrating to the new system.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
label_values: {
link: (msg: string) => (
<Link to='/admin_console/compliance/export'>
@ -5747,7 +5735,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ComplianceSettings.Enable',
label: defineMessage({id: 'admin.compliance.enableTitle', defaultMessage: 'Enable Compliance Reporting:'}),
help_text: defineMessage({id: 'admin.compliance.enableDesc', defaultMessage: 'When true, Mattermost allows compliance reporting from the <strong>Compliance and Auditing</strong> tab. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.compliance.enableDesc', defaultMessage: 'When true, Mattermost allows compliance reporting from the <strong>Compliance and Auditing</strong> tab. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink
@ -5804,7 +5792,7 @@ const AdminDefinition: AdminDefinitionType = {
settings: [
{
type: 'banner',
label: defineMessage({id: 'admin.rate.noteDescription', defaultMessage: 'Changing properties in this section will require a server restart before taking effect.'}),
label: defineMessage({id: 'admin.info_banner.restart_required.desc', defaultMessage: 'Changing properties in this section will require a server restart before taking effect.'}),
banner_type: 'info',
},
{
@ -5885,7 +5873,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'longtext',
key: 'ExperimentalAuditSettings.AdvancedLoggingJSON',
label: defineMessage({id: 'admin.log.AdvancedLoggingJSONTitle', defaultMessage: 'Advanced Logging:'}),
help_text: defineMessage({id: 'admin.log.AdvancedLoggingJSONDescription', defaultMessage: 'The JSON configuration for Advanced Audit Logging. Please see <link>documentation</link> to learn more about Advanced Logging and the JSON format it uses.'}),
help_text: defineMessage({id: 'admin.log.AdvancedAuditLoggingJSONDescription', defaultMessage: 'The JSON configuration for Advanced Audit Logging. Please see <link>documentation</link> to learn more about Advanced Logging and the JSON format it uses.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_markdown: false,
help_text_values: {
link: (msg: string) => (
@ -6020,7 +6008,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.ExperimentalEnableAuthenticationTransfer',
label: defineMessage({id: 'admin.experimental.experimentalEnableAuthenticationTransfer.title', defaultMessage: 'Allow Authentication Transfer:'}),
help_text: defineMessage({id: 'admin.experimental.experimentalEnableAuthenticationTransfer.desc', defaultMessage: 'When true, users can change their sign-in method to any that is enabled on the server, any via Account Settings or the APIs. When false, Users cannot change their sign-in method, regardless of which authentication options are enabled.'}),
help_text: defineMessage({id: 'admin.experimental.experimentalEnableAuthenticationTransfer.desc', defaultMessage: 'When true, users can change their sign-in method to any that is enabled on the server, either via their Profile or the APIs. When false, Users cannot change their sign-in method, regardless of which authentication options are enabled.'}),
help_text_markdown: false,
isHidden: it.any( // documented as E20 and higher, but only E10 in the code
it.not(it.licensed),
@ -6115,7 +6103,7 @@ const AdminDefinition: AdminDefinitionType = {
type: 'bool',
key: 'ServiceSettings.ExperimentalEnableHardenedMode',
label: defineMessage({id: 'admin.experimental.experimentalEnableHardenedMode.title', defaultMessage: 'Enable Hardened Mode:'}),
help_text: defineMessage({id: 'admin.experimental.experimentalEnableHardenedMode.desc', defaultMessage: 'Enables a hardened mode for Mattermost that makes user experience trade-offs in the interest of security. See <link>documentation</link> to learn more.'}),
help_text: defineMessage({id: 'admin.experimental.experimentalEnableHardenedMode.desc', defaultMessage: 'Enables a hardened mode for Mattermost that makes user experience trade-offs in the interest of security. See <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
help_text_values: {
link: (msg: string) => (
<ExternalLink

View file

@ -25,7 +25,7 @@ const SECTION_TLS = (
<tr>
<td>
<FormattedMessage
id='admin.connectionSecurityTls'
id='admin.connectionSecurityTls.title'
defaultMessage='TLS'
/>
</td>

View file

@ -113,6 +113,7 @@ export const getRestrictedIndicator = (displayBlocked = false, minimumPlanRequir
minimumPlanRequiredForFeature={minimumPlanRequiredForFeature}
tooltipMessageBlocked={defineMessage({
id: 'admin.sidebar.restricted_indicator.tooltip.message.blocked',
// eslint-disable-next-line formatjs/enforce-placeholders -- Placeholders provided in RestrictedIndicator
defaultMessage: 'This is {article} {minimumPlanRequiredForFeature} feature, available with an upgrade or free {trialLength}-day trial',
})}
/>

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable formatjs/enforce-placeholders -- Admin wizard uses help_text_values for placeholders, which ESLint cannot statically analyze */
import React from 'react';
import {FormattedMessage, defineMessage} from 'react-intl';

View file

@ -107,7 +107,7 @@ class AdminNavbarDropdown extends React.PureComponent<Props> {
<Menu.Group>
<Menu.ItemExternalLink
url={adminGuideLink}
text={formatMessage({id: 'admin.nav.administratorsGuide', defaultMessage: 'Administrator Guide'})}
text={formatMessage({id: 'admin.nav.administratorsGuide', defaultMessage: "Administrator's Guide"})}
/>
<Menu.ItemExternalLink
url={'https://forum.mattermost.com/t/how-to-use-the-troubleshooting-forum/150'}

View file

@ -1100,7 +1100,7 @@ exports[`components/AdminSidebar should match snapshot 1`] = `
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -2187,7 +2187,7 @@ exports[`components/AdminSidebar should match snapshot with license with enterpr
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -3261,7 +3261,7 @@ exports[`components/AdminSidebar should match snapshot with license with enterpr
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -4246,7 +4246,7 @@ exports[`components/AdminSidebar should match snapshot with license with profess
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -5220,7 +5220,7 @@ exports[`components/AdminSidebar should match snapshot with workspace optimizati
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -6233,7 +6233,7 @@ exports[`components/AdminSidebar should match snapshot, not prevent the console
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -7207,7 +7207,7 @@ exports[`components/AdminSidebar should match snapshot, render plugins without a
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -8082,7 +8082,7 @@ exports[`components/AdminSidebar should match snapshot, with license (with all f
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}
@ -9067,7 +9067,7 @@ exports[`components/AdminSidebar should match snapshot, with license (without an
name="experimental/feature_flags"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Features Flags"
defaultMessage="Feature Flags"
id="admin.feature_flags.title"
/>
}

View file

@ -61,7 +61,7 @@ export default function BillingHistoryModal(props: BillingHistoryModalProps) {
dialogClassName='a11y__modal'
>
<Modal.Header closeButton={true}>
<Modal.Title className='CloudBillingHistoryModal__title'>{formatMessage({id: 'cloud_billing_history_modal.title', defaultMessage: 'Unpaid Invoice(s)'})}</Modal.Title>
<Modal.Title className='CloudBillingHistoryModal__title'>{formatMessage({id: 'cloud_billing_history_modal.title', defaultMessage: 'Invoice(s)'})}</Modal.Title>
</Modal.Header>
<Modal.Body>
<BillingHistoryTable invoices={invoiceListToRecordList(props.invoices)}/>

View file

@ -70,7 +70,7 @@ const CloudTrialBanner = ({trialEndDate}: Props): JSX.Element | null => {
title={(
<FormattedMessage
id='admin.subscription.cloudTrialCard.upgradeTitle'
defaultMessage='Upgrade to one of our paid plans to keep your workspace '
defaultMessage='Upgrade to one of our paid plans to keep your workspace'
/>
)}
message={(

View file

@ -105,7 +105,7 @@ describe('Limits', () => {
const state = setupState(defaultOptions);
renderWithContext(<Limits/>, state);
screen.getByText('Message History');
screen.getByText('Message history');
screen.getByText(/of 10K/);
});
@ -113,7 +113,7 @@ describe('Limits', () => {
const state = setupState(defaultOptions);
renderWithContext(<Limits/>, state);
screen.getByText('File Storage');
screen.getByText('File storage');
screen.getByText(/of 1GB/);
});

View file

@ -68,7 +68,7 @@ const Limits = (): JSX.Element | null => {
name={(
<FormattedMessage
id='workspace_limits.file_storage'
defaultMessage='File Storage'
defaultMessage='File storage'
/>
)}
status={(
@ -92,13 +92,13 @@ const Limits = (): JSX.Element | null => {
name={
<FormattedMessage
id='workspace_limits.message_history'
defaultMessage='Message History'
defaultMessage='Message history'
/>
}
status={
<FormattedMessage
id='workspace_limits.message_history.usage'
defaultMessage='{actual} of {limit} ({percent}%)'
defaultMessage='{actual} of {limit} messages ({percent}%)'
values={{
actual: `${Math.floor(usage.messages.history / 1000)}K`,
limit: `${Math.floor(cloudLimits.messages.history / 1000)}K`,
@ -150,7 +150,7 @@ const Limits = (): JSX.Element | null => {
>
{intl.formatMessage({
id: 'admin.license.trialCard.contactSales',
defaultMessage: 'Contact sales',
defaultMessage: 'Contact Sales',
})}
</button>
</>

View file

@ -82,7 +82,7 @@ export const FreeTrial = ({daysLeftOnTrial}: FreeTrialProps) => {
{daysLeftOnTrial > TrialPeriodDays.TRIAL_1_DAY &&
<FormattedMessage
id='admin.billing.subscription.freeTrial.title'
defaultMessage={'You\'re currently on a free trial'}
defaultMessage="You're currently on a free trial"
/>
}
{(daysLeftOnTrial === TrialPeriodDays.TRIAL_1_DAY || daysLeftOnTrial === TrialPeriodDays.TRIAL_0_DAYS) &&
@ -96,21 +96,21 @@ export const FreeTrial = ({daysLeftOnTrial}: FreeTrialProps) => {
{daysLeftOnTrial > TrialPeriodDays.TRIAL_WARNING_THRESHOLD &&
<FormattedMessage
id='admin.billing.subscription.freeTrial.description'
defaultMessage='Your free trial will expire in {daysLeftOnTrial} days. Contact sales to continue after the trial ends.'
defaultMessage='Your free trial will expire in {daysLeftOnTrial} days. Contact Sales to continue after the trial ends.'
values={{daysLeftOnTrial}}
/>
}
{(daysLeftOnTrial > TrialPeriodDays.TRIAL_1_DAY && daysLeftOnTrial <= TrialPeriodDays.TRIAL_WARNING_THRESHOLD) &&
<FormattedMessage
id='admin.billing.subscription.freeTrial.lessThan3Days.description'
defaultMessage='Your free trial will end in {daysLeftOnTrial, number} {daysLeftOnTrial, plural, one {day} other {days}}. Contact sales to continue enjoying the benefits of Cloud Professional.'
defaultMessage='Your free trial will end in {daysLeftOnTrial, number} {daysLeftOnTrial, plural, one {day} other {days}}. Contact Sales to continue enjoying the benefits of Cloud Professional.'
values={{daysLeftOnTrial}}
/>
}
{(daysLeftOnTrial === TrialPeriodDays.TRIAL_1_DAY || daysLeftOnTrial === TrialPeriodDays.TRIAL_0_DAYS) &&
<FormattedMessage
id='admin.billing.subscription.freeTrial.lastDay.description'
defaultMessage='Your free trial has ended. Add payment information to continue enjoying the benefits of Cloud Professional.'
defaultMessage='Your free trial has ended. Contact Sales to continue enjoying the benefits of Cloud Professional.'
/>
}
</div>

View file

@ -32,7 +32,7 @@ const FeatureList = (props: FeatureListProps) => {
intl.formatMessage(
{
id: 'admin.billing.subscription.planDetails.features.limitedFileStorage',
defaultMessage: 'Limited to {limit} File Storage',
defaultMessage: 'Limited to {limit} file storage',
},
{

View file

@ -27,7 +27,7 @@ const AttributeHelpText = memo(({attributeKey, attributeName, attributeType}: At
<div className='help-text-container'>
{attributeKey === 'ldap' && (
<FormattedMessage
id='admin.customProfileAttribDesc'
id='admin.customProfileAttribDesc.ldap'
defaultMessage='(Optional) The attribute in the AD/LDAP server used to populate the {name} of users in Mattermost. When set, users cannot edit their {name}, since it is synchronized with the LDAP server. When left blank, users can set their {name} in <strong>Account Menu > Account Settings > Profile</strong>.'
values={{
name: attributeName,
@ -37,7 +37,7 @@ const AttributeHelpText = memo(({attributeKey, attributeName, attributeType}: At
)}
{attributeKey === 'saml' && (
<FormattedMessage
id='admin.customProfileAttribDesc'
id='admin.customProfileAttribDesc.saml'
defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the {name} of users in Mattermost.'
values={{
name: attributeName,
@ -49,10 +49,6 @@ const AttributeHelpText = memo(({attributeKey, attributeName, attributeType}: At
<FormattedMessage
id='admin.customProfileAttribWarning'
defaultMessage='(Warning) This attribute will be converted to a TEXT attribute, if the field is set to synchronize.'
values={{
name: attributeName,
strong: (msg) => <strong>{msg}</strong>,
}}
/>
</div>
)}
@ -132,7 +128,7 @@ const CustomProfileAttributes: React.FC<Props> = (props: Props): JSX.Element | n
subtitle={
<FormattedMessage
id='admin.customProfileAttributes.subtitle'
defaultMessage='You can add or remove custom profile attributes by going to the <link>system properties page</link>.'
defaultMessage='You can add or remove custom profile attributes by going to the <link>user attributes page</link>.'
values={{
link: (msg) => (
<Link

View file

@ -51,7 +51,7 @@ export const messages = defineMessages({
enableTermsOfServiceTitle: {id: 'admin.support.enableTermsOfServiceTitle', defaultMessage: 'Enable Custom Terms of Service'},
termsOfServiceTextTitle: {id: 'admin.support.termsOfServiceTextTitle', defaultMessage: 'Custom Terms of Service Text'},
termsOfServiceTextHelp: {id: 'admin.support.termsOfServiceTextHelp', defaultMessage: 'Text that will appear in your custom Terms of Service. Supports Markdown-formatted text.'},
termsOfServiceReAcceptanceTitle: {id: 'admin.support.termsOfServiceReAcceptanceTitle', defaultMessage: 'Re-Acceptance Period:'},
termsOfServiceReAcceptanceTitle: {id: 'admin.support.termsOfServiceReAcceptanceTitle', defaultMessage: 'Re-Acceptance Period'},
termsOfServiceReAcceptanceHelp: {id: 'admin.support.termsOfServiceReAcceptanceHelp', defaultMessage: 'The number of days before Terms of Service acceptance expires, and the terms must be re-accepted.'},
enableTermsOfServiceHelp: {id: 'admin.support.enableTermsOfServiceHelp', defaultMessage: 'When true, new users must accept the terms of service before accessing any Mattermost teams on desktop, web or mobile. Existing users must accept them after login or a page refresh. To update terms of service link displayed in account creation and login pages, go to <a>Site Configuration > Customization</a>'},
});

View file

@ -44,7 +44,7 @@ interface State extends BaseState {
}
const messages = defineMessages({
title: {id: 'admin.database.title', defaultMessage: 'Database Settings'},
title: {id: 'admin.database.title', defaultMessage: 'Database'},
recycleDescription: {id: 'admin.recycle.recycleDescription', defaultMessage: 'Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating "config.json" to the new desired configuration and using the {reloadConfiguration} feature to load the new settings while the server is running. The administrator should then use {featureName} feature to recycle the database connections based on the new settings.'},
featureName: {id: 'admin.recycle.recycleDescription.featureName', defaultMessage: 'Recycle Database Connections'},
reloadConfiguration: {id: 'admin.recycle.recycleDescription.reloadConfiguration', defaultMessage: 'Environment > Web Server > Reload Configuration from Disk'},
@ -187,6 +187,7 @@ export default class DatabaseSettings extends OLDAdminSettings<Props, State> {
showSuccessMessage={false}
errorMessage={defineMessage({
id: 'admin.recycle.reloadFail',
// eslint-disable-next-line formatjs/enforce-placeholders -- error provided by RequestButton
defaultMessage: 'Recycling unsuccessful: {error}',
})}
includeDetailedError={true}

View file

@ -65,18 +65,18 @@ export const messages = defineMessages({
bulkIndexingTitle: {id: 'admin.elasticsearch.bulkIndexingTitle', defaultMessage: 'Bulk Indexing:'},
help: {id: 'admin.elasticsearch.createJob.help', defaultMessage: 'All users, channels and posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete.'},
rebuildChannelsIndexTitle: {id: 'admin.elasticsearch.rebuildChannelsIndexTitle', defaultMessage: 'Rebuild Channels Index'},
rebuildChannelIndexHelpText: {id: 'admin.elasticsearch.rebuildChannelsIndex.helpText', defaultMessage: 'This purges the channels index and re-indexes all channels in the database, from oldest to newest. Channel autocomplete is available during indexing but search results may be incomplete until the indexing job is complete.\n<b>Note- Please ensure no other indexing job is in progress in the table above.</b>'},
rebuildChannelIndexHelpText: {id: 'admin.elasticsearch.rebuildChannelsIndex.helpText', defaultMessage: 'This purges the channels index and re-indexes all channels in the database, from oldest to newest. Channel autocomplete is available during indexing but search results may be incomplete until the indexing job is complete.\n\n<b>Note- Please ensure no other indexing job is in progress in the table above.</b>'}, // eslint-disable-line formatjs/no-multiple-whitespaces
rebuildChannelsIndexButtonText: {id: 'admin.elasticsearch.rebuildChannelsIndex.title', defaultMessage: 'Rebuild Channels Index'},
purgeIndexesHelpText: {id: 'admin.elasticsearch.purgeIndexesHelpText', defaultMessage: 'Purging will entirely remove the indexes on the Elasticsearch server. Search results may be incomplete until a bulk index of the existing database is rebuilt.'},
purgeIndexesButton: {id: 'admin.elasticsearch.purgeIndexesButton', defaultMessage: 'Purge Index'},
purgeIndexesButton: {id: 'admin.elasticsearch.purgeIndexesButton', defaultMessage: 'Purge Indexes'},
label: {id: 'admin.elasticsearch.purgeIndexesButton.label', defaultMessage: 'Purge Indexes:'},
enableSearchingTitle: {id: 'admin.elasticsearch.enableSearchingTitle', defaultMessage: 'Enable Elasticsearch for search queries:'},
enableSearchingDescription: {id: 'admin.elasticsearch.enableSearchingDescription', defaultMessage: 'Requires a successful connection to the Elasticsearch server. When true, Elasticsearch will be used for all search queries using the latest index. Search results may be incomplete until a bulk index of the existing post database is finished. When false, database search is used.'},
});
export const searchableStrings: Array<string|MessageDescriptor|[MessageDescriptor, {[key: string]: any}]> = [
[messages.connectionUrlDescription, {documentationLink: ''}],
[messages.enableIndexingDescription, {documentationLink: ''}],
[messages.connectionUrlDescription, {link: (msg: string) => msg}],
[messages.enableIndexingDescription, {link: (msg: string) => msg}],
messages.title,
messages.enableIndexingTitle,
messages.connectionUrlTitle,
@ -456,6 +456,7 @@ export default class ElasticsearchSettings extends OLDAdminSettings<Props, State
})}
errorMessage={defineMessage({
id: 'admin.elasticsearch.rebuildIndexSuccessfully.error',
// eslint-disable-next-line formatjs/enforce-placeholders -- error provided by RequestButton
defaultMessage: 'Failed to trigger channels index rebuild job: {error}',
})}
disabled={!this.state.canPurgeAndIndex || this.props.isDisabled!}
@ -472,6 +473,7 @@ export default class ElasticsearchSettings extends OLDAdminSettings<Props, State
})}
errorMessage={defineMessage({
id: 'admin.elasticsearch.purgeIndexesButton.error',
// eslint-disable-next-line formatjs/enforce-placeholders -- error provided by RequestButton
defaultMessage: 'Failed to purge indexes: {error}',
})}
disabled={this.props.isDisabled || !this.state.canPurgeAndIndex}

View file

@ -58,7 +58,7 @@ describe('components/feature_discovery', () => {
expect(screen.getByRole('button', {name: 'Start trial'})).toBeInTheDocument();
await userEvent.click(screen.getByRole('button', {name: 'Start trial'}));
await userEvent.click(screen.getByText('Mattermost Software and Services License Agreement'));
await userEvent.click(screen.getByText('Mattermost Software Evaluation Agreement'));
//cloud option
expect(screen.queryByRole('button', {name: 'Try free for 30 days'})).not.toBeInTheDocument();
@ -68,7 +68,7 @@ describe('components/feature_discovery', () => {
expect(featureLink).toBeInTheDocument();
expect(featureLink).toHaveAttribute('href', 'https://test.mattermost.com/secondary/?utm_source=mattermost&utm_medium=in-product&utm_content=feature_discovery&uid=&sid=&edition=team&server_version=');
expect(featureLink).toHaveTextContent('Learn more');
expect(screen.getByText('Mattermost Software and Services License Agreement')).toHaveAttribute('href', 'https://mattermost.com/pl/software-and-services-license-agreement?utm_source=mattermost&utm_medium=in-product&utm_content=feature_discovery&uid=&sid=&edition=team&server_version=');
expect(screen.getByText('Mattermost Software Evaluation Agreement')).toHaveAttribute('href', 'https://mattermost.com/pl/software-and-services-license-agreement?utm_source=mattermost&utm_medium=in-product&utm_content=feature_discovery&uid=&sid=&edition=team&server_version=');
expect(screen.getByText('Privacy Policy')).toHaveAttribute('href', AboutLinks.PRIVACY_POLICY + '?utm_source=mattermost&utm_medium=in-product&utm_content=feature_discovery&uid=&sid=&edition=team&server_version=');
expect(getPrevTrialLicense).toHaveBeenCalled();

View file

@ -167,7 +167,7 @@ export default class FeatureDiscovery extends React.PureComponent<Props, State>
<FormattedMessage
id='admin.feature_discovery.trial-request.accept-terms'
defaultMessage='By clicking <highlight>Start trial</highlight>, I agree to the <linkEvaluation>Mattermost Software and Services License Agreement</linkEvaluation>, <linkPrivacy>Privacy Policy</linkPrivacy> and receiving product emails.'
defaultMessage='By clicking <highlight>Start trial</highlight>, I agree to the <linkEvaluation>Mattermost Software Evaluation Agreement</linkEvaluation>, <linkPrivacy>Privacy Policy</linkPrivacy> and receiving product emails.'
values={{
highlight: (msg: React.ReactNode) => (
<strong>{msg}</strong>

View file

@ -31,6 +31,7 @@ const AutoTranslationFeatureDiscovery: React.FC = () => {
})}
copy={defineMessage({
id: 'admin.auto_translation_feature_discovery.copy',
// eslint-disable-next-line formatjs/enforce-placeholders -- values spread onto FormattedMessage in FeatureDiscovery
defaultMessage: 'Effortlessly collaborate across languages with auto-translation. Messages in shared channels are instantly translated based on each users language preference—no extra steps required.{br}<strong>Only available in Enterprise Advanced.</strong>',
values: {strong: (msg: string) => <strong>{msg}</strong>, br: <br/>},
})}

View file

@ -13,7 +13,7 @@ type Props = {
};
export const messages = defineMessages({
title: {id: 'admin.feature_flags.title', defaultMessage: 'Features Flags'},
title: {id: 'admin.feature_flags.title', defaultMessage: 'Feature Flags'},
});
const FeatureFlags: React.FC<Props> = (props: Props) => {
@ -40,7 +40,7 @@ const FeatureFlags: React.FC<Props> = (props: Props) => {
<div className='banner__content'>
<FormattedMessage
id='admin.feature_flags.introBanner'
defaultMessage={'Feature flag values displayed here show the status of features enabled on this server. The values here are used only for troubleshooting by the Mattermost support team.'}
defaultMessage={'The following feature flag values show the status of features enabled on this instance. The values are used for debugging purposes by the Mattermost support team.'}
/>
</div>
</div>

View file

@ -479,7 +479,7 @@ class GroupDetails extends React.PureComponent<Props, State> {
serverError = (
<FormattedMessage
id='admin.group_settings.group_detail.invalidOrReservedMentionNameError'
defaultMessage='Only letters (a-z), numbers(0-9), periods, dashes and underscores are allowed.'
defaultMessage='Only letters (a-z), numbers (0-9), periods, dashes and underscores are allowed.'
/>
);
} else if (

View file

@ -36,7 +36,7 @@ const GroupSettingsToggle = ({
title={
<FormattedMessage
id='admin.team_settings.team_details.groupDetailsToggle'
defaultMessage='Enable Group Mention'
defaultMessage='Enable Group Mentions'
/>
}
subTitle={

View file

@ -51,6 +51,7 @@ const GroupSettings = ({isDisabled}: Props) => {
<AdminPanel
id='ldap_groups'
title={defineMessage({id: 'admin.group_settings.ldapGroupsTitle', defaultMessage: 'AD/LDAP Groups'})}
// eslint-disable-next-line formatjs/enforce-placeholders -- placeholders via subtitleValues
subtitle={defineMessage({id: 'admin.group_settings.ldapGroupsDescription', defaultMessage: 'Connect AD/LDAP and create groups in Mattermost. To get started, configure group attributes on the <link>AD/LDAP</link> configuration page.'})}
subtitleValues={{
link: (msg: React.ReactNode) => (

View file

@ -27,7 +27,7 @@ const NoFiltersPanel = ({setShowAddModal}: NoFiltersPanelProps) => (
<div className='Subtitle'>
<FormattedMessage
id='admin.ip_filtering.any_ip_can_access_add_filter'
defaultMessage='Any IP can access your workspace. To limit access to selected IP Addresses, <add>Add a filter</add>'
defaultMessage='Any IP can access your workspace. To limit access to selected IP Addresses, <add>Add a filter</add>.'
values={{
add: (msg) => (
<div

View file

@ -342,7 +342,7 @@ const TrialBanner = ({
<p className='upgrade-legal-terms'>
<FormattedMessage
id='admin.license.upgrade-and-trial-request.accept-terms-initial-part'
defaultMessage='By selecting <strong>Upgrade Server And Start trial</strong>, I agree to the <linkEvaluation>Mattermost Software and Services License Agreement</linkEvaluation>, <linkPrivacy>Privacy Policy</linkPrivacy>, and receiving product emails. '
defaultMessage='By selecting <strong>Upgrade Server And Start trial</strong>, I agree to the <linkEvaluation>Mattermost Software Evaluation Agreement</linkEvaluation>, <linkPrivacy>Privacy Policy</linkPrivacy>, and receiving product emails. '
values={{
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
linkEvaluation: (msg: React.ReactNode) => (

View file

@ -112,6 +112,7 @@ export default function AutoTranslation(props: SystemConsoleCustomSettingsCompon
helpText={
<FormattedMessage
id='admin.site.localization.autoTranslationProviderDescription'
// eslint-disable-next-line formatjs/enforce-placeholders -- values provided by providerHelpTextValues
defaultMessage='<strong>NOTE:</strong> If using external translation services (e.g., cloud based),{br}message data may be processed outside of your environment.'
values={providerHelpTextValues}
/>

View file

@ -193,7 +193,7 @@ export class MessageExportSettings extends OLDAdminSettings<BaseProps & WrappedC
const exportFormatOptions = [
{value: exportFormats.EXPORT_FORMAT_ACTIANCE, text: this.props.intl.formatMessage({id: 'admin.complianceExport.exportFormat.actiance', defaultMessage: 'Actiance XML'})},
{value: exportFormats.EXPORT_FORMAT_CSV, text: this.props.intl.formatMessage({id: 'admin.complianceExport.exportFormat.csv', defaultMessage: 'CSV'})},
{value: exportFormats.EXPORT_FORMAT_GLOBALRELAY, text: this.props.intl.formatMessage({id: 'admin.complianceExport.exportFormat.globalrelay', defaultMessage: 'GlobalRelay EML'})},
{value: exportFormats.EXPORT_FORMAT_GLOBALRELAY, text: this.props.intl.formatMessage({id: 'admin.complianceExport.exportFormat.globalrelay', defaultMessage: 'Global Relay EML'})},
];
// if the export format is globalrelay, the user needs to set some additional parameters

View file

@ -76,7 +76,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_schemes
}
subtitle={
Object {
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>",
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>.",
"id": "admin.permissions.teamOverrideSchemesBannerText",
}
}
@ -203,7 +203,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_schemes
}
subtitle={
Object {
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>",
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>.",
"id": "admin.permissions.teamOverrideSchemesBannerText",
}
}
@ -309,7 +309,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_schemes
}
subtitle={
Object {
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>",
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>.",
"id": "admin.permissions.teamOverrideSchemesBannerText",
}
}
@ -454,7 +454,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_schemes
}
subtitle={
Object {
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>",
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>.",
"id": "admin.permissions.teamOverrideSchemesBannerText",
}
}
@ -598,7 +598,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_schemes
}
subtitle={
Object {
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>",
"defaultMessage": "Use when specific teams need permission exceptions to the <link>System Scheme</link>.",
"id": "admin.permissions.teamOverrideSchemesBannerText",
}
}

View file

@ -46,7 +46,7 @@ const PermissionRow = ({
if (permissionRolesStrings[id]) {
description = (
<FormattedMessage
id={permissionRolesStrings[id].description.id}
{...permissionRolesStrings[id].description}
values={additionalValues}
/>
);

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable formatjs/enforce-placeholders -- link placeholders provided by admin panel components */
import React from 'react';
import {type MessageDescriptor} from 'react-intl';
import {FormattedMessage, defineMessage, defineMessages} from 'react-intl';
@ -53,7 +55,7 @@ const messages = defineMessages({
systemSchemeBannerText: {id: 'admin.permissions.systemSchemeBannerText', defaultMessage: 'Set the default permissions inherited by all teams unless a <link>Team Override Scheme</link> is applied.'},
systemSchemeBannerButton: {id: 'admin.permissions.systemSchemeBannerButton', defaultMessage: 'Edit Scheme'},
teamOverrideSchemesTitle: {id: 'admin.permissions.teamOverrideSchemesTitle', defaultMessage: 'Team Override Schemes'},
teamOverrideSchemesBannerText: {id: 'admin.permissions.teamOverrideSchemesBannerText', defaultMessage: 'Use when specific teams need permission exceptions to the <link>System Scheme</link>'},
teamOverrideSchemesBannerText: {id: 'admin.permissions.teamOverrideSchemesBannerText', defaultMessage: 'Use when specific teams need permission exceptions to the <link>System Scheme</link>.'},
teamOverrideSchemesNewButton: {id: 'admin.permissions.teamOverrideSchemesNewButton', defaultMessage: 'New Team Override Scheme'},
});

View file

@ -175,7 +175,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_system_
>
<span>
<MemoizedFormattedMessage
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link>is applied in specific teams."
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link> is applied in specific teams."
id="admin.permissions.systemScheme.introBanner"
values={
Object {
@ -523,7 +523,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_system_
>
<span>
<MemoizedFormattedMessage
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link>is applied in specific teams."
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link> is applied in specific teams."
id="admin.permissions.systemScheme.introBanner"
values={
Object {
@ -872,7 +872,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_system_
>
<span>
<MemoizedFormattedMessage
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link>is applied in specific teams."
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link> is applied in specific teams."
id="admin.permissions.systemScheme.introBanner"
values={
Object {
@ -1221,7 +1221,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_system_
>
<span>
<MemoizedFormattedMessage
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link>is applied in specific teams."
defaultMessage="Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link> is applied in specific teams."
id="admin.permissions.systemScheme.introBanner"
values={
Object {

View file

@ -405,7 +405,7 @@ class PermissionSystemSchemeSettings extends React.PureComponent<Props, State> {
<span>
<FormattedMessage
id='admin.permissions.systemScheme.introBanner'
defaultMessage='Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link>is applied in specific teams.'
defaultMessage='Configure the default permissions for Team Admins, Channel Admins and other members. This scheme is inherited by all teams unless a <link>Team Override Scheme</link> is applied in specific teams.'
values={{
link: (msg: React.ReactNode) => (
<ExternalLink

View file

@ -11,7 +11,7 @@ exports[`components/PluginManagement should match snapshot 1`] = `
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -270,7 +270,7 @@ exports[`components/PluginManagement should match snapshot when \`Enable Marketp
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -529,7 +529,7 @@ exports[`components/PluginManagement should match snapshot when \`Enable Plugins
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -605,7 +605,7 @@ exports[`components/PluginManagement should match snapshot when \`Enable Remote
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -864,7 +864,7 @@ exports[`components/PluginManagement should match snapshot when \`Require Signat
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -1125,7 +1125,7 @@ exports[`components/PluginManagement should match snapshot, No installed plugins
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -1388,7 +1388,7 @@ exports[`components/PluginManagement should match snapshot, allow insecure URL e
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -1647,7 +1647,7 @@ exports[`components/PluginManagement should match snapshot, disabled 1`] = `
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -1730,11 +1730,12 @@ exports[`components/PluginManagement should match snapshot, disabled 1`] = `
<SettingSet
helpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="To enable plugins, set **Enable Plugins** to true. See <link>documentation</link> to learn more."
defaultMessage="To enable plugins, set <strong>Enable Plugins</strong> to true. See <link>documentation</link> to learn more."
id="admin.plugin.uploadAndPluginDisabledDesc"
values={
Object {
"link": [Function],
"strong": [Function],
}
}
/>
@ -1890,7 +1891,7 @@ exports[`components/PluginManagement should match snapshot, text entered into th
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -2149,7 +2150,7 @@ exports[`components/PluginManagement should match snapshot, upload disabled 1`]
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -2415,7 +2416,7 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -2737,7 +2738,7 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -3027,7 +3028,7 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -3317,7 +3318,7 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>
@ -3607,7 +3608,7 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
>
<AdminHeader>
<MemoizedFormattedMessage
defaultMessage="Management"
defaultMessage="Plugin Management"
id="admin.plugin.management.title"
/>
</AdminHeader>

View file

@ -189,7 +189,7 @@ type PluginItemProps = {
};
const messages = defineMessages({
title: {id: 'admin.plugin.management.title', defaultMessage: 'Management'},
title: {id: 'admin.plugin.management.title', defaultMessage: 'Plugin Management'},
enable: {id: 'admin.plugins.settings.enable', defaultMessage: 'Enable Plugins: '},
enableDesc: {id: 'admin.plugins.settings.enableDesc', defaultMessage: 'When true, enables plugins on your Mattermost server. Use plugins to integrate with third-party systems, extend functionality, or customize the user interface of your Mattermost server. See <link>documentation</link> to learn more.'},
uploadTitle: {id: 'admin.plugin.uploadTitle', defaultMessage: 'Upload Plugin: '},
@ -1073,7 +1073,7 @@ class PluginManagement extends OLDAdminSettings<Props, State> {
uploadHelpText = (
<FormattedMessage
id='admin.plugin.uploadAndPluginDisabledDesc'
defaultMessage='To enable plugins, set **Enable Plugins** to true. See <link>documentation</link> to learn more.'
defaultMessage='To enable plugins, set <strong>Enable Plugins</strong> to true. See <link>documentation</link> to learn more.'
values={{
link: (msg: React.ReactNode) => (
<ExternalLink
@ -1083,6 +1083,7 @@ class PluginManagement extends OLDAdminSettings<Props, State> {
{msg}
</ExternalLink>
),
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
}}
/>
);

View file

@ -389,14 +389,14 @@ class PushSettings extends OLDAdminSettings<Props, State> {
label={
<FormattedMessage
id='admin.team.maxNotificationsPerChannelTitle'
defaultMessage='Max Notifications Per Channel:'
defaultMessage='Large Channel Notification Threshold:'
/>
}
placeholder={defineMessage({id: 'admin.team.maxNotificationsPerChannelExample', defaultMessage: 'E.g.: "1000"'})}
helpText={
<FormattedMessage
id='admin.team.maxNotificationsPerChannelDescription'
defaultMessage='Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.'
defaultMessage='The maximum number of users that can be mentioned using @here, @all, or @channel. Above this limit, mentions and typing indicators are disabled, and users will see a system message if they try to use them.'
/>
}
value={this.state.maxNotificationsPerChannel}

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable formatjs/enforce-placeholders -- Admin request button uses runtime injection for error placeholder */
import React from 'react';
import type {MessageDescriptor} from 'react-intl';
import {FormattedMessage, defineMessage} from 'react-intl';

View file

@ -39,14 +39,14 @@ const messages = defineMessages({
extendSessionLengthActivity_label: {id: 'admin.service.extendSessionLengthActivity.label', defaultMessage: 'Extend session length with activity: '},
extendSessionLengthActivity_helpText: {id: 'admin.service.extendSessionLengthActivity.helpText', defaultMessage: 'When true, sessions will be automatically extended when the user is active in their Mattermost client. Users sessions will only expire if they are not active in their Mattermost client for the entire duration of the session lengths defined in the fields below. When false, sessions will not extend with activity in Mattermost. User sessions will immediately expire at the end of the session length or idle timeouts defined below. '},
terminateSessionsOnPasswordChange_label: {id: 'admin.service.terminateSessionsOnPasswordChange.label', defaultMessage: 'Terminate Sessions on Password Change: '},
terminateSessionsOnPasswordChange_helpText: {id: 'admin.service.terminateSessionsOnPasswordChange.helpText', defaultMessage: 'When true, all sessions of a user will expire if their password is changed by themselves or an administrator.'},
terminateSessionsOnPasswordChange_helpText: {id: 'admin.service.terminateSessionsOnPasswordChange.helpText', defaultMessage: 'When true, all sessions of a user will expire if their password is changed by themselves or an administrator. If password change is initiated by user, their current session is not terminated.'},
webSessionHours: {id: 'admin.service.webSessionHours', defaultMessage: 'Session Length AD/LDAP and Email (hours):'},
mobileSessionHours: {id: 'admin.service.mobileSessionHours', defaultMessage: 'Session Length Mobile (hours):'},
ssoSessionHours: {id: 'admin.service.ssoSessionHours', defaultMessage: 'Session Length SSO (hours):'},
sessionCache: {id: 'admin.service.sessionCache', defaultMessage: 'Session Cache (minutes):'},
sessionCacheDesc: {id: 'admin.service.sessionCacheDesc', defaultMessage: 'The number of minutes to cache a session in memory:'},
sessionHoursEx: {id: 'admin.service.sessionHoursEx', defaultMessage: 'E.g.: "720"'},
sessionIdleTimeoutDesc: {id: 'admin.service.sessionIdleTimeoutDesc', defaultMessage: "The number of minutes from the last time a user was active on the system to the expiry of the user's session. Once expired, the user will need to log in to continue. Minimum is 5 minutes, and 0 is unlimited. Applies to the desktop app and browsers. For mobile apps, use an EMM provider to lock the app when not in use. In High Availability mode, enable IP hash load balancing for reliable timeout measurement."},
sessionIdleTimeoutDesc: {id: 'admin.service.sessionIdleTimeoutDesc', defaultMessage: "The number of minutes from the last time a user was active on the system to the expiry of the user's session. Once expired, the user will need to log in to continue. Minimum is 5 minutes, and 0 is unlimited.\n \nApplies to the desktop app and browsers. For mobile apps, use an EMM provider to lock the app when not in use. In High Availability mode, enable IP hash load balancing for reliable timeout measurement."}, // eslint-disable-line formatjs/no-multiple-whitespaces
});
export const searchableStrings = [

View file

@ -180,7 +180,7 @@ exports[`admin_console/add_users_to_role_modal search should not include bot use
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}
@ -332,7 +332,7 @@ exports[`admin_console/add_users_to_role_modal should exclude user 1`] = `
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}
@ -528,7 +528,7 @@ exports[`admin_console/add_users_to_role_modal should have single passed value 1
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}
@ -765,7 +765,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 1`
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}
@ -1002,7 +1002,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 2`
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}
@ -1198,7 +1198,7 @@ exports[`admin_console/add_users_to_role_modal should not include bot user 1`] =
perPage={50}
placeholderText={
Object {
"defaultMessage": "Search and add members",
"defaultMessage": "Search for people",
"id": "multiselect.placeholder",
}
}

View file

@ -271,7 +271,7 @@ export class AddUsersToRoleModal extends React.PureComponent<Props, State> {
buttonSubmitLoadingText={buttonSubmitLoadingText}
saving={this.state.saving}
loading={this.state.loading}
placeholderText={defineMessage({id: 'multiselect.placeholder', defaultMessage: 'Search and add members'})}
placeholderText={defineMessage({id: 'multiselect.placeholder', defaultMessage: 'Search for people'})}
/>
</Modal.Body>
</Modal>

View file

@ -868,7 +868,7 @@ export class SystemUserDetail extends PureComponent<Props, State> {
<WithTooltip
title={defineMessage({
id: 'generic.enterprise_feature',
defaultMessage: 'Enterprise feature',
defaultMessage: 'Enterprise Feature',
})}
hint={defineMessage({
id: 'admin.user_item.manageSettings.disabled_tooltip',
@ -976,7 +976,7 @@ export class SystemUserDetail extends PureComponent<Props, State> {
message={
<div>
<FormattedMessage
id='deactivate_member_modal.desc'
id='deactivate_member_modal.desc_with_confirmation'
defaultMessage='This action deactivates {username}. They will be logged out and not have access to any teams or channels on this system. Are you sure you want to deactivate {username}?'
values={{
username: this.state.user?.username ?? '',

View file

@ -30,7 +30,7 @@ export default class TeamRow extends React.PureComponent<Props> {
return (
<FormattedMessage
id={'admin.systemUserDetail.teamList.teamType.groupSync'}
defaultMessage={'Group sync'}
defaultMessage={'Group Sync'}
/>
);
}
@ -38,14 +38,14 @@ export default class TeamRow extends React.PureComponent<Props> {
return (
<FormattedMessage
id={'admin.systemUserDetail.teamList.teamType.anyoneCanJoin'}
defaultMessage={'Anyone can join'}
defaultMessage={'Anyone Can Join'}
/>
);
}
return (
<FormattedMessage
id={'admin.systemUserDetail.teamList.teamType.inviteOnly'}
defaultMessage={'Invite only'}
defaultMessage={'Invite Only'}
/>
);
};

View file

@ -52,7 +52,7 @@ export default function DeactivateMemberModal({user, onExited, onSuccess, onErro
const defaultMessage = (
<FormattedMessage
id='deactivate_member_modal.desc'
defaultMessage='This action deactivates {username}. They will be logged out and not have access to any teams or channels on this system.\n'
defaultMessage={'This action deactivates {username}. They will be logged out and not have access to any teams or channels on this system.\n'}
values={{
username: user.username,
}}
@ -107,7 +107,6 @@ export default function DeactivateMemberModal({user, onExited, onSuccess, onErro
id='deactivate_member_modal.desc.for_users_with_bot_accounts3'
defaultMessage='Bot accounts they manage will be disabled along with their integrations. To enable them again, go to <linkBots>Integrations > Bot Accounts</linkBots>. <linkDocumentation>Learn more about bot accounts</linkDocumentation>.'
values={{
siteURL,
linkBots: (msg: React.ReactNode) => (
<a
href={`${siteURL}/_redirect/integrations/bots`}

View file

@ -328,7 +328,7 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
return managedByLDAP ? {
trailingElements: formatMessage({
id: 'admin.system_users.list.actions.menu.managedByLdap',
defaultMessage: 'Managed by LDAP',
defaultMessage: '(Managed by LDAP)',
}),
} : {};
};
@ -524,7 +524,7 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
labels={
<FormattedMessage
id='admin.system_users.list.actions.menu.removeSessions'
defaultMessage='Remove sessions'
defaultMessage='Revoke sessions'
/>
}
onClick={handleRemoveSessionsClick}

View file

@ -164,7 +164,7 @@ export function getDefaultSelectedTeam(teamId: Team['id'] | string, label?: stri
label: (
<FormattedMessage
id='admin.system_users.filters.team.allTeams'
defaultMessage='All Teams'
defaultMessage='All teams'
/>
),
};
@ -174,7 +174,7 @@ export function getDefaultSelectedTeam(teamId: Team['id'] | string, label?: stri
label: (
<FormattedMessage
id='admin.system_users.filters.team.noTeams'
defaultMessage='No Teams'
defaultMessage='No teams'
/>
),
};

View file

@ -135,7 +135,7 @@ export const ChannelAccessControl: React.FC<Props> = (props: Props): JSX.Element
return (
<AdminPanelWithButton
id='channel_access_control_with_policy'
title={defineMessage({id: 'admin.channel_settings.channel_detail.access_control_policy_title', defaultMessage: 'Access Policy'})}
title={defineMessage({id: 'admin.channel_settings.channel_detail.access_control_policy_title', defaultMessage: 'Access policy'})}
subtitle={defineMessage({id: 'admin.channel_settings.channel_detail.policy_following', defaultMessage: 'This channel is currently using the following access policy.'})}
buttonText={defineMessage({id: 'admin.channel_settings.channel_detail.remove_policy', defaultMessage: 'Remove all'})}
onButtonClick={() => {

View file

@ -65,7 +65,7 @@ export const UsersWillBeRemovedError = ({users, total, scope, scopeId}: UsersWil
let error = (
<FormattedMessage
id='admin.team_channel_settings.users_will_be_removed'
defaultMessage='{amount, number} {amount, plural, one {User} other {Users}} will be removed from this team. They are not in groups linked to this team.'
defaultMessage='{amount, number} {amount, plural, one {user} other {users}} will be removed from this team. They are not in groups linked to this team.'
values={{amount: total}}
/>
);

View file

@ -132,6 +132,7 @@ const PrewrittenChips = ({channelId, currentUserId, prefillMessage}: Props) => {
event: 'prefilled_message_selected_dm_hey',
message: defineMessage({
id: 'create_post.prewritten.tip.dm_hey_message',
// eslint-disable-next-line formatjs/enforce-placeholders -- username provided when message is formatted
defaultMessage: ':wave: Hey @{username}',
}),
display: defineMessage({

View file

@ -79,7 +79,7 @@ function PriorityLabels({
<WithTooltip
title={intl.formatMessage({
id: 'post_priority.remove',
defaultMessage: 'Remove {priority}',
defaultMessage: 'Remove {priority} label',
}, {priority})}
>
<button
@ -90,7 +90,7 @@ function PriorityLabels({
<span className='sr-only'>
<FormattedMessage
id={'post_priority.remove'}
defaultMessage={'Remove {priority}'}
defaultMessage={'Remove {priority} label'}
values={{priority}}
/>
</span>

View file

@ -118,7 +118,7 @@ export default function RewriteMenu({
placeholderText = prompt;
} else if (draftMessage.trim()) {
placeholderText = formatMessage({
id: 'texteditor.rewrite.rewriting',
id: 'texteditor.rewrite.placeholder.rewriting',
defaultMessage: 'Rewriting...',
});
}

View file

@ -95,7 +95,7 @@ export default function ScheduledPostCustomTimeModal({channelId, onExited, onCon
confirmButtonText={
<FormattedMessage
id='schedule_post.custom_time_modal.confirm_button_text'
defaultMessage='Confirm'
defaultMessage='Schedule'
/>
}
cancelButtonText={

View file

@ -70,7 +70,7 @@ function CoreMenuOptions({handleOnSelect, channelId}: Props) {
const teammateTimeDisplay = (
<FormattedMessage
id='create_post_button.option.schedule_message.options.teammate_user_hour'
defaultMessage="{time} {user}'s time"
defaultMessage='{time} {user}s time'
values={{
user: (
<span className='userDisplayName'>

View file

@ -97,7 +97,7 @@ export function SendPostOptions({disabled, onSelect, channelId}: Props) {
labels={
<FormattedMessage
id='create_post_button.option.schedule_message.options.header'
defaultMessage='Scheduled message'
defaultMessage='Schedule message'
/>
}
/>

View file

@ -100,15 +100,13 @@ describe('components/analytics/system_analytics/system_analytics.tsx', () => {
});
test('plugins data', async () => {
const totalPlaybooksID = 'total_playbooks';
const totalPlaybookRunsID = 'total_playbook_runs';
const playbooksStats = {
playbook_count: {
id: 'total_playbooks',
icon: 'fa-book',
name:
<FormattedMessage
id={totalPlaybooksID}
id='total_playbooks'
defaultMessage='Total Playbooks'
/>,
value: 45,
@ -118,7 +116,7 @@ describe('components/analytics/system_analytics/system_analytics.tsx', () => {
icon: 'fa-list-alt',
name:
<FormattedMessage
id={totalPlaybookRunsID}
id='total_playbook_runs'
defaultMessage='Total Runs'
/>,
value: 45,

View file

@ -220,7 +220,7 @@ const ConfigurationAnnouncementBar = (props: Props) => {
src={warningIcon}
/>
<FormattedMessage
id={AnnouncementBarMessages.LICENSE_PAST_GRACE}
id='announcement_bar.error.past_grace'
defaultMessage='{licenseSku} license is expired and some features may be disabled. Please contact your System Administrator for details.'
values={{
licenseSku: getSkuDisplayName(props.license.SkuShortName, props.license.IsGovSku === 'true'),
@ -237,8 +237,8 @@ const ConfigurationAnnouncementBar = (props: Props) => {
props.config?.EnablePreviewModeBanner === 'true'
) {
const emailMessage = formatMessage({
id: AnnouncementBarMessages.PREVIEW_MODE,
defaultMessage: 'Preview Mode: Email notifications have not been configured',
id: 'announcement_bar.error.preview_mode',
defaultMessage: 'Preview Mode: Email notifications have not been configured.',
});
return (

View file

@ -88,6 +88,6 @@ export default function PaymentAnnouncementBar() {
const messages = defineMessages({
updatePaymentInfo: {
id: 'admin.billing.subscription.updatePaymentInfo',
defaultMessage: 'Update payment info',
defaultMessage: 'Update Payment Information',
},
});

View file

@ -56,7 +56,7 @@ class AppsFormContainer extends React.PureComponent<Props, State> {
return {error: makeCallErrorResponse(makeErrorMsg(errMsg))};
}
if (!form.submit) {
const errMsg = this.props.intl.formatMessage({id: 'apps.error.form.no_submit', defaultMessage: '`submit` is not defined'});
const errMsg = this.props.intl.formatMessage({id: 'apps.error.form.no_submit', defaultMessage: '`submit` is not defined.'});
return {error: makeCallErrorResponse(makeErrorMsg(errMsg))};
}
if (!this.props.appContext) {
@ -170,7 +170,7 @@ class AppsFormContainer extends React.PureComponent<Props, State> {
const makeErrorMsg = (message: string) => intl.formatMessage(
{
id: 'apps.error.form.refresh',
defaultMessage: 'There has been an error fetching the select fields. Contact the app developer. Details: {details}',
defaultMessage: 'There has been an error updating the modal. Contact the app developer. Details: {details}',
},
{details: message},
);

View file

@ -94,7 +94,7 @@ exports[`components/ChannelHeader should match snapshot with last active display
className="last-active__text"
>
<MemoizedFormattedMessage
defaultMessage="Active {timestamp}"
defaultMessage="Last online {timestamp}"
id="channel_header.lastActive"
values={
Object {
@ -1709,7 +1709,7 @@ exports[`components/ChannelHeader should render properly when custom status is e
className="last-active__text"
>
<MemoizedFormattedMessage
defaultMessage="Active {timestamp}"
defaultMessage="Last online {timestamp}"
id="channel_header.lastActive"
values={
Object {
@ -1963,7 +1963,7 @@ exports[`components/ChannelHeader should render properly when custom status is s
className="last-active__text"
>
<MemoizedFormattedMessage
defaultMessage="Active {timestamp}"
defaultMessage="Last online {timestamp}"
id="channel_header.lastActive"
values={
Object {

View file

@ -202,7 +202,7 @@ class ChannelHeader extends React.PureComponent<Props> {
<span className='last-active__text'>
<FormattedMessage
id='channel_header.lastActive'
defaultMessage='Active {timestamp}'
defaultMessage='Last online {timestamp}'
values={{
timestamp: (
<Timestamp

View file

@ -52,7 +52,7 @@ const ChannelInfoButton = ({channel}: Props) => {
let tooltip;
if (buttonActive) {
tooltip = intl.formatMessage({id: 'channel_header.closeChannelInfo', defaultMessage: 'Close info'});
tooltip = intl.formatMessage({id: 'channel_header.closeChannelInfo', defaultMessage: 'Close Info'});
} else {
tooltip = intl.formatMessage({id: 'channel_header.openChannelInfo', defaultMessage: 'View Info'});
}

View file

@ -96,7 +96,7 @@ const TeamWarningBanner = (props: Props) => {
formatMessage(
{
id: 'channel_invite.invite_team_members.guests.message',
defaultMessage: '{count, plural, =1 {{firstUser} is a guest user and needs} other {{users} are guest users and need}} to first be invited to the team before you can add them to the channel. Once they\'ve joined the team, you can add them to this channel.',
defaultMessage: "{count, plural, =1 {{firstUser} is a guest user and needs} other {{users} are guest users and need}} to first be invited to the team before you can add them to the channel. Once they've joined the team, you can add them to this channel.",
},
{
count: guests.length,
@ -107,7 +107,6 @@ const TeamWarningBanner = (props: Props) => {
mentionName={firstName}
/>
),
team: (<strong>{team?.display_name}</strong>),
},
)
);

View file

@ -138,7 +138,7 @@ export default function ChannelNotificationsModal(props: Props) {
}
description={formatMessage({
id: 'channel_notifications.muteChannelDesc',
defaultMessage: 'Turns off notifications for this channel. You\'ll still see badges if you\'re mentioned.',
defaultMessage: 'Turns off notifications for this channel. Youll still see badges if youre mentioned.',
})}
inputFieldValue={settings.mark_unread === 'mention'}
inputFieldData={utils.MuteChannelInputFieldData}
@ -193,7 +193,7 @@ export default function ChannelNotificationsModal(props: Props) {
inputFieldTitle={
<FormattedMessage
id='channel_notifications.checkbox.threadsReplyTitle'
defaultMessage="Notify me about replies to threads I\'m following"
defaultMessage='Notify me about replies to threads Im following'
/>
}
handleChange={(e) => handleChange({desktop_threads: e ? 'all' : 'mention'})}
@ -262,7 +262,7 @@ export default function ChannelNotificationsModal(props: Props) {
inputFieldTitle={
<FormattedMessage
id='channel_notifications.checkbox.threadsReplyTitle'
defaultMessage="Notify me about replies to threads I\'m following"
defaultMessage='Notify me about replies to threads Im following'
/>
}
inputFieldValue={settings.push_threads === 'all'}

Some files were not shown because too many files have changed in this diff Show more