From 0cf0eca0689c8154a05d91ede0f261f41a39e56a Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:32:41 -0500 Subject: [PATCH] Add localization strings batch 4 (#2420) * Add localization strings batch 4 * run prettier * Update test snap * Remove duplicates --- src/Explorer/Controls/Dialog.tsx | 4 +- .../Controls/Settings/SettingsComponent.tsx | 28 ++- .../Controls/Settings/SettingsRenderUtils.tsx | 162 +++++++------- .../ComputedPropertiesComponent.tsx | 8 +- .../ConflictResolutionComponent.tsx | 15 +- .../ContainerPolicyComponent.tsx | 8 +- .../DataMaskingComponent.tsx | 6 +- .../GlobalSecondaryIndexComponent.tsx | 8 +- .../GlobalSecondaryIndexSourceComponent.tsx | 6 +- .../GlobalSecondaryIndexTargetComponent.tsx | 12 +- .../IndexingPolicyComponent.tsx | 4 +- .../IndexingPolicyRefreshComponent.tsx | 8 +- ...dexingPolicyRefreshComponent.test.tsx.snap | 2 +- .../AddMongoIndexComponent.tsx | 10 +- .../MongoIndexingPolicyComponent.tsx | 56 ++++- .../PartitionKeyComponent.tsx | 51 +++-- .../SettingsSubComponents/ScaleComponent.tsx | 15 +- .../SubSettingsComponent.tsx | 81 ++++--- .../ThroughputBucketsComponent.tsx | 14 +- .../ThroughputInputAutoPilotV3Component.tsx | 69 +++--- ...putInputAutoPilotV3Component.test.tsx.snap | 28 +-- .../ComputedPropertiesComponent.test.tsx.snap | 3 +- .../PartitionKeyComponent.test.tsx.snap | 9 +- .../SubSettingsComponent.test.tsx.snap | 20 +- .../Controls/Settings/SettingsUtils.tsx | 61 ++--- .../SettingsRenderUtils.test.tsx.snap | 10 +- src/Localization/en/Resources.json | 208 ++++++++++++++++++ 27 files changed, 629 insertions(+), 277 deletions(-) diff --git a/src/Explorer/Controls/Dialog.tsx b/src/Explorer/Controls/Dialog.tsx index d02eb1120..99718d291 100644 --- a/src/Explorer/Controls/Dialog.tsx +++ b/src/Explorer/Controls/Dialog.tsx @@ -17,6 +17,8 @@ import { } from "@fluentui/react"; import React, { FC, useEffect } from "react"; import create, { UseStore } from "zustand"; +import { Keys } from "../../Localization/Keys.generated"; +import { t } from "../../Localization/t"; export interface DialogState { visible: boolean; @@ -88,7 +90,7 @@ export const useDialog: UseStore = create((set, get) => ({ isModal: true, title, subText, - primaryButtonText: "Close", + primaryButtonText: t(Keys.common.close), secondaryButtonText: undefined, onPrimaryButtonClick: () => { get().closeDialog(); diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 9f50b53c2..3ed5787a5 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -44,6 +44,8 @@ import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter import { SettingsTabV2 } from "../../Tabs/SettingsTabV2"; import "./SettingsComponent.less"; import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils"; +import { Keys } from "../../../Localization/Keys.generated"; +import { t } from "../../../Localization/t"; import { ConflictResolutionComponent, ConflictResolutionComponentProps, @@ -689,12 +691,12 @@ export class SettingsComponent extends React.Component { const validationErrors = []; if (newDataMasking.includedPaths === undefined || newDataMasking.includedPaths === null) { - validationErrors.push("includedPaths is required"); + validationErrors.push(t(Keys.controls.settings.dataMasking.includedPathsRequired)); } else if (!Array.isArray(newDataMasking.includedPaths)) { - validationErrors.push("includedPaths must be an array"); + validationErrors.push(t(Keys.controls.settings.dataMasking.includedPathsMustBeArray)); } if (newDataMasking.excludedPaths !== undefined && !Array.isArray(newDataMasking.excludedPaths)) { - validationErrors.push("excludedPaths must be an array if provided"); + validationErrors.push(t(Keys.controls.settings.dataMasking.excludedPathsMustBeArray)); } this.setState({ @@ -896,7 +898,7 @@ export class SettingsComponent extends React.Component {this.shouldShowKeyspaceSharedThroughputMessage() && ( -
This table shared throughput is configured at the keyspace
+
{t(Keys.controls.settings.scale.keyspaceSharedThroughput)}
)}
- Cost estimate* + + {t(Keys.controls.settings.costEstimate.title)} + {costElement} - How we calculate this + {t(Keys.controls.settings.costEstimate.howWeCalculate)} @@ -353,7 +357,8 @@ export const getEstimatedSpendingElement = ( {priceBreakdown.currencySign} - {priceBreakdown.pricePerRu}/RU + {priceBreakdown.pricePerRu} + {t(Keys.controls.settings.costEstimate.perRu)} @@ -365,18 +370,16 @@ export const getEstimatedSpendingElement = ( export const manualToAutoscaleDisclaimerElement: JSX.Element = ( - The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings - and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "} + {t(Keys.controls.settings.throughput.manualToAutoscaleDisclaimer)}{" "} Learn more ); export const ttlWarning: JSX.Element = ( - The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete - operation explicitly issued by a client application. For more information see,{" "} + {t(Keys.controls.settings.throughput.ttlWarningText)}{" "} - Time to Live (TTL) in Azure Cosmos DB + {t(Keys.controls.settings.throughput.ttlWarningLinkText)} . @@ -384,29 +387,28 @@ export const ttlWarning: JSX.Element = ( export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => ( - You have not saved the latest changes made to your{" "} + {t(Keys.controls.settings.throughput.unsavedEditorWarningPrefix)}{" "} {editor === "indexPolicy" - ? "indexing policy" + ? t(Keys.controls.settings.throughput.unsavedIndexingPolicy) : editor === "dataMasking" - ? "data masking policy" - : "computed properties"} - . Please click save to confirm the changes. + ? t(Keys.controls.settings.throughput.unsavedDataMaskingPolicy) + : t(Keys.controls.settings.throughput.unsavedComputedProperties)} + {t(Keys.controls.settings.throughput.unsavedEditorWarningSuffix)} ); export const updateThroughputDelayedApplyWarningMessage: JSX.Element = ( - You are about to request an increase in throughput beyond the pre-allocated capacity. This operation will take some - time to complete. + {t(Keys.controls.settings.throughput.updateDelayedApplyWarning)} ); export const getUpdateThroughputBeyondInstantLimitMessage = (instantMaximumThroughput: number): JSX.Element => { return ( - Scaling up will take 4-6 hours as it exceeds what Azure Cosmos DB can instantly support currently based on your - number of physical partitions. You can increase your throughput to {instantMaximumThroughput} instantly or proceed - with this value and wait until the scale-up is completed. + {t(Keys.controls.settings.throughput.scalingUpDelayMessage, { + instantMaximumThroughput: String(instantMaximumThroughput), + })} ); }; @@ -418,22 +420,26 @@ export const getUpdateThroughputBeyondSupportLimitMessage = ( return ( <> - Your request to increase throughput exceeds the pre-allocated capacity which may take longer than expected. - There are three options you can choose from to proceed: + {t(Keys.controls.settings.throughput.exceedPreAllocatedMessage)}
    -
  1. You can instantly scale up to {instantMaximumThroughput} RU/s.
  2. +
  3. + {t(Keys.controls.settings.throughput.instantScaleOption, { + instantMaximumThroughput: String(instantMaximumThroughput), + })} +
  4. {instantMaximumThroughput < maximumThroughput && ( -
  5. You can asynchronously scale up to any value under {maximumThroughput} RU/s in 4-6 hours.
  6. +
  7. + {t(Keys.controls.settings.throughput.asyncScaleOption, { maximumThroughput: String(maximumThroughput) })} +
  8. )}
  9. - Your current quota max is {maximumThroughput} RU/s. To go over this limit, you must request a quota increase - and the Azure Cosmos DB team will review. + {t(Keys.controls.settings.throughput.quotaMaxOption, { maximumThroughput: String(maximumThroughput) })} - Learn more + {t(Keys.common.learnMore)}
@@ -444,23 +450,19 @@ export const getUpdateThroughputBeyondSupportLimitMessage = ( export const getUpdateThroughputBelowMinimumMessage = (minimum: number): JSX.Element => { return ( - You are not able to lower throughput below your current minimum of {minimum} RU/s. For more information on this - limit, please refer to our service quote documentation. + {t(Keys.controls.settings.throughput.belowMinimumMessage, { minimum: String(minimum) })} - Learn more + {t(Keys.common.learnMore)} ); }; export const saveThroughputWarningMessage: JSX.Element = ( - - Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below - before saving your changes - + {t(Keys.controls.settings.throughput.saveThroughputWarning)} ); const getCurrentThroughput = ( @@ -472,23 +474,29 @@ const getCurrentThroughput = ( if (targetThroughput) { if (throughput) { return isAutoscale - ? `, Current autoscale throughput: ${Math.round( + ? `, ${t(Keys.controls.settings.throughput.currentAutoscaleThroughput)} ${Math.round( throughput / 10, - )} - ${throughput} ${throughputUnit}, Target autoscale throughput: ${Math.round( - targetThroughput / 10, - )} - ${targetThroughput} ${throughputUnit}` - : `, Current manual throughput: ${throughput} ${throughputUnit}, Target manual throughput: ${targetThroughput}`; + )} - ${throughput} ${throughputUnit}, ${t( + Keys.controls.settings.throughput.targetAutoscaleThroughput, + )} ${Math.round(targetThroughput / 10)} - ${targetThroughput} ${throughputUnit}` + : `, ${t(Keys.controls.settings.throughput.currentManualThroughput)} ${throughput} ${throughputUnit}, ${t( + Keys.controls.settings.throughput.targetManualThroughput, + )} ${targetThroughput}`; } else { return isAutoscale - ? `, Target autoscale throughput: ${Math.round(targetThroughput / 10)} - ${targetThroughput} ${throughputUnit}` - : `, Target manual throughput: ${targetThroughput} ${throughputUnit}`; + ? `, ${t(Keys.controls.settings.throughput.targetAutoscaleThroughput)} ${Math.round( + targetThroughput / 10, + )} - ${targetThroughput} ${throughputUnit}` + : `, ${t(Keys.controls.settings.throughput.targetManualThroughput)} ${targetThroughput} ${throughputUnit}`; } } if (!targetThroughput && throughput) { return isAutoscale - ? `, Current autoscale throughput: ${Math.round(throughput / 10)} - ${throughput} ${throughputUnit}` - : `, Current manual throughput: ${throughput} ${throughputUnit}`; + ? `, ${t(Keys.controls.settings.throughput.currentAutoscaleThroughput)} ${Math.round( + throughput / 10, + )} - ${throughput} ${throughputUnit}` + : `, ${t(Keys.controls.settings.throughput.currentManualThroughput)} ${throughput} ${throughputUnit}`; } return ""; @@ -503,10 +511,10 @@ export const getThroughputApplyDelayedMessage = ( requestedThroughput: number, ): JSX.Element => ( - The request to increase the throughput has successfully been submitted. This operation will take 1-3 business days - to complete. View the latest status in Notifications. + {t(Keys.controls.settings.throughput.applyDelayedMessage)}
- Database: {databaseName}, Container: {collectionName}{" "} + {t(Keys.controls.settings.throughput.databaseLabel)} {databaseName},{" "} + {t(Keys.controls.settings.throughput.containerLabel)} {collectionName}{" "} {getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
); @@ -519,9 +527,13 @@ export const getThroughputApplyShortDelayMessage = ( collectionName: string, ): JSX.Element => ( - A request to increase the throughput is currently in progress. This operation will take some time to complete. + {t(Keys.controls.settings.throughput.applyShortDelayMessage)}
- {collectionName ? `Database: ${databaseName}, Container: ${collectionName} ` : `Database: ${databaseName} `} + {collectionName + ? `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName}, ${t( + Keys.controls.settings.throughput.containerLabel, + )} ${collectionName} ` + : `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName} `} {getCurrentThroughput(isAutoscale, throughput, throughputUnit)}
); @@ -535,10 +547,13 @@ export const getThroughputApplyLongDelayMessage = ( requestedThroughput: number, ): JSX.Element => ( - A request to increase the throughput is currently in progress. This operation will take 1-3 business days to - complete. View the latest status in Notifications. + {t(Keys.controls.settings.throughput.applyLongDelayMessage)}
- {collectionName ? `Database: ${databaseName}, Container: ${collectionName} ` : `Database: ${databaseName} `} + {collectionName + ? `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName}, ${t( + Keys.controls.settings.throughput.containerLabel, + )} ${collectionName} ` + : `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName} `} {getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
); @@ -547,63 +562,49 @@ export const getToolTipContainer = (content: string | JSX.Element): JSX.Element content ? {content} : undefined; export const conflictResolutionLwwTooltip: JSX.Element = ( - - Gets or sets the name of a integer property in your documents which is used for the Last Write Wins (LWW) based - conflict resolution scheme. By default, the system uses the system defined timestamp property, _ts to decide the - winner for the conflicting versions of the document. Specify your own integer property if you want to override the - default timestamp based conflict resolution. - + {t(Keys.controls.settings.conflictResolution.lwwTooltip)} ); export const conflictResolutionCustomToolTip: JSX.Element = ( - Gets or sets the name of a stored procedure (aka merge procedure) for resolving the conflicts. You can write - application defined logic to determine the winner of the conflicting versions of a document. The stored procedure - will get executed transactionally, exactly once, on the server side. If you do not provide a stored procedure, the - conflicts will be populated in the + {t(Keys.controls.settings.conflictResolution.customTooltip)} - {` conflicts feed`} + {t(Keys.controls.settings.conflictResolution.customTooltipConflictsFeed)} - . You can update/re-register the stored procedure at any time. + {t(Keys.controls.settings.conflictResolution.customTooltipSuffix)} ); export const changeFeedPolicyToolTip: JSX.Element = ( - - Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. - To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. - Reads are unaffected. - + {t(Keys.controls.settings.changeFeed.tooltip)} ); export const mongoIndexingPolicyDisclaimer: JSX.Element = ( - For queries that filter on multiple properties, create multiple single field indexes instead of a compound index. + {t(Keys.controls.settings.mongoIndexing.disclaimer)} - {` Compound indexes `} + {t(Keys.controls.settings.mongoIndexing.disclaimerCompoundIndexesLink)} - are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo - shell. + {t(Keys.controls.settings.mongoIndexing.disclaimerSuffix)} ); export const mongoCompoundIndexNotSupportedMessage: JSX.Element = ( - Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this - collection, use the Mongo Shell. + {t(Keys.controls.settings.mongoIndexing.compoundNotSupported)} ); export const mongoIndexingPolicyAADError: JSX.Element = ( - To use the indexing policy editor, please login to the + {t(Keys.controls.settings.mongoIndexing.aadError)} - {"azure portal."} + {t(Keys.controls.settings.mongoIndexing.aadErrorLink)} @@ -611,7 +612,7 @@ export const mongoIndexingPolicyAADError: JSX.Element = ( export const mongoIndexTransformationRefreshingMessage: JSX.Element = ( - Refreshing index transformation progress + {t(Keys.controls.settings.mongoIndexing.refreshingProgress)} ); @@ -623,15 +624,18 @@ export const renderMongoIndexTransformationRefreshMessage = ( if (progress === 0) { return ( - {"You can make more indexing changes once the current index transformation is complete. "} - {"Refresh to check if it has completed."} + {t(Keys.controls.settings.mongoIndexing.canMakeMoreChangesZero)} + {t(Keys.controls.settings.mongoIndexing.refreshToCheck)} ); } else { return ( - {`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `} - {"Refresh to check the progress."} + {`${t(Keys.controls.settings.mongoIndexing.canMakeMoreChangesProgress).replace( + "{{progress}}", + String(progress), + )} `} + {t(Keys.controls.settings.mongoIndexing.refreshToCheckProgress)} ); } diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx index 039a66304..034e16cf5 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx @@ -4,6 +4,8 @@ import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/C import { isDirty } from "Explorer/Controls/Settings/SettingsUtils"; import { loadMonaco } from "Explorer/LazyMonaco"; import { monacoTheme, useThemeStore } from "hooks/useTheme"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; import * as monaco from "monaco-editor"; import * as React from "react"; export interface ComputedPropertiesComponentProps { @@ -107,7 +109,7 @@ export class ComputedPropertiesComponent extends React.Component< this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, { value: value, language: "json", - ariaLabel: "Computed properties", + ariaLabel: t(Keys.controls.settings.computedProperties.ariaLabel), theme: monacoTheme(), }); if (this.computedPropertiesEditor) { @@ -151,9 +153,9 @@ export class ComputedPropertiesComponent extends React.Component< )} - {"Learn more"} + {t(Keys.common.learnMore)} -   about how to define computed properties and how to use them. +   {t(Keys.controls.settings.computedProperties.learnMorePrefix)}
( ( = {vectorEmbeddings && ( @@ -175,7 +177,7 @@ export const ContainerPolicyComponent: React.FC = {fullTextSearchPolicy ? ( @@ -218,7 +220,7 @@ export const ContainerPolicyComponent: React.FC = }); }} > - Create new full text search policy + {t(Keys.controls.settings.containerPolicy.createFullTextPolicy)} )} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/DataMaskingComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/DataMaskingComponent.tsx index 61ac40931..8c8f9ff86 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/DataMaskingComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/DataMaskingComponent.tsx @@ -2,6 +2,8 @@ import { MessageBar, MessageBarType, Stack } from "@fluentui/react"; import * as monaco from "monaco-editor"; import * as React from "react"; import * as DataModels from "../../../../Contracts/DataModels"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; import { loadMonaco } from "../../../LazyMonaco"; import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils"; import { isDirty as isContentDirty, isDataMaskingEnabled } from "../SettingsUtils"; @@ -89,7 +91,7 @@ export class DataMaskingComponent extends React.Component 0 && ( - Validation failed: {this.props.validationErrors.join(", ")} + {t(Keys.controls.settings.dataMasking.validationFailed)} {this.props.validationErrors.join(", ")} )}
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexComponent.tsx index 66aa3313a..fe9ee7889 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexComponent.tsx @@ -2,6 +2,8 @@ import { FontIcon, Link, Stack, Text } from "@fluentui/react"; import Explorer from "Explorer/Explorer"; import React from "react"; import * as ViewModels from "../../../../Contracts/ViewModels"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; import { GlobalSecondaryIndexSourceComponent } from "./GlobalSecondaryIndexSourceComponent"; import { GlobalSecondaryIndexTargetComponent } from "./GlobalSecondaryIndexTargetComponent"; @@ -21,7 +23,9 @@ export const GlobalSecondaryIndexComponent: React.FC {isSourceContainer && ( - This container has the following indexes defined for it. + + {t(Keys.controls.settings.globalSecondaryIndex.indexesDefined)} + )} {" "} - about how to define global secondary indexes and how to use them. + {t(Keys.controls.settings.globalSecondaryIndex.learnMoreSuffix)} {isSourceContainer && } diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexSourceComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexSourceComponent.tsx index aa0a0edae..04ce13c50 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexSourceComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexSourceComponent.tsx @@ -9,6 +9,8 @@ import { useSidePanel } from "hooks/useSidePanel"; import * as monaco from "monaco-editor"; import React, { useEffect, useRef } from "react"; import * as ViewModels from "../../../../Contracts/ViewModels"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; export interface GlobalSecondaryIndexSourceComponentProps { collection: ViewModels.Collection; @@ -67,7 +69,7 @@ export const GlobalSecondaryIndexSourceComponent: React.FC useSidePanel diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexTargetComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexTargetComponent.tsx index 8fa1171e8..211ab54b8 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexTargetComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/GlobalSecondaryIndexTargetComponent.tsx @@ -1,6 +1,8 @@ import { Stack, Text } from "@fluentui/react"; import * as React from "react"; import * as ViewModels from "../../../../Contracts/ViewModels"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; export interface GlobalSecondaryIndexTargetComponentProps { collection: ViewModels.Collection; @@ -25,17 +27,21 @@ export const GlobalSecondaryIndexTargetComponent: React.FC - Global Secondary Index Settings + {t(Keys.controls.settings.globalSecondaryIndex.settingsTitle)} - Source container + + {t(Keys.controls.settings.globalSecondaryIndex.sourceContainer)} + {globalSecondaryIndexDefinition?.sourceCollectionId} - Global secondary index definition + + {t(Keys.controls.settings.globalSecondaryIndex.indexDefinition)} + {globalSecondaryIndexDefinition?.definition} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx index 8990f7c93..db2fbbf61 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx @@ -3,6 +3,8 @@ import { monacoTheme, useThemeStore } from "hooks/useTheme"; import * as monaco from "monaco-editor"; import * as React from "react"; import * as DataModels from "../../../../Contracts/DataModels"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; import { loadMonaco } from "../../../LazyMonaco"; import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils"; import { isDirty, isIndexTransforming } from "../SettingsUtils"; @@ -119,7 +121,7 @@ export class IndexingPolicyComponent extends React.Component< value: value, language: "json", readOnly: isIndexTransforming(this.props.indexTransformationProgress), - ariaLabel: "Indexing Policy", + ariaLabel: t(Keys.controls.settings.indexingPolicy.ariaLabel), theme: monacoTheme(), }); if (this.indexingPolicyEditor) { diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx index 509373b8d..0bf803cbc 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx @@ -1,6 +1,8 @@ import { MessageBar, MessageBarType } from "@fluentui/react"; import * as React from "react"; import { handleError } from "../../../../../Common/ErrorHandlingUtils"; +import { Keys } from "../../../../../Localization/Keys.generated"; +import { t } from "../../../../../Localization/t"; import { mongoIndexTransformationRefreshingMessage, renderMongoIndexTransformationRefreshMessage, @@ -46,7 +48,11 @@ export class IndexingPolicyRefreshComponent extends React.Component< try { await this.props.refreshIndexTransformationProgress(); } catch (error) { - handleError(error, "RefreshIndexTransformationProgress", "Refreshing index transformation progress failed"); + handleError( + error, + "RefreshIndexTransformationProgress", + t(Keys.controls.settings.indexingPolicyRefresh.refreshFailed), + ); } finally { this.setState({ isRefreshing: false }); } diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap index 813b86e05..1f20f7361 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap @@ -14,7 +14,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = ` } } > - You can make more indexing changes once the current index transformation has completed. It is 90% complete. + You can make more indexing changes once the current index transformation has completed. It is 90% complete. diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/AddMongoIndexComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/AddMongoIndexComponent.tsx index cecafd83d..ab0f5a1d9 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/AddMongoIndexComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/AddMongoIndexComponent.tsx @@ -9,6 +9,8 @@ import { IDropdownOption, ITextField, } from "@fluentui/react"; +import { Keys } from "../../../../../Localization/Keys.generated"; +import { t } from "../../../../../Localization/t"; import { addMongoIndexSubElementsTokens, mongoErrorMessageStyles, @@ -66,7 +68,7 @@ export class AddMongoIndexComponent extends React.Component this.props.onDiscard()} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx index 36e999049..ef74be2f2 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx @@ -15,6 +15,8 @@ import { } from "@fluentui/react"; import * as React from "react"; import { MongoIndex } from "../../../../../Utils/arm/generatedClients/cosmos/types"; +import { Keys } from "../../../../../Localization/Keys.generated"; +import { t } from "../../../../../Localization/t"; import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent"; import { addMongoIndexStackProps, @@ -83,11 +85,25 @@ export class MongoIndexingPolicyComponent extends React.Component { return isCurrentIndex ? ( { @@ -170,7 +200,7 @@ export class MongoIndexingPolicyComponent extends React.Component ) : ( { this.props.onRevertIndexDrop(arrayPosition); @@ -258,7 +288,10 @@ export class MongoIndexingPolicyComponent extends React.Component - + { <> - + {indexesToBeDropped.length > 0 && ( = ({ return (collection.partitionKeyProperties || []).map((property) => "/" + property).join(", "); }; - const partitionKeyName = "Partition key"; + const partitionKeyName = t(Keys.controls.settings.partitionKey.partitionKey); const partitionKeyValue = getPartitionKeyValue(); const textHeadingStyle = { @@ -148,7 +150,13 @@ export const PartitionKeyComponent: React.FC = ({ const getProgressDescription = (): string => { const processedCount = portalDataTransferJob?.properties?.processedCount; const totalCount = portalDataTransferJob?.properties?.totalCount; - const processedCountString = totalCount > 0 ? `(${processedCount} of ${totalCount} documents processed)` : ""; + const processedCountString = + totalCount > 0 + ? t(Keys.controls.settings.partitionKeyEditor.documentsProcessed, { + processedCount: String(processedCount), + totalCount: String(totalCount), + }) + : ""; return `${portalDataTransferJob?.properties?.status} ${processedCountString}`; }; @@ -181,16 +189,28 @@ export const PartitionKeyComponent: React.FC = ({ return ( - {!isReadOnly && Change {partitionKeyName.toLowerCase()}} + {!isReadOnly && ( + + {t(Keys.controls.settings.partitionKeyEditor.changePartitionKey, { + partitionKeyName: partitionKeyName.toLowerCase(), + })} + + )} - Current {partitionKeyName.toLowerCase()} - Partitioning + + {t(Keys.controls.settings.partitionKeyEditor.currentPartitionKey, { + partitionKeyName: partitionKeyName.toLowerCase(), + })} + + {t(Keys.controls.settings.partitionKeyEditor.partitioning)} {partitionKeyValue} - {isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"} + {isHierarchicalPartitionedContainer() + ? t(Keys.controls.settings.partitionKeyEditor.hierarchical) + : t(Keys.controls.settings.partitionKeyEditor.nonHierarchical)} @@ -204,33 +224,33 @@ export const PartitionKeyComponent: React.FC = ({ messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }} styles={darkThemeMessageBarStyles} > - To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to - the source container for the entire duration of the partition key change process. + {t(Keys.controls.settings.partitionKeyEditor.safeguardWarning)} - Learn more + {t(Keys.common.learnMore)} - To change the partition key, a new destination container must be created or an existing destination - container selected. Data will then be copied to the destination container. + {t(Keys.controls.settings.partitionKeyEditor.changeDescription)} {configContext.platform !== Platform.Emulator && ( )} {portalDataTransferJob && ( - {partitionKeyName} change job + + {t(Keys.controls.settings.partitionKeyEditor.changeJob, { partitionKeyName })} + = ({ }} > {isCurrentJobInProgress(portalDataTransferJob) && ( - cancelRunningDataTransferJob(portalDataTransferJob)} /> + cancelRunningDataTransferJob(portalDataTransferJob)} + /> )} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx index 1928a68e6..38cb670be 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx @@ -4,6 +4,8 @@ import * as Constants from "../../../../Common/Constants"; import { Platform, configContext } from "../../../../ConfigContext"; import * as DataModels from "../../../../Contracts/DataModels"; import * as ViewModels from "../../../../Contracts/ViewModels"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; import * as SharedConstants from "../../../../Shared/Constants"; import { userContext } from "../../../../UserContext"; import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils"; @@ -156,14 +158,12 @@ export class ScaleComponent extends React.Component { const freeTierLimits = SharedConstants.FreeTierLimits; return ( - With free tier, you will get the first {freeTierLimits.RU} RU/s and {freeTierLimits.Storage} GB of storage in - this account for free. To keep your account free, keep the total RU/s across all resources in the account to{" "} - {freeTierLimits.RU} RU/s. + {t(Keys.controls.settings.scale.freeTierInfo, { ru: freeTierLimits.RU, storage: freeTierLimits.Storage })} - Learn more. + {t(Keys.controls.settings.scale.freeTierLearnMore)} ); @@ -188,12 +188,9 @@ export class ScaleComponent extends React.Component { {/* TODO: Replace link with call to the Azure Support blade */} {this.isAutoScaleEnabled() && ( - Throughput (RU/s) + {t(Keys.controls.settings.scale.throughputRuS)} - - Your account has custom settings that prevents setting throughput at the container level. Please work with - your Cosmos DB engineering team point of contact to make changes. - + {t(Keys.controls.settings.scale.autoScaleCustomSettings)} )} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx index e2da4db0b..8db9733b9 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx @@ -12,6 +12,8 @@ import { } from "@fluentui/react"; import * as React from "react"; import * as ViewModels from "../../../../Contracts/ViewModels"; +import { Keys } from "../../../../Localization/Keys.generated"; +import { t } from "../../../../Localization/t"; import { userContext } from "../../../../UserContext"; import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; import { @@ -85,9 +87,12 @@ export class SubSettingsComponent extends React.Component { @@ -216,13 +225,13 @@ export class SubSettingsComponent extends React.Component - To enable time-to-live (TTL) for your collection/documents,{" "} + {t(Keys.controls.settings.subSettings.mongoTtlMessage)}{" "} - create a TTL index + {t(Keys.controls.settings.subSettings.mongoTtlLinkText)} . @@ -231,7 +240,7 @@ export class SubSettingsComponent extends React.Component )} @@ -264,16 +273,16 @@ export class SubSettingsComponent extends React.Component ( )} @@ -302,14 +311,22 @@ export class SubSettingsComponent extends React.Component ( { @@ -328,7 +345,10 @@ export class SubSettingsComponent extends React.Component {this.getPartitionKeyVisible() && ( Large {this.partitionKeyName.toLowerCase()} has been enabled. + + {t(Keys.controls.settings.subSettings.largePartitionKeyEnabled, { + partitionKeyName: this.partitionKeyName.toLowerCase(), + })} + )} {userContext.apiType === "SQL" && (this.isHierarchicalPartitionedContainer() ? ( - Hierarchically partitioned container. + {t(Keys.controls.settings.subSettings.hierarchicalPartitioned)} ) : ( - Non-hierarchically partitioned container. + + {t(Keys.controls.settings.subSettings.nonHierarchicalPartitioned)} + ))} ); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx index bd8f638cd..d401ec088 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx @@ -1,5 +1,7 @@ import { Label, Slider, Stack, TextField, Toggle } from "@fluentui/react"; import { ThroughputBucket } from "Contracts/DataModels"; +import { Keys } from "../../../../../Localization/Keys.generated"; +import { t } from "../../../../../Localization/t"; import React, { FC, useEffect, useState } from "react"; import { isDirty } from "../../SettingsUtils"; @@ -65,7 +67,9 @@ export const ThroughputBucketsComponent: FC = ( return ( - + {throughputBuckets?.map((bucket) => ( @@ -76,7 +80,9 @@ export const ThroughputBucketsComponent: FC = ( value={bucket.maxThroughputPercentage} onChange={(newValue) => handleBucketChange(bucket.id, newValue)} showValue={false} - label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`} + label={`${t(Keys.controls.settings.throughputBuckets.bucketLabel, { id: String(bucket.id) })}${ + bucket.id === 1 ? t(Keys.controls.settings.throughputBuckets.dataExplorerQueryBucket) : "" + }`} styles={{ root: { flex: 2, maxWidth: 400 }, titleLabel: { @@ -99,8 +105,8 @@ export const ThroughputBucketsComponent: FC = ( disabled={bucket.maxThroughputPercentage === 100} /> onToggle(bucket.id, checked)} styles={{ diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx index a90237898..8158fb10c 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx @@ -18,6 +18,8 @@ import { } from "@fluentui/react"; import React from "react"; import * as DataModels from "../../../../../Contracts/DataModels"; +import { Keys } from "../../../../../Localization/Keys.generated"; +import { t } from "../../../../../Localization/t"; import * as SharedConstants from "../../../../../Shared/Constants"; import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor"; @@ -97,8 +99,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< private throughputInputMaxValue: number; private autoPilotInputMaxValue: number; private options: IChoiceGroupOption[] = [ - { key: "true", text: "Autoscale" }, - { key: "false", text: "Manual" }, + { key: "true", text: t(Keys.controls.settings.throughputInput.autoscale) }, + { key: "false", text: t(Keys.controls.settings.throughputInput.manual) }, ]; // Style constants for theme-aware colors and layout @@ -244,7 +246,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< return (
- Updated cost per month + {t(Keys.controls.settings.costEstimate.updatedCostPerMonth)} - {newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)} min + {newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)}{" "} + {t(Keys.controls.settings.throughputInput.min)} - {newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)} max + {newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}{" "} + {t(Keys.controls.settings.throughputInput.max)}
@@ -274,7 +278,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< {newThroughput && newThroughputCostElement()} - Current cost per month + {t(Keys.controls.settings.costEstimate.currentCostPerMonth)} - {prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)} min + {prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)}{" "} + {t(Keys.controls.settings.throughputInput.min)} - {prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)} max + {prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}{" "} + {t(Keys.controls.settings.throughputInput.max)} @@ -326,7 +332,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< return (
- Updated cost per month + {t(Keys.controls.settings.costEstimate.updatedCostPerMonth)} @@ -349,7 +355,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< {newThroughput && newThroughputCostElement()} - Current cost per month + {t(Keys.controls.settings.costEstimate.currentCostPerMonth)} @@ -444,10 +450,14 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< this.setState({ spendAckChecked: checked }); private getStorageCapacityTitle = (): JSX.Element => { - const capacity: string = this.props.isFixed ? "Fixed" : "Unlimited"; + const capacity: string = this.props.isFixed + ? t(Keys.controls.settings.throughputInput.fixed) + : t(Keys.controls.settings.throughputInput.unlimited); return ( - + {capacity} ); @@ -555,10 +565,14 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< /> - Instant + + {t(Keys.controls.settings.throughputInput.instant)} + - 4-6 hrs + + {t(Keys.controls.settings.throughputInput.fourToSixHrs)} + @@ -638,7 +652,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< variant="small" style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }} > - Minimum RU/s + {t(Keys.controls.settings.throughputInput.minimumRuS)} @@ -672,7 +686,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< color: "var(--colorNeutralForeground1)", }} > - x 10 = + {t(Keys.controls.settings.throughputInput.x10Equals)} {/* Column 3: Maximum RU/s */} @@ -682,7 +696,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< variant="small" style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }} > - Maximum RU/s + {t(Keys.controls.settings.throughputInput.maximumRuS)} @@ -723,7 +737,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< onGetErrorMessage={(value: string) => { const sanitizedValue = getSanitizedInputValue(value); const errorMessage: string = - sanitizedValue % 1000 ? "Throughput value must be in increments of 1000" : this.props.throughputError; + sanitizedValue % 1000 + ? t(Keys.controls.settings.throughput.throughputIncrementError) + : this.props.throughputError; return {errorMessage}; }} validateOnLoad={false} @@ -769,7 +785,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< )} {this.props.isAutoPilotSelected ? ( - Based on usage, your {this.props.collectionName ? "container" : "database"} throughput will scale from{" "} + {t(Keys.controls.settings.throughputInput.autoscaleDescription, { + resourceType: this.props.collectionName ? "container" : "database", + })}{" "} {AutoPilotUtils.getMinRUsBasedOnUserInput(this.props.maxAutoPilotThroughput)} RU/s (10% of max RU/s) -{" "} {this.props.maxAutoPilotThroughput} RU/s @@ -784,16 +802,19 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< styles={this.darkThemeMessageBarStyles} style={{ marginTop: "40px" }} > - {`Billing will apply if you provision more than ${SharedConstants.FreeTierLimits.RU} RU/s of manual throughput, or if the resource scales beyond ${SharedConstants.FreeTierLimits.RU} RU/s with autoscale.`} + {t(Keys.controls.settings.throughputInput.freeTierWarning, { + ru: String(SharedConstants.FreeTierLimits.RU), + })} )} )} {!this.overrideWithProvisionedThroughputSettings() && ( - Estimate your required RU/s with + {t(Keys.controls.settings.throughputInput.capacityCalculator)} - {` capacity calculator`} + {t(Keys.controls.settings.throughputInput.capacityCalculatorLink)}{" "} + )} @@ -806,9 +827,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< onChange={this.onSpendAckChecked} /> )} - {this.props.isFixed && ( -

When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.

- )} + {this.props.isFixed &&

{t(Keys.controls.settings.throughputInput.fixedStorageNote)}

} {this.props.collectionName && ( {this.getStorageCapacityTitle()} )} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap index d5de24a7b..9945e37b4 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap @@ -552,9 +552,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = ` } } > - You are not able to lower throughput below your current minimum of - 10000 - RU/s. For more information on this limit, please refer to our service quote documentation. + You are not able to lower throughput below your current minimum of 10000 RU/s. For more information on this limit, please refer to our service quote documentation. - Based on usage, your - container - throughput will scale from + Based on usage, your container throughput will scale from 400 @@ -683,7 +679,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = ` $ 35.04 - min + + min
@@ -730,7 +728,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = ` $ 35.04 - min + + min
@@ -1151,9 +1151,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = ` } } > - You are not able to lower throughput below your current minimum of - 10000 - RU/s. For more information on this limit, please refer to our service quote documentation. + You are not able to lower throughput below your current minimum of 10000 RU/s. For more information on this limit, please refer to our service quote documentation. - You are not able to lower throughput below your current minimum of - 10000 - RU/s. For more information on this limit, please refer to our service quote documentation. + You are not able to lower throughput below your current minimum of 10000 RU/s. For more information on this limit, please refer to our service quote documentation. -   about how to define computed properties and how to use them. +   + about how to define computed properties and how to use them.
- Change - partition key + Change partition key - Current - partition key + Current partition key - Current - partition key + Current partition key - Large - partition key - has been enabled. + Large partition key has been enabled. { export const getTabTitle = (tab: SettingsV2TabTypes): string => { switch (tab) { case SettingsV2TabTypes.ScaleTab: - return "Scale"; + return t(Keys.controls.settings.tabTitles.scale); case SettingsV2TabTypes.ConflictResolutionTab: - return "Conflict Resolution"; + return t(Keys.controls.settings.tabTitles.conflictResolution); case SettingsV2TabTypes.SubSettingsTab: - return "Settings"; + return t(Keys.controls.settings.tabTitles.settings); case SettingsV2TabTypes.IndexingPolicyTab: - return "Indexing Policy"; + return t(Keys.controls.settings.tabTitles.indexingPolicy); case SettingsV2TabTypes.PartitionKeyTab: - return isFabricNative() ? "Partition Keys" : "Partition Keys (preview)"; + return isFabricNative() + ? t(Keys.controls.settings.tabTitles.partitionKeys) + : t(Keys.controls.settings.tabTitles.partitionKeysPreview); case SettingsV2TabTypes.ComputedPropertiesTab: - return "Computed Properties"; + return t(Keys.controls.settings.tabTitles.computedProperties); case SettingsV2TabTypes.ContainerVectorPolicyTab: - return "Container Policies"; + return t(Keys.controls.settings.tabTitles.containerPolicies); case SettingsV2TabTypes.ThroughputBucketsTab: - return "Throughput Buckets"; + return t(Keys.controls.settings.tabTitles.throughputBuckets); case SettingsV2TabTypes.GlobalSecondaryIndexTab: - return "Global Secondary Index (Preview)"; + return t(Keys.controls.settings.tabTitles.globalSecondaryIndexPreview); case SettingsV2TabTypes.DataMaskingTab: - return "Masking Policy (preview)"; + return t(Keys.controls.settings.tabTitles.maskingPolicyPreview); default: throw new Error(`Unknown tab ${tab}`); } @@ -203,19 +207,19 @@ export const getMongoNotification = (description: string, type: MongoIndexTypes) if (description && !type) { return { type: MongoNotificationType.Warning, - message: "Please select a type for each index.", + message: t(Keys.controls.settings.mongoNotifications.selectTypeWarning), }; } if (type && (!description || description.trim().length === 0)) { return { type: MongoNotificationType.Error, - message: "Please enter a field name.", + message: t(Keys.controls.settings.mongoNotifications.enterFieldNameError), }; } else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) { return { type: MongoNotificationType.Error, - message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder, + message: t(Keys.controls.settings.mongoNotifications.wildcardPathError) + MongoWildcardPlaceHolder, }; } @@ -249,28 +253,29 @@ export const isIndexTransforming = (indexTransformationProgress: number): boolea indexTransformationProgress !== undefined && indexTransformationProgress !== 100; export const getPartitionKeyName = (apiType: string, isLowerCase?: boolean): string => { - const partitionKeyName = apiType === "Mongo" ? "Shard key" : "Partition key"; + const partitionKeyName = + apiType === "Mongo" + ? t(Keys.controls.settings.partitionKey.shardKey) + : t(Keys.controls.settings.partitionKey.partitionKey); return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName; }; export const getPartitionKeyTooltipText = (apiType: string): string => { if (apiType === "Mongo") { - return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. It’s critical to choose a field that will evenly distribute your data."; + return t(Keys.controls.settings.partitionKey.shardKeyTooltip); } - let tooltipText = `The ${getPartitionKeyName( - apiType, - true, - )} is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume.`; + let tooltipText = `The ${getPartitionKeyName(apiType, true)} ${t( + Keys.controls.settings.partitionKey.partitionKeyTooltip, + )}`; if (apiType === "SQL") { - tooltipText += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice."; + tooltipText += t(Keys.controls.settings.partitionKey.sqlPartitionKeyTooltipSuffix); } return tooltipText; }; export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: string): string => { if (partitionKeyDefault && (apiType === "SQL" || apiType === "Mongo")) { - const subtext = "For small workloads, the item ID is a suitable choice for the partition key."; - return subtext; + return t(Keys.controls.settings.partitionKey.partitionKeySubtext); } return ""; }; @@ -278,18 +283,18 @@ export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: st export const getPartitionKeyPlaceHolder = (apiType: string, index?: number): string => { switch (apiType) { case "Mongo": - return "e.g., categoryId"; + return t(Keys.controls.settings.partitionKey.mongoPlaceholder); case "Gremlin": - return "e.g., /address"; + return t(Keys.controls.settings.partitionKey.gremlinPlaceholder); case "SQL": return `${ index === undefined - ? "Required - first partition key e.g., /TenantId" + ? t(Keys.controls.settings.partitionKey.sqlFirstPartitionKey) : index === 0 - ? "second partition key e.g., /UserId" - : "third partition key e.g., /SessionId" + ? t(Keys.controls.settings.partitionKey.sqlSecondPartitionKey) + : t(Keys.controls.settings.partitionKey.sqlThirdPartitionKey) }`; default: - return "e.g., /address/zipCode"; + return t(Keys.controls.settings.partitionKey.defaultPlaceholder); } }; diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap index baae4ef02..a333091cf 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap @@ -127,9 +127,13 @@ exports[`SettingsUtils functions render 1`] = ` > The request to increase the throughput has successfully been submitted. This operation will take 1-3 business days to complete. View the latest status in Notifications.
- Database: + Database: + sampleDb - , Container: + , + + Container: + sampleCollection , Current manual throughput: 1000 RU/s, Target manual throughput: 2000 @@ -309,7 +313,7 @@ exports[`SettingsUtils functions render 1`] = ` } } > - You can make more indexing changes once the current index transformation has completed. It is 90% complete. + You can make more indexing changes once the current index transformation has completed. It is 90% complete. diff --git a/src/Localization/en/Resources.json b/src/Localization/en/Resources.json index 43e49717b..5d8d771af 100644 --- a/src/Localization/en/Resources.json +++ b/src/Localization/en/Resources.json @@ -723,5 +723,213 @@ "information": "Information", "moreDetails": "More details" } + }, + "controls": { + "settings": { + "tabTitles": { + "scale": "Scale", + "conflictResolution": "Conflict Resolution", + "settings": "Settings", + "indexingPolicy": "Indexing Policy", + "partitionKeys": "Partition Keys", + "partitionKeysPreview": "Partition Keys (preview)", + "computedProperties": "Computed Properties", + "containerPolicies": "Container Policies", + "throughputBuckets": "Throughput Buckets", + "globalSecondaryIndexPreview": "Global Secondary Index (Preview)", + "maskingPolicyPreview": "Masking Policy (preview)" + }, + "mongoNotifications": { + "selectTypeWarning": "Please select a type for each index.", + "enterFieldNameError": "Please enter a field name.", + "wildcardPathError": "Wildcard path is not present in the field name. Use a pattern like " + }, + "partitionKey": { + "shardKey": "Shard key", + "partitionKey": "Partition key", + "shardKeyTooltip": "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. It's critical to choose a field that will evenly distribute your data.", + "partitionKeyTooltip": "is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume.", + "sqlPartitionKeyTooltipSuffix": " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.", + "partitionKeySubtext": "For small workloads, the item ID is a suitable choice for the partition key.", + "mongoPlaceholder": "e.g., categoryId", + "gremlinPlaceholder": "e.g., /address", + "sqlFirstPartitionKey": "Required - first partition key e.g., /TenantId", + "sqlSecondPartitionKey": "second partition key e.g., /UserId", + "sqlThirdPartitionKey": "third partition key e.g., /SessionId", + "defaultPlaceholder": "e.g., /address/zipCode" + }, + "costEstimate": { + "title": "Cost estimate*", + "howWeCalculate": "How we calculate this", + "updatedCostPerMonth": "Updated cost per month", + "currentCostPerMonth": "Current cost per month", + "perRu": "/RU" + }, + "throughput": { + "manualToAutoscaleDisclaimer": "The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.", + "ttlWarningText": "The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application. For more information see,", + "ttlWarningLinkText": "Time to Live (TTL) in Azure Cosmos DB", + "unsavedIndexingPolicy": "indexing policy", + "unsavedDataMaskingPolicy": "data masking policy", + "unsavedComputedProperties": "computed properties", + "unsavedEditorWarningPrefix": "You have not saved the latest changes made to your", + "unsavedEditorWarningSuffix": ". Please click save to confirm the changes.", + "updateDelayedApplyWarning": "You are about to request an increase in throughput beyond the pre-allocated capacity. This operation will take some time to complete.", + "scalingUpDelayMessage": "Scaling up will take 4-6 hours as it exceeds what Azure Cosmos DB can instantly support currently based on your number of physical partitions. You can increase your throughput to {{instantMaximumThroughput}} instantly or proceed with this value and wait until the scale-up is completed.", + "exceedPreAllocatedMessage": "Your request to increase throughput exceeds the pre-allocated capacity which may take longer than expected. There are three options you can choose from to proceed:", + "instantScaleOption": "You can instantly scale up to {{instantMaximumThroughput}} RU/s.", + "asyncScaleOption": "You can asynchronously scale up to any value under {{maximumThroughput}} RU/s in 4-6 hours.", + "quotaMaxOption": "Your current quota max is {{maximumThroughput}} RU/s. To go over this limit, you must request a quota increase and the Azure Cosmos DB team will review.", + "belowMinimumMessage": "You are not able to lower throughput below your current minimum of {{minimum}} RU/s. For more information on this limit, please refer to our service quote documentation.", + "saveThroughputWarning": "Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below before saving your changes", + "currentAutoscaleThroughput": "Current autoscale throughput:", + "targetAutoscaleThroughput": "Target autoscale throughput:", + "currentManualThroughput": "Current manual throughput:", + "targetManualThroughput": "Target manual throughput:", + "applyDelayedMessage": "The request to increase the throughput has successfully been submitted. This operation will take 1-3 business days to complete. View the latest status in Notifications.", + "databaseLabel": "Database:", + "containerLabel": "Container:", + "applyShortDelayMessage": "A request to increase the throughput is currently in progress. This operation will take some time to complete.", + "applyLongDelayMessage": "A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.", + "throughputCapError": "Your account is currently configured with a total throughput limit of {{throughputCap}} RU/s. This update isn't possible because it would increase the total throughput to {{newTotalThroughput}} RU/s. Change total throughput limit in cost management.", + "throughputIncrementError": "Throughput value must be in increments of 1000" + }, + "conflictResolution": { + "lwwDefault": "Last Write Wins (default)", + "customMergeProcedure": "Merge Procedure (custom)", + "mode": "Mode", + "conflictResolverProperty": "Conflict Resolver Property", + "storedProcedure": "Stored procedure", + "lwwTooltip": "Gets or sets the name of a integer property in your documents which is used for the Last Write Wins (LWW) based conflict resolution scheme. By default, the system uses the system defined timestamp property, _ts to decide the winner for the conflicting versions of the document. Specify your own integer property if you want to override the default timestamp based conflict resolution.", + "customTooltip": "Gets or sets the name of a stored procedure (aka merge procedure) for resolving the conflicts. You can write application defined logic to determine the winner of the conflicting versions of a document. The stored procedure will get executed transactionally, exactly once, on the server side. If you do not provide a stored procedure, the conflicts will be populated in the", + "customTooltipConflictsFeed": " conflicts feed", + "customTooltipSuffix": ". You can update/re-register the stored procedure at any time." + }, + "changeFeed": { + "label": "Change feed log retention policy", + "tooltip": "Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected." + }, + "mongoIndexing": { + "disclaimer": "For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.", + "disclaimerCompoundIndexesLink": " Compound indexes ", + "disclaimerSuffix": "are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.", + "compoundNotSupported": "Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this collection, use the Mongo Shell.", + "aadError": "To use the indexing policy editor, please login to the", + "aadErrorLink": "azure portal.", + "refreshingProgress": "Refreshing index transformation progress", + "canMakeMoreChangesZero": "You can make more indexing changes once the current index transformation is complete. ", + "refreshToCheck": "Refresh to check if it has completed.", + "canMakeMoreChangesProgress": "You can make more indexing changes once the current index transformation has completed. It is {{progress}}% complete. ", + "refreshToCheckProgress": "Refresh to check the progress.", + "definitionColumn": "Definition", + "typeColumn": "Type", + "dropIndexColumn": "Drop Index", + "addIndexBackColumn": "Add index back", + "deleteIndexButton": "Delete index Button", + "addBackIndexButton": "Add back Index Button", + "currentIndexes": "Current index(es)", + "indexesToBeDropped": "Index(es) to be dropped", + "indexFieldName": "Index Field Name", + "indexType": "Index Type", + "selectIndexType": "Select an index type", + "undoButton": "Undo Button" + }, + "subSettings": { + "timeToLive": "Time to Live", + "ttlOff": "Off", + "ttlOnNoDefault": "On (no default)", + "ttlOn": "On", + "seconds": "second(s)", + "timeToLiveInSeconds": "Time to live in seconds", + "analyticalStorageTtl": "Analytical Storage Time to Live", + "geospatialConfiguration": "Geospatial Configuration", + "geography": "Geography", + "geometry": "Geometry", + "uniqueKeys": "Unique keys", + "mongoTtlMessage": "To enable time-to-live (TTL) for your collection/documents,", + "mongoTtlLinkText": "create a TTL index", + "partitionKeyTooltipTemplate": "This {{partitionKeyName}} is used to distribute data across multiple partitions for scalability. The value \"{{partitionKeyValue}}\" determines how documents are partitioned.", + "largePartitionKeyEnabled": "Large {{partitionKeyName}} has been enabled.", + "hierarchicalPartitioned": "Hierarchically partitioned container.", + "nonHierarchicalPartitioned": "Non-hierarchically partitioned container." + }, + "scale": { + "freeTierInfo": "With free tier, you will get the first {{ru}} RU/s and {{storage}} GB of storage in this account for free. To keep your account free, keep the total RU/s across all resources in the account to {{ru}} RU/s.", + "freeTierLearnMore": "Learn more.", + "throughputRuS": "Throughput (RU/s)", + "autoScaleCustomSettings": "Your account has custom settings that prevents setting throughput at the container level. Please work with your Cosmos DB engineering team point of contact to make changes.", + "keyspaceSharedThroughput": "This table shared throughput is configured at the keyspace" + }, + "partitionKeyEditor": { + "changePartitionKey": "Change {{partitionKeyName}}", + "currentPartitionKey": "Current {{partitionKeyName}}", + "partitioning": "Partitioning", + "hierarchical": "Hierarchical", + "nonHierarchical": "Non-hierarchical", + "safeguardWarning": "To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the source container for the entire duration of the partition key change process.", + "changeDescription": "To change the partition key, a new destination container must be created or an existing destination container selected. Data will then be copied to the destination container.", + "changeButton": "Change", + "changeJob": "{{partitionKeyName}} change job", + "cancelButton": "Cancel", + "documentsProcessed": "({{processedCount}} of {{totalCount}} documents processed)" + }, + "computedProperties": { + "ariaLabel": "Computed properties", + "learnMorePrefix": "about how to define computed properties and how to use them." + }, + "indexingPolicy": { + "ariaLabel": "Indexing Policy" + }, + "dataMasking": { + "ariaLabel": "Data Masking Policy", + "validationFailed": "Validation failed:", + "includedPathsRequired": "includedPaths is required", + "includedPathsMustBeArray": "includedPaths must be an array", + "excludedPathsMustBeArray": "excludedPaths must be an array if provided" + }, + "containerPolicy": { + "vectorPolicy": "Vector Policy", + "fullTextPolicy": "Full Text Policy", + "createFullTextPolicy": "Create new full text search policy" + }, + "globalSecondaryIndex": { + "indexesDefined": "This container has the following indexes defined for it.", + "learnMoreSuffix": "about how to define global secondary indexes and how to use them.", + "jsonAriaLabel": "Global Secondary Index JSON", + "addIndex": "Add index", + "settingsTitle": "Global Secondary Index Settings", + "sourceContainer": "Source container", + "indexDefinition": "Global secondary index definition" + }, + "indexingPolicyRefresh": { + "refreshFailed": "Refreshing index transformation progress failed" + }, + "throughputInput": { + "autoscale": "Autoscale", + "manual": "Manual", + "minimumRuS": "Minimum RU/s", + "maximumRuS": "Maximum RU/s", + "x10Equals": "x 10 =", + "storageCapacity": "Storage capacity", + "fixed": "Fixed", + "unlimited": "Unlimited", + "instant": "Instant", + "fourToSixHrs": "4-6 hrs", + "autoscaleDescription": "Based on usage, your {{resourceType}} throughput will scale from", + "freeTierWarning": "Billing will apply if you provision more than {{ru}} RU/s of manual throughput, or if the resource scales beyond {{ru}} RU/s with autoscale.", + "capacityCalculator": "Estimate your required RU/s with", + "capacityCalculatorLink": " capacity calculator", + "fixedStorageNote": "When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.", + "min": "min", + "max": "max" + }, + "throughputBuckets": { + "label": "Throughput Buckets", + "bucketLabel": "Bucket {{id}}", + "dataExplorerQueryBucket": " (Data Explorer Query Bucket)", + "active": "Active", + "inactive": "Inactive" + } + } } }