Localization second batch (#2410)

* Localization second batch

* update test

* fix tests

* Fix test
This commit is contained in:
sunghyunkang1111
2026-03-05 12:05:45 -06:00
committed by GitHub
parent c343bad630
commit 77132be3b3
11 changed files with 320 additions and 159 deletions

View File

@@ -7,6 +7,8 @@ import {
AddGlobalSecondaryIndexPanelProps, AddGlobalSecondaryIndexPanelProps,
} from "Explorer/Panes/AddGlobalSecondaryIndexPanel/AddGlobalSecondaryIndexPanel"; } from "Explorer/Panes/AddGlobalSecondaryIndexPanel/AddGlobalSecondaryIndexPanel";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { Keys } from "Localization/Keys.generated";
import { t } from "Localization/t";
import { isFabric, isFabricNative, openRestoreContainerDialog } from "Platform/Fabric/FabricUtil"; import { isFabric, isFabricNative, openRestoreContainerDialog } from "Platform/Fabric/FabricUtil";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor"; import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
@@ -24,6 +26,7 @@ import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg"; import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg"; import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
@@ -35,7 +38,6 @@ import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger"; import Trigger from "./Tree/Trigger";
import UserDefinedFunction from "./Tree/UserDefinedFunction"; import UserDefinedFunction from "./Tree/UserDefinedFunction";
import { useSelectedNode } from "./useSelectedNode"; import { useSelectedNode } from "./useSelectedNode";
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
export interface CollectionContextMenuButtonParams { export interface CollectionContextMenuButtonParams {
databaseId: string; databaseId: string;
@@ -57,7 +59,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
{ {
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked({ databaseId }), onClick: () => container.onNewCollectionClicked({ databaseId }),
label: `New ${getCollectionName()}`, label: t(Keys.contextMenu.newContainer, { containerName: getCollectionName() }),
}, },
]; ];
@@ -67,7 +69,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
items.push({ items.push({
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
onClick: () => openRestoreContainerDialog(), onClick: () => openRestoreContainerDialog(),
label: `Restore ${getCollectionName()}`, label: t(Keys.contextMenu.restoreContainer, { containerName: getCollectionName() }),
}); });
} }
} }
@@ -80,11 +82,11 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
"Delete " + getDatabaseName(), t(Keys.contextMenu.deleteDatabase, { databaseName: getDatabaseName() }),
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />, <DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />,
); );
}, },
label: `Delete ${getDatabaseName()}`, label: t(Keys.contextMenu.deleteDatabase, { databaseName: getDatabaseName() }),
styleClass: "deleteDatabaseMenuItem", styleClass: "deleteDatabaseMenuItem",
}); });
} }
@@ -100,7 +102,7 @@ export const createCollectionContextMenuButton = (
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined), onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined),
label: "New SQL Query", label: t(Keys.contextMenu.newSqlQuery),
}); });
} }
@@ -108,7 +110,7 @@ export const createCollectionContextMenuButton = (
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined), onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined),
label: "New Query", label: t(Keys.contextMenu.newQuery),
}); });
items.push({ items.push({
@@ -123,8 +125,8 @@ export const createCollectionContextMenuButton = (
}, },
label: label:
useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell
? "Open Mongo Shell" ? t(Keys.contextMenu.openMongoShell)
: "New Shell", : t(Keys.contextMenu.newShell),
}); });
} }
@@ -137,7 +139,7 @@ export const createCollectionContextMenuButton = (
onClick: () => { onClick: () => {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
}, },
label: "Open Cassandra Shell", label: t(Keys.contextMenu.openCassandraShell),
}); });
} }
@@ -150,7 +152,7 @@ export const createCollectionContextMenuButton = (
onClick: () => { onClick: () => {
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
}, },
label: "New Stored Procedure", label: t(Keys.contextMenu.newStoredProcedure),
}); });
items.push({ items.push({
@@ -158,7 +160,7 @@ export const createCollectionContextMenuButton = (
onClick: () => { onClick: () => {
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
}, },
label: "New UDF", label: t(Keys.contextMenu.newUdf),
}); });
items.push({ items.push({
@@ -166,7 +168,7 @@ export const createCollectionContextMenuButton = (
onClick: () => { onClick: () => {
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, undefined); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, undefined);
}, },
label: "New Trigger", label: t(Keys.contextMenu.newTrigger),
}); });
} }
@@ -179,11 +181,11 @@ export const createCollectionContextMenuButton = (
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
"Delete " + getCollectionName(), t(Keys.contextMenu.deleteContainer, { containerName: getCollectionName() }),
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />, <DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />,
); );
}, },
label: `Delete ${getCollectionName()}`, label: t(Keys.contextMenu.deleteContainer, { containerName: getCollectionName() }),
styleClass: "deleteCollectionMenuItem", styleClass: "deleteCollectionMenuItem",
}); });
} }
@@ -220,14 +222,14 @@ export const createSampleCollectionContextMenuButton = (): TreeNodeMenuItem[] =>
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot); useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType }); traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
}, },
label: "New SQL Query", label: t(Keys.contextMenu.newSqlQuery),
}); });
} else if (copilotVersion === "v2.0") { } else if (copilotVersion === "v2.0") {
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection; const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => sampleCollection && sampleCollection.onNewQueryClick(sampleCollection, undefined), onClick: () => sampleCollection && sampleCollection.onNewQueryClick(sampleCollection, undefined),
label: "New SQL Query", label: t(Keys.contextMenu.newSqlQuery),
}); });
} }
} }
@@ -247,7 +249,7 @@ export const createStoreProcedureContextMenuItems = (
{ {
iconSrc: DeleteSprocIcon, iconSrc: DeleteSprocIcon,
onClick: () => storedProcedure.delete(), onClick: () => storedProcedure.delete(),
label: "Delete Stored Procedure", label: t(Keys.contextMenu.deleteStoredProcedure),
}, },
]; ];
}; };
@@ -261,7 +263,7 @@ export const createTriggerContextMenuItems = (container: Explorer, trigger: Trig
{ {
iconSrc: DeleteTriggerIcon, iconSrc: DeleteTriggerIcon,
onClick: () => trigger.delete(), onClick: () => trigger.delete(),
label: "Delete Trigger", label: t(Keys.contextMenu.deleteTrigger),
}, },
]; ];
}; };
@@ -278,7 +280,7 @@ export const createUserDefinedFunctionContextMenuItems = (
{ {
iconSrc: DeleteUDFIcon, iconSrc: DeleteUDFIcon,
onClick: () => userDefinedFunction.delete(), onClick: () => userDefinedFunction.delete(),
label: "Delete User Defined Function", label: t(Keys.contextMenu.deleteUdf),
}, },
]; ];
}; };

View File

@@ -18,6 +18,8 @@ import { queryConflicts } from "../../Common/dataAccess/queryConflicts";
import { updateDocument } from "../../Common/dataAccess/updateDocument"; import { updateDocument } from "../../Common/dataAccess/updateDocument";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { Keys } from "../../Localization/Keys.generated";
import { t } from "../../Localization/t";
import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
@@ -57,7 +59,7 @@ export default class ConflictsTab extends TabsBase {
private _documentsIterator: MinimalQueryIterator; private _documentsIterator: MinimalQueryIterator;
private _container: Explorer; private _container: Explorer;
private _acceptButtonLabel: ko.Observable<string> = ko.observable("Save"); private _acceptButtonLabel: ko.Observable<string> = ko.observable(t(Keys.common.save));
constructor(options: ViewModels.ConflictsTabOptions) { constructor(options: ViewModels.ConflictsTabOptions) {
super(options); super(options);
@@ -213,9 +215,9 @@ export default class ConflictsTab extends TabsBase {
this.selectedConflictContent.subscribe((newContent: string) => this._onEditorContentChange(newContent)); this.selectedConflictContent.subscribe((newContent: string) => this._onEditorContentChange(newContent));
this.conflictOperation.subscribe((newOperationType: string) => { this.conflictOperation.subscribe((newOperationType: string) => {
let operationLabel = "Save"; let operationLabel = t(Keys.common.save);
if (newOperationType === Constants.ConflictOperationType.Replace) { if (newOperationType === Constants.ConflictOperationType.Replace) {
operationLabel = "Update"; operationLabel = t(Keys.common.update);
} }
this._acceptButtonLabel(operationLabel); this._acceptButtonLabel(operationLabel);
@@ -229,7 +231,7 @@ export default class ConflictsTab extends TabsBase {
this._documentsIterator = this.createIterator(); this._documentsIterator = this.createIterator();
await this.loadNextPage(); await this.loadNextPage();
} catch (error) { } catch (error) {
useDialog.getState().showOkModalDialog("Refresh documents grid failed", getErrorMessage(error)); useDialog.getState().showOkModalDialog(t(Keys.tabs.conflicts.refreshGridFailed), getErrorMessage(error));
} }
} }
@@ -257,11 +259,11 @@ export default class ConflictsTab extends TabsBase {
useDialog useDialog
.getState() .getState()
.showOkCancelModalDialog( .showOkCancelModalDialog(
"Unsaved changes", t(Keys.tabs.conflicts.unsavedChanges),
"Changes will be lost. Do you want to continue?", t(Keys.tabs.conflicts.changesWillBeLost),
"OK", t(Keys.common.ok),
async () => await this.resolveConflict(), async () => await this.resolveConflict(),
"Cancel", t(Keys.common.cancel),
undefined, undefined,
); );
} else { } else {
@@ -332,7 +334,7 @@ export default class ConflictsTab extends TabsBase {
} catch (error) { } catch (error) {
this.isExecutionError(true); this.isExecutionError(true);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Resolve conflict failed", errorMessage); useDialog.getState().showOkModalDialog(t(Keys.tabs.conflicts.resolveConflictFailed), errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.ResolveConflict, Action.ResolveConflict,
{ {
@@ -386,7 +388,7 @@ export default class ConflictsTab extends TabsBase {
} catch (error) { } catch (error) {
this.isExecutionError(true); this.isExecutionError(true);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Delete conflict failed", errorMessage); useDialog.getState().showOkModalDialog(t(Keys.tabs.conflicts.deleteConflictFailed), errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.DeleteConflict, Action.DeleteConflict,
{ {
@@ -617,7 +619,7 @@ export default class ConflictsTab extends TabsBase {
} }
if (this.discardButton.visible()) { if (this.discardButton.visible()) {
const label = "Discard"; const label = t(Keys.common.discard);
buttons.push({ buttons.push({
iconSrc: DiscardIcon, iconSrc: DiscardIcon,
iconAlt: label, iconAlt: label,
@@ -630,7 +632,7 @@ export default class ConflictsTab extends TabsBase {
} }
if (this.deleteButton.visible()) { if (this.deleteButton.visible()) {
const label = "Delete"; const label = t(Keys.common.delete);
buttons.push({ buttons.push({
iconSrc: DeleteIcon, iconSrc: DeleteIcon,
iconAlt: label, iconAlt: label,

View File

@@ -41,6 +41,8 @@ import { usePrevious } from "Explorer/Tabs/DocumentsTabV2/SelectionHelper";
import { CosmosFluentProvider, LayoutConstants, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil"; import { CosmosFluentProvider, LayoutConstants, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import { Keys } from "Localization/Keys.generated";
import { t } from "Localization/t";
import { isFabric } from "Platform/Fabric/FabricUtil"; import { isFabric } from "Platform/Fabric/FabricUtil";
import { QueryConstants } from "Shared/Constants"; import { QueryConstants } from "Shared/Constants";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
@@ -349,7 +351,7 @@ export const getTabsButtons = ({
} }
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
const label = !isPreferredApiMongoDB ? "New Item" : "New Document"; const label = !isPreferredApiMongoDB ? t(Keys.tabs.documents.newItem) : t(Keys.tabs.documents.newDocument);
if (getNewDocumentButtonState(editorState).visible) { if (getNewDocumentButtonState(editorState).visible) {
buttons.push({ buttons.push({
iconSrc: NewDocumentIcon, iconSrc: NewDocumentIcon,
@@ -368,7 +370,7 @@ export const getTabsButtons = ({
} }
if (getSaveNewDocumentButtonState(editorState).visible) { if (getSaveNewDocumentButtonState(editorState).visible) {
const label = "Save"; const label = t(Keys.common.save);
buttons.push({ buttons.push({
iconSrc: SaveIcon, iconSrc: SaveIcon,
iconAlt: label, iconAlt: label,
@@ -386,7 +388,7 @@ export const getTabsButtons = ({
} }
if (getDiscardNewDocumentChangesButtonState(editorState).visible) { if (getDiscardNewDocumentChangesButtonState(editorState).visible) {
const label = "Discard"; const label = t(Keys.common.discard);
buttons.push({ buttons.push({
iconSrc: DiscardIcon, iconSrc: DiscardIcon,
iconAlt: label, iconAlt: label,
@@ -403,7 +405,7 @@ export const getTabsButtons = ({
} }
if (getSaveExistingDocumentButtonState(editorState).visible) { if (getSaveExistingDocumentButtonState(editorState).visible) {
const label = "Update"; const label = t(Keys.common.update);
buttons.push({ buttons.push({
iconSrc: SaveIcon, iconSrc: SaveIcon,
iconAlt: label, iconAlt: label,
@@ -421,7 +423,7 @@ export const getTabsButtons = ({
} }
if (getDiscardExistingDocumentChangesButtonState(editorState).visible) { if (getDiscardExistingDocumentChangesButtonState(editorState).visible) {
const label = "Discard"; const label = t(Keys.common.discard);
buttons.push({ buttons.push({
iconSrc: DiscardIcon, iconSrc: DiscardIcon,
iconAlt: label, iconAlt: label,
@@ -438,7 +440,7 @@ export const getTabsButtons = ({
} }
if (selectedRows.size > 0) { if (selectedRows.size > 0) {
const label = "Delete"; const label = t(Keys.common.delete);
buttons.push({ buttons.push({
iconSrc: DeleteDocumentIcon, iconSrc: DeleteDocumentIcon,
iconAlt: label, iconAlt: label,
@@ -453,7 +455,7 @@ export const getTabsButtons = ({
} }
if (!isPreferredApiMongoDB) { if (!isPreferredApiMongoDB) {
const label = "Upload Item"; const label = t(Keys.tabs.documents.uploadItem);
buttons.push({ buttons.push({
id: UPLOAD_BUTTON_ID, id: UPLOAD_BUTTON_ID,
iconSrc: UploadIcon, iconSrc: UploadIcon,
@@ -737,17 +739,18 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} else if (result.statusCode >= 400) { } else if (result.statusCode >= 400) {
newFailed.push(result.documentId); newFailed.push(result.documentId);
logConsoleError( logConsoleError(
`Failed to delete document ${result.documentId.id()} with status code ${result.statusCode}`, t(Keys.tabs.documents.deleteDocumentFailedLog, {
documentId: result.documentId.id(),
statusCode: result.statusCode,
}),
); );
} }
}); });
logConsoleInfo(`Successfully deleted ${newSuccessful.length} document(s)`); logConsoleInfo(t(Keys.tabs.documents.deleteSuccessLog, { count: newSuccessful.length }));
if (newThrottled.length > 0) { if (newThrottled.length > 0) {
logConsoleError( logConsoleError(t(Keys.tabs.documents.deleteThrottledLog, { count: newThrottled.length }));
`Failed to delete ${newThrottled.length} document(s) due to "Request too large" (429) error. Retrying...`,
);
} }
// Update result of the bulk delete: method is called again, because the state variables changed // Update result of the bulk delete: method is called again, because the state variables changed
@@ -917,11 +920,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
useDialog useDialog
.getState() .getState()
.showOkCancelModalDialog( .showOkCancelModalDialog(
"Unsaved changes", t(Keys.tabs.documents.unsavedChanges),
"Your unsaved changes will be lost. Do you want to continue?", t(Keys.tabs.documents.unsavedChangesMessage),
"OK", t(Keys.common.ok),
onDiscard, onDiscard,
"Cancel", t(Keys.common.cancel),
onCancelDiscard, onCancelDiscard,
); );
} else { } else {
@@ -1011,7 +1014,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
(error) => { (error) => {
onExecutionErrorChange(true); onExecutionErrorChange(true);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Create document failed", errorMessage); useDialog.getState().showOkModalDialog(t(Keys.tabs.documents.createDocumentFailed), errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.CreateDocument, Action.CreateDocument,
{ {
@@ -1097,7 +1100,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
selectedDocumentId.partitionKeyValue = originalPartitionKeyValue; selectedDocumentId.partitionKeyValue = originalPartitionKeyValue;
onExecutionErrorChange(true); onExecutionErrorChange(true);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Update document failed", errorMessage); useDialog.getState().showOkModalDialog(t(Keys.tabs.documents.updateDocumentFailed), errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.UpdateDocument, Action.UpdateDocument,
{ {
@@ -1174,7 +1177,12 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
// Remove the check for systemKey, remove call to deleteNoSqlDocument(). deleteNoSqlDocuments() should // Remove the check for systemKey, remove call to deleteNoSqlDocument(). deleteNoSqlDocuments() should
// always be called for NoSQL. // always be called for NoSQL.
deletePromise = deleteNoSqlDocument(_collection, toDeleteDocumentIds[0]).then(() => { deletePromise = deleteNoSqlDocument(_collection, toDeleteDocumentIds[0]).then(() => {
useDialog.getState().showOkModalDialog("Delete document", "Document successfully deleted."); useDialog
.getState()
.showOkModalDialog(
t(Keys.tabs.documents.deleteDocumentDialogTitle),
t(Keys.tabs.documents.documentDeleted),
);
return [toDeleteDocumentIds[0]]; return [toDeleteDocumentIds[0]];
}); });
// ---------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------
@@ -1251,17 +1259,20 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
useDialog useDialog
.getState() .getState()
.showOkModalDialog( .showOkModalDialog(
"Delete documents", t(Keys.tabs.documents.deleteDocumentsDialogTitle),
`Some documents failed to delete due to a rate limiting error. Please try again later. To prevent this in the future, consider increasing the throughput on your container or database.`, t(Keys.tabs.documents.throttlingError),
{ {
linkText: "Learn More", linkText: t(Keys.common.learnMore),
linkUrl: MONGO_THROTTLING_DOC_URL, linkUrl: MONGO_THROTTLING_DOC_URL,
}, },
); );
} else { } else {
useDialog useDialog
.getState() .getState()
.showOkModalDialog("Delete documents", `Deleting document(s) failed (${error.message})`); .showOkModalDialog(
t(Keys.tabs.documents.deleteDocumentsDialogTitle),
t(Keys.tabs.documents.deleteFailed, { error: error.message }),
);
} }
}, },
) )
@@ -1275,21 +1286,21 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
const isPlural = selectedRows.size > 1; const isPlural = selectedRows.size > 1;
const documentName = !isPreferredApiMongoDB const documentName = !isPreferredApiMongoDB
? isPlural ? isPlural
? `the selected ${selectedRows.size} items` ? t(Keys.tabs.documents.selectedItems, { count: selectedRows.size })
: "the selected item" : t(Keys.tabs.documents.selectedItem)
: isPlural : isPlural
? `the selected ${selectedRows.size} documents` ? t(Keys.tabs.documents.selectedDocuments, { count: selectedRows.size })
: "the selected document"; : t(Keys.tabs.documents.selectedDocument);
const msg = `Are you sure you want to delete ${documentName}?`; const msg = t(Keys.tabs.documents.confirmDelete, { documentName });
useDialog useDialog
.getState() .getState()
.showOkCancelModalDialog( .showOkCancelModalDialog(
"Confirm delete", t(Keys.tabs.documents.confirmDeleteTitle),
msg, msg,
"Delete", t(Keys.common.delete),
() => deleteDocuments(Array.from(selectedRows).map((index) => documentIds[index as number])), () => deleteDocuments(Array.from(selectedRows).map((index) => documentIds[index as number])),
"Cancel", t(Keys.common.cancel),
undefined, undefined,
); );
}, [deleteDocuments, documentIds, isPreferredApiMongoDB, selectedRows]); }, [deleteDocuments, documentIds, isPreferredApiMongoDB, selectedRows]);
@@ -1823,8 +1834,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
const partitionKeyProperty = partitionKeyProperties?.[0]; const partitionKeyProperty = partitionKeyProperties?.[0];
if (partitionKeyProperty !== "_id" && !_hasShardKeySpecified(documentContent)) { if (partitionKeyProperty !== "_id" && !_hasShardKeySpecified(documentContent)) {
const message = `The document is lacking the shard property: ${partitionKeyProperty}`; const message = t(Keys.tabs.documents.missingShardProperty, { partitionKeyProperty });
useDialog.getState().showOkModalDialog("Create document failed", message); useDialog.getState().showOkModalDialog(t(Keys.tabs.documents.createDocumentFailed), message);
onExecutionErrorChange(true); onExecutionErrorChange(true);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.CreateDocument, Action.CreateDocument,
@@ -1835,7 +1846,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
}, },
startKey, startKey,
); );
Logger.logError("Failed to save new document: Document shard key not defined", "MongoDocumentsTab"); Logger.logError(t(Keys.tabs.documents.missingShardKeyLog), "MongoDocumentsTab");
throw new Error("Document without shard key"); throw new Error("Document without shard key");
} }
@@ -1878,7 +1889,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
(error) => { (error) => {
onExecutionErrorChange(true); onExecutionErrorChange(true);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Create document failed", errorMessage); useDialog.getState().showOkModalDialog(t(Keys.tabs.documents.createDocumentFailed), errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.CreateDocument, Action.CreateDocument,
{ {
@@ -1949,7 +1960,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
(error) => { (error) => {
onExecutionErrorChange(true); onExecutionErrorChange(true);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Update document failed", errorMessage); useDialog.getState().showOkModalDialog(t(Keys.tabs.documents.updateDocumentFailed), errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.UpdateDocument, Action.UpdateDocument,
{ {
@@ -2058,7 +2069,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
useDialog.getState().showOkModalDialog("Refresh documents grid failed", getErrorMessage(error)); useDialog.getState().showOkModalDialog(t(Keys.tabs.documents.refreshGridFailed), getErrorMessage(error));
} }
}, },
[createIterator, filterContent], [createIterator, filterContent],
@@ -2070,18 +2081,17 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
* @returns 429 warning message * @returns 429 warning message
*/ */
const get429WarningMessageNoSql = (): string => { const get429WarningMessageNoSql = (): string => {
let message = 'Some delete requests failed due to a "Request too large" exception (429)'; let message = t(Keys.tabs.documents.requestTooLargeBase);
if (bulkDeleteOperation.count === bulkDeleteProcess.successfulIds.length) { if (bulkDeleteOperation.count === bulkDeleteProcess.successfulIds.length) {
message += ", but were successfully retried."; message += ", " + t(Keys.tabs.documents.retriedSuccessfully);
} else if (bulkDeleteMode === "inProgress" || bulkDeleteMode === "aborting") { } else if (bulkDeleteMode === "inProgress" || bulkDeleteMode === "aborting") {
message += ". Retrying now."; message += ". " + t(Keys.tabs.documents.retryingNow);
} else { } else {
message += "."; message += ".";
} }
return (message += return (message += " " + t(Keys.tabs.documents.increaseThroughputTip));
" To prevent this in the future, consider increasing the throughput on your container or database.");
}; };
const onColumnSelectionChange = (newSelectedColumnIds: string[]): void => { const onColumnSelectionChange = (newSelectedColumnIds: string[]): void => {
@@ -2128,7 +2138,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
const nonBlankLastFilters = lastFilterContents.filter((filter) => filter.trim() !== ""); const nonBlankLastFilters = lastFilterContents.filter((filter) => filter.trim() !== "");
if (nonBlankLastFilters.length > 0) { if (nonBlankLastFilters.length > 0) {
options.push({ options.push({
label: "Saved filters", label: t(Keys.tabs.documents.savedFilters),
options: nonBlankLastFilters, options: nonBlankLastFilters,
}); });
} }
@@ -2157,14 +2167,14 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
dropdownOptions={getFilterChoices()} dropdownOptions={getFilterChoices()}
placeholder={ placeholder={
isPreferredApiMongoDB isPreferredApiMongoDB
? "Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents." ? t(Keys.tabs.documents.mongoFilterPlaceholder)
: "Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents." : t(Keys.tabs.documents.sqlFilterPlaceholder)
} }
title="Type a query predicate or choose one from the list." title={t(Keys.tabs.documents.filterTooltip)}
value={filterContent} value={filterContent}
onChange={updateFilterContent} onChange={updateFilterContent}
onKeyDown={onFilterKeyDown} onKeyDown={onFilterKeyDown}
bottomLink={{ text: "Learn more", url: DATA_EXPLORER_DOC_URL }} bottomLink={{ text: t(Keys.common.learnMore), url: DATA_EXPLORER_DOC_URL }}
/> />
<Button <Button
appearance="primary" appearance="primary"
@@ -2180,10 +2190,12 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} }
}} }}
disabled={isExecuting && isPreferredApiMongoDB} disabled={isExecuting && isPreferredApiMongoDB}
aria-label={!isExecuting || isPreferredApiMongoDB ? "Apply filter" : "Cancel"} aria-label={
!isExecuting || isPreferredApiMongoDB ? t(Keys.tabs.documents.applyFilter) : t(Keys.common.cancel)
}
tabIndex={0} tabIndex={0}
> >
{!isExecuting || isPreferredApiMongoDB ? "Apply Filter" : "Cancel"} {!isExecuting || isPreferredApiMongoDB ? t(Keys.tabs.documents.applyFilter) : t(Keys.common.cancel)}
</Button> </Button>
</div> </div>
<Allotment <Allotment
@@ -2227,14 +2239,14 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
</div> </div>
{tableContainerSizePx?.width >= calculateOffset(selectedColumnIds.length) + 200 && ( {tableContainerSizePx?.width >= calculateOffset(selectedColumnIds.length) + 200 && (
<div <div
title="Refresh" title={t(Keys.common.refresh)}
className={styles.refreshBtn} className={styles.refreshBtn}
role="button" role="button"
onClick={() => refreshDocumentsGrid(false)} onClick={() => refreshDocumentsGrid(false)}
aria-label="Refresh" aria-label={t(Keys.common.refresh)}
tabIndex={0} tabIndex={0}
> >
<img src={RefreshIcon} alt="Refresh" /> <img src={RefreshIcon} alt={t(Keys.common.refresh)} />
</div> </div>
)} )}
</div> </div>
@@ -2247,7 +2259,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
onClick={() => loadNextPage(documentsIterator.iterator, false)} onClick={() => loadNextPage(documentsIterator.iterator, false)}
onKeyDown={onLoadMoreKeyInput} onKeyDown={onLoadMoreKeyInput}
> >
Load more {t(Keys.tabs.documents.loadMore)}
</a> </a>
)} )}
</div> </div>
@@ -2259,7 +2271,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
language={"json"} language={"json"}
content={selectedDocumentContent} content={selectedDocumentContent}
isReadOnly={false} isReadOnly={false}
ariaLabel={"Document editor"} ariaLabel={t(Keys.tabs.documents.documentEditor)}
lineNumbers={"on"} lineNumbers={"on"}
theme={"_theme"} theme={"_theme"}
onContentChanged={_onEditorContentChange} onContentChanged={_onEditorContentChange}
@@ -2267,7 +2279,9 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
/> />
)} )}
{selectedRows.size > 1 && ( {selectedRows.size > 1 && (
<span style={{ margin: 10 }}>Number of selected documents: {selectedRows.size}</span> <span style={{ margin: 10 }}>
{t(Keys.tabs.documents.numberOfSelectedDocuments, { count: selectedRows.size })}
</span>
)} )}
</div> </div>
</Allotment.Pane> </Allotment.Pane>
@@ -2276,42 +2290,43 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
{bulkDeleteOperation && ( {bulkDeleteOperation && (
<ProgressModalDialog <ProgressModalDialog
isOpen={isBulkDeleteDialogOpen} isOpen={isBulkDeleteDialogOpen}
dismissText="Abort" dismissText={t(Keys.tabs.documents.abort)}
onDismiss={() => { onDismiss={() => {
setIsBulkDeleteDialogOpen(false); setIsBulkDeleteDialogOpen(false);
setBulkDeleteOperation(undefined); setBulkDeleteOperation(undefined);
}} }}
onCancel={() => setBulkDeleteMode("aborting")} onCancel={() => setBulkDeleteMode("aborting")}
title={`Deleting ${bulkDeleteOperation.count} document(s)`} title={t(Keys.tabs.documents.deletingDocuments, { count: bulkDeleteOperation.count })}
message={`Successfully deleted ${bulkDeleteProcess.successfulIds.length} document(s).`} message={t(Keys.tabs.documents.deletedDocumentsSuccess, { count: bulkDeleteProcess.successfulIds.length })}
maxValue={bulkDeleteOperation.count} maxValue={bulkDeleteOperation.count}
value={bulkDeleteProcess.successfulIds.length} value={bulkDeleteProcess.successfulIds.length}
mode={bulkDeleteMode} mode={bulkDeleteMode}
> >
<div className={styles.deleteProgressContent}> <div className={styles.deleteProgressContent}>
{(bulkDeleteMode === "aborting" || bulkDeleteMode === "aborted") && ( {(bulkDeleteMode === "aborting" || bulkDeleteMode === "aborted") && (
<div style={{ paddingBottom: tokens.spacingVerticalL }}>Deleting document(s) was aborted.</div> <div style={{ paddingBottom: tokens.spacingVerticalL }}>{t(Keys.tabs.documents.deleteAborted)}</div>
)} )}
{(bulkDeleteProcess.failedIds.length > 0 || {(bulkDeleteProcess.failedIds.length > 0 ||
(bulkDeleteProcess.throttledIds.length > 0 && bulkDeleteMode !== "inProgress")) && ( (bulkDeleteProcess.throttledIds.length > 0 && bulkDeleteMode !== "inProgress")) && (
<MessageBar intent="error" style={{ marginBottom: tokens.spacingVerticalL }}> <MessageBar intent="error" style={{ marginBottom: tokens.spacingVerticalL }}>
<MessageBarBody> <MessageBarBody>
<MessageBarTitle>Error</MessageBarTitle> <MessageBarTitle>{t(Keys.tabs.documents.error)}</MessageBarTitle>
Failed to delete{" "} {t(Keys.tabs.documents.failedToDeleteDocuments, {
{bulkDeleteMode === "inProgress" count:
? bulkDeleteProcess.failedIds.length bulkDeleteMode === "inProgress"
: bulkDeleteProcess.failedIds.length + bulkDeleteProcess.throttledIds.length}{" "} ? bulkDeleteProcess.failedIds.length
document(s). : bulkDeleteProcess.failedIds.length + bulkDeleteProcess.throttledIds.length,
})}
</MessageBarBody> </MessageBarBody>
</MessageBar> </MessageBar>
)} )}
{bulkDeleteProcess.hasBeenThrottled && ( {bulkDeleteProcess.hasBeenThrottled && (
<MessageBar intent="warning"> <MessageBar intent="warning">
<MessageBarBody> <MessageBarBody>
<MessageBarTitle>Warning</MessageBarTitle> <MessageBarTitle>{t(Keys.tabs.documents.warning)}</MessageBarTitle>
{get429WarningMessageNoSql()}{" "} {get429WarningMessageNoSql()}{" "}
<Link href={NO_SQL_THROTTLING_DOC_URL} target="_blank"> <Link href={NO_SQL_THROTTLING_DOC_URL} target="_blank">
Learn More {t(Keys.common.learnMore)}
</Link> </Link>
</MessageBarBody> </MessageBarBody>
</MessageBar> </MessageBar>

View File

@@ -44,13 +44,13 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
} }
onChange={[Function]} onChange={[Function]}
onKeyDown={[Function]} onKeyDown={[Function]}
placeholder="Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents." placeholder="Type a query predicate (e.g., WHERE c.id="1"), or choose one from the drop down list, or leave empty to query all documents."
title="Type a query predicate or choose one from the list." title="Type a query predicate or choose one from the list."
value="" value=""
/> />
<Button <Button
appearance="primary" appearance="primary"
aria-label="Apply filter" aria-label="Apply Filter"
data-test="DocumentsTab/ApplyFilter" data-test="DocumentsTab/ApplyFilter"
disabled={false} disabled={false}
onClick={[Function]} onClick={[Function]}

View File

@@ -1,6 +1,8 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { configContext } from "../../../ConfigContext"; import { configContext } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { Keys } from "../../../Localization/Keys.generated";
import { t } from "../../../Localization/t";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
@@ -212,7 +214,7 @@ export default class MongoShellTabComponent extends Component<
src={this.state.url} src={this.state.url}
id={this.props.tabsBaseInstance.tabId} id={this.props.tabsBaseInstance.tabId}
onLoad={(event) => this.setContentFocus(event)} onLoad={(event) => this.setContentFocus(event)}
title="Mongo Shell" title={t(Keys.tabs.mongoShell.title)}
role="tabpanel" role="tabpanel"
></iframe> ></iframe>
); );

View File

@@ -17,6 +17,8 @@ import { QueryTabStyles, useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil"; import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import { Keys } from "Localization/Keys.generated";
import { t } from "Localization/t";
import { QueryConstants } from "Shared/Constants"; import { QueryConstants } from "Shared/Constants";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
@@ -315,7 +317,9 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
}; };
public onSaveQueryClick = (): void => { public onSaveQueryClick = (): void => {
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />); useSidePanel
.getState()
.openSidePanel(t(Keys.tabs.query.saveQuery), <SaveQueryPane explorer={this.props.collection.container} />);
}; };
public launchQueryCopilotChat = (): void => { public launchQueryCopilotChat = (): void => {
@@ -325,7 +329,10 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
public onSavedQueriesClick = (): void => { public onSavedQueriesClick = (): void => {
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.props.collection.container} />); .openSidePanel(
t(Keys.tabs.query.openSavedQueries),
<BrowseQueriesPane explorer={this.props.collection.container} />,
);
}; };
public toggleResult(): void { public toggleResult(): void {
@@ -473,7 +480,8 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
if (this.executeQueryButton.visible) { if (this.executeQueryButton.visible) {
const label = this.state.selectedContent?.length > 0 ? "Execute Selection" : "Execute Query"; const label =
this.state.selectedContent?.length > 0 ? t(Keys.tabs.query.executeSelection) : t(Keys.tabs.query.executeQuery);
buttons.push({ buttons.push({
iconSrc: ExecuteQueryIcon, iconSrc: ExecuteQueryIcon,
iconAlt: label, iconAlt: label,
@@ -490,7 +498,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
if (this.saveQueryButton.visible) { if (this.saveQueryButton.visible) {
if (configContext.platform !== Platform.Fabric) { if (configContext.platform !== Platform.Fabric) {
const label = "Save Query"; const label = t(Keys.tabs.query.saveQuery);
buttons.push({ buttons.push({
iconSrc: SaveQueryIcon, iconSrc: SaveQueryIcon,
iconAlt: label, iconAlt: label,
@@ -507,11 +515,11 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
buttons.push({ buttons.push({
iconSrc: DownloadQueryIcon, iconSrc: DownloadQueryIcon,
iconAlt: "Download Query", iconAlt: t(Keys.tabs.query.downloadQuery),
keyboardAction: KeyboardAction.DOWNLOAD_ITEM, keyboardAction: KeyboardAction.DOWNLOAD_ITEM,
onCommandClick: this.onDownloadQueryClick, onCommandClick: this.onDownloadQueryClick,
commandButtonLabel: "Download Query", commandButtonLabel: t(Keys.tabs.query.downloadQuery),
ariaLabel: "Download Query", ariaLabel: t(Keys.tabs.query.downloadQuery),
hasPopup: false, hasPopup: false,
disabled: !this.saveQueryButton.enabled, disabled: !this.saveQueryButton.enabled,
}); });
@@ -568,7 +576,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
// } // }
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) { if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
const label = "Cancel query"; const label = t(Keys.tabs.query.cancelQuery);
buttons.push({ buttons.push({
iconSrc: CancelQueryIcon, iconSrc: CancelQueryIcon,
iconAlt: label, iconAlt: label,
@@ -589,23 +597,23 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
const verticalButton: CommandButtonComponentProps = { const verticalButton: CommandButtonComponentProps = {
isSelected: this.state.queryResultsView === SplitterDirection.Vertical, isSelected: this.state.queryResultsView === SplitterDirection.Vertical,
iconSrc: this.state.queryResultsView === SplitterDirection.Vertical ? CheckIcon : undefined, iconSrc: this.state.queryResultsView === SplitterDirection.Vertical ? CheckIcon : undefined,
commandButtonLabel: "Vertical", commandButtonLabel: t(Keys.tabs.query.vertical),
ariaLabel: "Vertical", ariaLabel: t(Keys.tabs.query.vertical),
onCommandClick: () => this._setViewLayout(SplitterDirection.Vertical), onCommandClick: () => this._setViewLayout(SplitterDirection.Vertical),
hasPopup: false, hasPopup: false,
}; };
const horizontalButton: CommandButtonComponentProps = { const horizontalButton: CommandButtonComponentProps = {
isSelected: this.state.queryResultsView === SplitterDirection.Horizontal, isSelected: this.state.queryResultsView === SplitterDirection.Horizontal,
iconSrc: this.state.queryResultsView === SplitterDirection.Horizontal ? CheckIcon : undefined, iconSrc: this.state.queryResultsView === SplitterDirection.Horizontal ? CheckIcon : undefined,
commandButtonLabel: "Horizontal", commandButtonLabel: t(Keys.tabs.query.horizontal),
ariaLabel: "Horizontal", ariaLabel: t(Keys.tabs.query.horizontal),
onCommandClick: () => this._setViewLayout(SplitterDirection.Horizontal), onCommandClick: () => this._setViewLayout(SplitterDirection.Horizontal),
hasPopup: false, hasPopup: false,
}; };
return { return {
commandButtonLabel: "View", commandButtonLabel: t(Keys.tabs.query.view),
ariaLabel: "View", ariaLabel: t(Keys.tabs.query.view),
hasPopup: true, hasPopup: true,
children: [verticalButton, horizontalButton], children: [verticalButton, horizontalButton],
}; };
@@ -782,7 +790,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
modelMarkers={this.state.modelMarkers} modelMarkers={this.state.modelMarkers}
isReadOnly={false} isReadOnly={false}
wordWrap={"on"} wordWrap={"on"}
ariaLabel={"Editing Query"} ariaLabel={t(Keys.tabs.query.editingQuery)}
lineNumbers={"on"} lineNumbers={"on"}
theme={this.props.monacoTheme} theme={this.props.monacoTheme}
onContentChanged={(newContent: string) => this.onChangeContent(newContent)} onContentChanged={(newContent: string) => this.onChangeContent(newContent)}

View File

@@ -11,6 +11,8 @@ import { createStoredProcedure } from "../../../Common/dataAccess/createStoredPr
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure"; import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure"; import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { Keys } from "../../../Localization/Keys.generated";
import { t } from "../../../Localization/t";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole"; import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
import { useTabs } from "../../../hooks/useTabs"; import { useTabs } from "../../../hooks/useTabs";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
@@ -133,7 +135,7 @@ export default class StoredProcedureTabComponent extends React.Component<
this.collection = this.props.collection; this.collection = this.props.collection;
this.executeResultsEditorId = `executestoredprocedureresults${this.props.scriptTabBaseInstance.tabId}`; this.executeResultsEditorId = `executestoredprocedureresults${this.props.scriptTabBaseInstance.tabId}`;
this.executeLogsEditorId = `executestoredprocedurelogs${this.props.scriptTabBaseInstance.tabId}`; this.executeLogsEditorId = `executestoredprocedurelogs${this.props.scriptTabBaseInstance.tabId}`;
this.props.scriptTabBaseInstance.ariaLabel("Stored Procedure Body"); this.props.scriptTabBaseInstance.ariaLabel(t(Keys.tabs.storedProcedure.body));
this.props.iStorProcTabComponentAccessor({ this.props.iStorProcTabComponentAccessor({
onExecuteSprocsResultEvent: this.onExecuteSprocsResult.bind(this), onExecuteSprocsResultEvent: this.onExecuteSprocsResult.bind(this),
@@ -318,7 +320,7 @@ export default class StoredProcedureTabComponent extends React.Component<
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
const label = "Save"; const label = t(Keys.common.save);
if (this.state.saveButton.visible) { if (this.state.saveButton.visible) {
buttons.push({ buttons.push({
iconSrc: SaveIcon, iconSrc: SaveIcon,
@@ -333,7 +335,7 @@ export default class StoredProcedureTabComponent extends React.Component<
} }
if (this.state.updateButton.visible) { if (this.state.updateButton.visible) {
const label = "Update"; const label = t(Keys.common.update);
buttons.push({ buttons.push({
iconSrc: SaveIcon, iconSrc: SaveIcon,
iconAlt: label, iconAlt: label,
@@ -347,7 +349,7 @@ export default class StoredProcedureTabComponent extends React.Component<
} }
if (this.state.discardButton.visible) { if (this.state.discardButton.visible) {
const label = "Discard"; const label = t(Keys.common.discard);
buttons.push({ buttons.push({
iconSrc: DiscardIcon, iconSrc: DiscardIcon,
iconAlt: label, iconAlt: label,
@@ -361,7 +363,7 @@ export default class StoredProcedureTabComponent extends React.Component<
} }
if (this.state.executeButton.visible) { if (this.state.executeButton.visible) {
const label = "Execute"; const label = t(Keys.common.execute);
buttons.push({ buttons.push({
iconSrc: ExecuteQueryIcon, iconSrc: ExecuteQueryIcon,
iconAlt: label, iconAlt: label,
@@ -519,7 +521,7 @@ export default class StoredProcedureTabComponent extends React.Component<
<div className="tab-pane flexContainer stored-procedure-tab" role="tabpanel"> <div className="tab-pane flexContainer stored-procedure-tab" role="tabpanel">
<div className="storedTabForm flexContainer"> <div className="storedTabForm flexContainer">
<div className="formTitleFirst"> <div className="formTitleFirst">
Stored Procedure Id {t(Keys.tabs.storedProcedure.id)}
<span className="mandatoryStar" style={{ color: "#ff0707", fontSize: "14px", fontWeight: "bold" }}> <span className="mandatoryStar" style={{ color: "#ff0707", fontSize: "14px", fontWeight: "bold" }}>
*&nbsp; *&nbsp;
</span> </span>
@@ -531,28 +533,28 @@ export default class StoredProcedureTabComponent extends React.Component<
required required
pattern={ValidCosmosDbIdInputPattern.source} pattern={ValidCosmosDbIdInputPattern.source}
title={ValidCosmosDbIdDescription} title={ValidCosmosDbIdDescription}
aria-label="Stored procedure id" aria-label={t(Keys.tabs.storedProcedure.idAriaLabel)}
placeholder="Enter the new stored procedure id" placeholder={t(Keys.tabs.storedProcedure.idPlaceholder)}
size={40} size={40}
value={this.state.id} value={this.state.id}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => this.handleIdOnChange(event)} onChange={(event: React.ChangeEvent<HTMLInputElement>) => this.handleIdOnChange(event)}
/> />
</span> </span>
<div className="spUdfTriggerHeader">Stored Procedure Body</div> <div className="spUdfTriggerHeader">{t(Keys.tabs.storedProcedure.body)}</div>
<EditorReact <EditorReact
language={"javascript"} language={"javascript"}
content={this.state.sProcEditorContent} content={this.state.sProcEditorContent}
isReadOnly={false} isReadOnly={false}
ariaLabel={"Stored procedure body"} ariaLabel={t(Keys.tabs.storedProcedure.bodyAriaLabel)}
lineNumbers={"on"} lineNumbers={"on"}
theme={"_theme"} theme={"_theme"}
onContentChanged={(newContent: string) => this.onChangeContent(newContent)} onContentChanged={(newContent: string) => this.onChangeContent(newContent)}
/> />
{this.state.hasResults && ( {this.state.hasResults && (
<div className="results-container"> <div className="results-container">
<Pivot aria-label="Successful execution of stored procedure" style={{ height: "100%" }}> <Pivot aria-label={t(Keys.tabs.storedProcedure.successfulExecution)} style={{ height: "100%" }}>
<PivotItem <PivotItem
headerText="Result" headerText={t(Keys.common.result)}
headerButtonProps={{ headerButtonProps={{
"data-order": 1, "data-order": 1,
"data-title": "Result", "data-title": "Result",
@@ -563,11 +565,11 @@ export default class StoredProcedureTabComponent extends React.Component<
language={"javascript"} language={"javascript"}
content={this.state.resultData} content={this.state.resultData}
isReadOnly={true} isReadOnly={true}
ariaLabel={"Execute stored procedure result"} ariaLabel={t(Keys.tabs.storedProcedure.resultAriaLabel)}
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerText="console.log" headerText={t(Keys.tabs.storedProcedure.consoleLogTab)}
headerButtonProps={{ headerButtonProps={{
"data-order": 2, "data-order": 2,
"data-title": "console.log", "data-title": "console.log",
@@ -578,7 +580,7 @@ export default class StoredProcedureTabComponent extends React.Component<
language={"javascript"} language={"javascript"}
content={this.state.logsData} content={this.state.logsData}
isReadOnly={true} isReadOnly={true}
ariaLabel={"Execute stored procedure logs"} ariaLabel={t(Keys.tabs.storedProcedure.logsAriaLabel)}
/> />
</PivotItem> </PivotItem>
</Pivot> </Pivot>
@@ -586,16 +588,16 @@ export default class StoredProcedureTabComponent extends React.Component<
)} )}
{this.state.hasErrors && ( {this.state.hasErrors && (
<div className="errors-container"> <div className="errors-container">
<div className="errors-header">Errors:</div> <div className="errors-header">{t(Keys.tabs.storedProcedure.errors)}</div>
<div className="errorContent"> <div className="errorContent">
<span className="errorMessage">{this.state.error}</span> <span className="errorMessage">{this.state.error}</span>
<span className="errorDetailsLink"> <span className="errorDetailsLink">
<a <a
aria-label="Error details link" aria-label={t(Keys.tabs.storedProcedure.errorDetailsAriaLabel)}
onClick={() => this.onErrorDetailsClick()} onClick={() => this.onErrorDetailsClick()}
onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) => this.onErrorDetailsKeyPress(event)} onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) => this.onErrorDetailsKeyPress(event)}
> >
More details {t(Keys.tabs.storedProcedure.moreDetails)}
</a> </a>
</span> </span>
</div> </div>

View File

@@ -11,6 +11,8 @@ import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"
import { createTrigger } from "../../Common/dataAccess/createTrigger"; import { createTrigger } from "../../Common/dataAccess/createTrigger";
import { updateTrigger } from "../../Common/dataAccess/updateTrigger"; import { updateTrigger } from "../../Common/dataAccess/updateTrigger";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { Keys } from "../../Localization/Keys.generated";
import { t } from "../../Localization/t";
import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types"; import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
@@ -19,8 +21,8 @@ import { EditorReact } from "../Controls/Editor/EditorReact";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import TriggerTab from "./TriggerTab"; import TriggerTab from "./TriggerTab";
const triggerTypeOptions: IDropdownOption[] = [ const triggerTypeOptions: IDropdownOption[] = [
{ key: "Pre", text: "Pre" }, { key: "Pre", text: t(Keys.tabs.trigger.pre) },
{ key: "Post", text: "Post" }, { key: "Post", text: t(Keys.tabs.trigger.post) },
]; ];
const dropdownStyles: Partial<IDropdownStyles> = { const dropdownStyles: Partial<IDropdownStyles> = {
@@ -147,10 +149,10 @@ const dropdownStyles: Partial<IDropdownStyles> = {
}; };
const triggerOperationOptions: IDropdownOption[] = [ const triggerOperationOptions: IDropdownOption[] = [
{ key: "All", text: "All" }, { key: "All", text: t(Keys.tabs.trigger.all) },
{ key: "Create", text: "Create" }, { key: "Create", text: t(Keys.tabs.trigger.operationCreate) },
{ key: "Delete", text: "Delete" }, { key: "Delete", text: t(Keys.tabs.trigger.operationDelete) },
{ key: "Replace", text: "Replace" }, { key: "Replace", text: t(Keys.tabs.trigger.operationReplace) },
]; ];
interface Ibutton { interface Ibutton {
@@ -334,7 +336,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
const label = "Save"; const label = t(Keys.common.save);
if (this.saveButton.visible) { if (this.saveButton.visible) {
buttons.push({ buttons.push({
setState: this.setState, setState: this.setState,
@@ -351,7 +353,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
} }
if (this.updateButton.visible) { if (this.updateButton.visible) {
const label = "Update"; const label = t(Keys.common.update);
buttons.push({ buttons.push({
...this, ...this,
iconSrc: SaveIcon, iconSrc: SaveIcon,
@@ -366,7 +368,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
} }
if (this.discardButton.visible) { if (this.discardButton.visible) {
const label = "Discard"; const label = t(Keys.common.discard);
buttons.push({ buttons.push({
setState: this.setState, setState: this.setState,
...this, ...this,
@@ -415,14 +417,14 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
<div className="tab-pane flexContainer trigger-form" role="tabpanel"> <div className="tab-pane flexContainer trigger-form" role="tabpanel">
<TextField <TextField
className="trigger-field" className="trigger-field"
label="Trigger Id" label={t(Keys.tabs.trigger.id)}
id="entityTimeId" id="entityTimeId"
autoFocus autoFocus
required required
type="text" type="text"
pattern={ValidCosmosDbIdInputPattern.source} pattern={ValidCosmosDbIdInputPattern.source}
title={ValidCosmosDbIdDescription} title={ValidCosmosDbIdDescription}
placeholder="Enter the new trigger id" placeholder={t(Keys.tabs.trigger.idPlaceholder)}
size={40} size={40}
value={triggerId} value={triggerId}
readOnly={!isIdEditable} readOnly={!isIdEditable}
@@ -446,8 +448,8 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
}} }}
/> />
<Dropdown <Dropdown
placeholder="Trigger Type" placeholder={t(Keys.tabs.trigger.type)}
label="Trigger Type" label={t(Keys.tabs.trigger.type)}
options={triggerTypeOptions} options={triggerTypeOptions}
selectedKey={triggerType} selectedKey={triggerType}
className="trigger-field" className="trigger-field"
@@ -455,8 +457,8 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
styles={dropdownStyles} styles={dropdownStyles}
/> />
<Dropdown <Dropdown
placeholder="Trigger Operation" placeholder={t(Keys.tabs.trigger.operation)}
label="Trigger Operation" label={t(Keys.tabs.trigger.operation)}
selectedKey={triggerOperation} selectedKey={triggerOperation}
options={triggerOperationOptions} options={triggerOperationOptions}
className="trigger-field" className="trigger-field"
@@ -465,12 +467,12 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
} }
styles={dropdownStyles} styles={dropdownStyles}
/> />
<Label className="trigger-field">Trigger Body</Label> <Label className="trigger-field">{t(Keys.tabs.trigger.body)}</Label>
<EditorReact <EditorReact
language={"json"} language={"json"}
content={triggerBody} content={triggerBody}
isReadOnly={false} isReadOnly={false}
ariaLabel={"Graph JSON"} ariaLabel={t(Keys.tabs.trigger.bodyAriaLabel)}
onContentChanged={this.handleTriggerBodyChange} onContentChanged={this.handleTriggerBodyChange}
/> />
</div> </div>

View File

@@ -12,6 +12,8 @@ import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction"; import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction"; import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { Keys } from "../../Localization/Keys.generated";
import { t } from "../../Localization/t";
import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
@@ -82,7 +84,7 @@ export default class UserDefinedFunctionTabContent extends Component<
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
const label = "Save"; const label = t(Keys.common.save);
if (this.saveButton.visible) { if (this.saveButton.visible) {
buttons.push({ buttons.push({
...this, ...this,
@@ -99,7 +101,7 @@ export default class UserDefinedFunctionTabContent extends Component<
} }
if (this.updateButton.visible) { if (this.updateButton.visible) {
const label = "Update"; const label = t(Keys.common.update);
buttons.push({ buttons.push({
...this, ...this,
iconSrc: SaveIcon, iconSrc: SaveIcon,
@@ -114,7 +116,7 @@ export default class UserDefinedFunctionTabContent extends Component<
} }
if (this.discardButton.visible) { if (this.discardButton.visible) {
const label = "Discard"; const label = t(Keys.common.discard);
buttons.push({ buttons.push({
setState: this.setState, setState: this.setState,
...this, ...this,
@@ -265,7 +267,7 @@ export default class UserDefinedFunctionTabContent extends Component<
<FluentProvider theme={currentTheme}> <FluentProvider theme={currentTheme}>
<TextField <TextField
className="trigger-field" className="trigger-field"
label="User Defined Function Id" label={t(Keys.tabs.udf.id)}
id="entityTimeId" id="entityTimeId"
autoFocus autoFocus
required required
@@ -273,7 +275,7 @@ export default class UserDefinedFunctionTabContent extends Component<
type="text" type="text"
pattern={ValidCosmosDbIdInputPattern.source} pattern={ValidCosmosDbIdInputPattern.source}
title={ValidCosmosDbIdDescription} title={ValidCosmosDbIdDescription}
placeholder="Enter the new user defined function id" placeholder={t(Keys.tabs.udf.idPlaceholder)}
size={40} size={40}
value={udfId} value={udfId}
onChange={this.handleUdfIdChange} onChange={this.handleUdfIdChange}
@@ -299,12 +301,12 @@ export default class UserDefinedFunctionTabContent extends Component<
}} }}
/>{" "} />{" "}
</FluentProvider> </FluentProvider>
<Label className="trigger-field">User Defined Function Body</Label> <Label className="trigger-field">{t(Keys.tabs.udf.body)}</Label>
<EditorReact <EditorReact
language={"javascript"} language={"javascript"}
content={udfBody} content={udfBody}
isReadOnly={false} isReadOnly={false}
ariaLabel={"User defined function body"} ariaLabel={t(Keys.tabs.udf.bodyAriaLabel)}
onContentChanged={this.handleUdfBodyChange} onContentChanged={this.handleUdfBodyChange}
/> />
</div> </div>

View File

@@ -291,5 +291,126 @@
"errorCreateContainer": "Failed to create container: {{error}}", "errorCreateContainer": "Failed to create container: {{error}}",
"errorImportData": "Failed to import data: {{error}}" "errorImportData": "Failed to import data: {{error}}"
} }
},
"contextMenu": {
"newContainer": "New {{containerName}}",
"restoreContainer": "Restore {{containerName}}",
"deleteDatabase": "Delete {{databaseName}}",
"deleteContainer": "Delete {{containerName}}",
"newSqlQuery": "New SQL Query",
"newQuery": "New Query",
"openMongoShell": "Open Mongo Shell",
"newShell": "New Shell",
"openCassandraShell": "Open Cassandra Shell",
"newStoredProcedure": "New Stored Procedure",
"newUdf": "New UDF",
"newTrigger": "New Trigger",
"deleteStoredProcedure": "Delete Stored Procedure",
"deleteTrigger": "Delete Trigger",
"deleteUdf": "Delete User Defined Function"
},
"tabs": {
"documents": {
"newItem": "New Item",
"newDocument": "New Document",
"uploadItem": "Upload Item",
"applyFilter": "Apply Filter",
"unsavedChanges": "Unsaved changes",
"unsavedChangesMessage": "Your unsaved changes will be lost. Do you want to continue?",
"createDocumentFailed": "Create document failed",
"updateDocumentFailed": "Update document failed",
"documentDeleted": "Document successfully deleted.",
"deleteDocumentDialogTitle": "Delete document",
"deleteDocumentsDialogTitle": "Delete documents",
"throttlingError": "Some documents failed to delete due to a rate limiting error. Please try again later. To prevent this in the future, consider increasing the throughput on your container or database.",
"deleteFailed": "Deleting document(s) failed ({{error}})",
"missingShardProperty": "The document is lacking the shard property: {{partitionKeyProperty}}",
"refreshGridFailed": "Refresh documents grid failed",
"confirmDelete": "Are you sure you want to delete {{documentName}}?",
"confirmDeleteTitle": "Confirm delete",
"selectedItems": "the selected {{count}} items",
"selectedItem": "the selected item",
"selectedDocuments": "the selected {{count}} documents",
"selectedDocument": "the selected document",
"deleteDocumentFailedLog": "Failed to delete document {{documentId}} with status code {{statusCode}}",
"deleteSuccessLog": "Successfully deleted {{count}} document(s)",
"deleteThrottledLog": "Failed to delete {{count}} document(s) due to \"Request too large\" (429) error. Retrying...",
"missingShardKeyLog": "Failed to save new document: Document shard key not defined",
"filterTooltip": "Type a query predicate or choose one from the list.",
"loadMore": "Load more",
"documentEditor": "Document editor",
"savedFilters": "Saved filters",
"defaultFilters": "Default filters",
"abort": "Abort",
"deletingDocuments": "Deleting {{count}} document(s)",
"deletedDocumentsSuccess": "Successfully deleted {{count}} document(s).",
"deleteAborted": "Deleting document(s) was aborted.",
"failedToDeleteDocuments": "Failed to delete {{count}} document(s).",
"requestTooLargeBase": "Some delete requests failed due to a \"Request too large\" exception (429)",
"retriedSuccessfully": "but were successfully retried.",
"retryingNow": "Retrying now.",
"increaseThroughputTip": "To prevent this in the future, consider increasing the throughput on your container or database.",
"numberOfSelectedDocuments": "Number of selected documents: {{count}}",
"mongoFilterPlaceholder": "Type a query predicate (e.g., {\"id\":\"foo\"}), or choose one from the drop down list, or leave empty to query all documents.",
"sqlFilterPlaceholder": "Type a query predicate (e.g., WHERE c.id=\"1\"), or choose one from the drop down list, or leave empty to query all documents.",
"error": "Error",
"warning": "Warning"
},
"query": {
"executeQuery": "Execute Query",
"executeSelection": "Execute Selection",
"saveQuery": "Save Query",
"downloadQuery": "Download Query",
"cancelQuery": "Cancel query",
"openSavedQueries": "Open Saved Queries",
"vertical": "Vertical",
"horizontal": "Horizontal",
"view": "View",
"editingQuery": "Editing Query"
},
"storedProcedure": {
"id": "Stored Procedure Id",
"idPlaceholder": "Enter the new stored procedure id",
"idAriaLabel": "Stored procedure id",
"body": "Stored Procedure Body",
"bodyAriaLabel": "Stored procedure body",
"successfulExecution": "Successful execution of stored procedure",
"resultAriaLabel": "Execute stored procedure result",
"logsAriaLabel": "Execute stored procedure logs",
"errors": "Errors:",
"errorDetailsAriaLabel": "Error details link",
"moreDetails": "More details",
"consoleLogTab": "console.log"
},
"trigger": {
"id": "Trigger Id",
"idPlaceholder": "Enter the new trigger id",
"type": "Trigger Type",
"operation": "Trigger Operation",
"body": "Trigger Body",
"bodyAriaLabel": "Trigger body",
"pre": "Pre",
"post": "Post",
"all": "All",
"operationCreate": "Create",
"operationDelete": "Delete",
"operationReplace": "Replace"
},
"udf": {
"id": "User Defined Function Id",
"idPlaceholder": "Enter the new user defined function id",
"body": "User Defined Function Body",
"bodyAriaLabel": "User defined function body"
},
"conflicts": {
"unsavedChanges": "Unsaved changes",
"changesWillBeLost": "Changes will be lost. Do you want to continue?",
"resolveConflictFailed": "Resolve conflict failed",
"deleteConflictFailed": "Delete conflict failed",
"refreshGridFailed": "Refresh documents grid failed"
},
"mongoShell": {
"title": "Mongo Shell"
}
} }
} }

View File

@@ -4,9 +4,14 @@ import Adapter from "enzyme-adapter-react-16";
import "jest-canvas-mock"; import "jest-canvas-mock";
import enableHooks from "jest-react-hooks-shallow"; import enableHooks from "jest-react-hooks-shallow";
import { TextDecoder, TextEncoder } from "util"; import { TextDecoder, TextEncoder } from "util";
import i18n from "./i18n";
import enResources from "./Localization/en/Resources.json";
configure({ adapter: new Adapter() }); configure({ adapter: new Adapter() });
initializeIcons(); initializeIcons();
// Load English translations synchronously so t() returns real values in tests
i18n.addResourceBundle("en", "Resources", enResources, true, true);
if (typeof window.URL.createObjectURL === "undefined") { if (typeof window.URL.createObjectURL === "undefined") {
Object.defineProperty(window.URL, "createObjectURL", { value: () => {} }); Object.defineProperty(window.URL, "createObjectURL", { value: () => {} });
} }