Browse Source

Merge pull request #6512 from nocodb/fix/i8n

I18n
pull/6513/head
Muhammed Mustafa 1 year ago committed by GitHub
parent
commit
fcf02abe8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      packages/nc-gui/components/account/License.vue
  2. 26
      packages/nc-gui/components/account/Profile.vue
  3. 8
      packages/nc-gui/components/account/ResetPassword.vue
  4. 2
      packages/nc-gui/components/account/SignupSettings.vue
  5. 32
      packages/nc-gui/components/account/Token.vue
  6. 8
      packages/nc-gui/components/account/UserList.vue
  7. 2
      packages/nc-gui/components/account/UsersModal.vue
  8. 18
      packages/nc-gui/components/cell/attachment/Modal.vue
  9. 2
      packages/nc-gui/components/cell/attachment/RenameFile.vue
  10. 8
      packages/nc-gui/components/cell/attachment/index.vue
  11. 6
      packages/nc-gui/components/dashboard/Sidebar/Header.vue
  12. 18
      packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue
  13. 8
      packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue
  14. 8
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  15. 2
      packages/nc-gui/components/dashboard/TreeView/TableList.vue
  16. 2
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  17. 5
      packages/nc-gui/components/dashboard/TreeView/ViewsList.vue
  18. 6
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  19. 4
      packages/nc-gui/components/dashboard/TreeView/index.vue
  20. 6
      packages/nc-gui/components/dashboard/settings/AppStore.vue
  21. 46
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  22. 4
      packages/nc-gui/components/dashboard/settings/Misc.vue
  23. 6
      packages/nc-gui/components/dashboard/settings/Modal.vue
  24. 24
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  25. 6
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  26. 6
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  27. 8
      packages/nc-gui/components/dlg/AirtableImport.vue
  28. 8
      packages/nc-gui/components/dlg/ProjectDuplicate.vue
  29. 18
      packages/nc-gui/components/dlg/QuickImport.vue
  30. 6
      packages/nc-gui/components/dlg/TableCreate.vue
  31. 8
      packages/nc-gui/components/dlg/TableDuplicate.vue
  32. 4
      packages/nc-gui/components/dlg/TableRename.vue
  33. 29
      packages/nc-gui/components/dlg/ViewCreate.vue
  34. 10
      packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue
  35. 4
      packages/nc-gui/components/dlg/share-and-collaborate/ShareBase.vue
  36. 12
      packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue
  37. 14
      packages/nc-gui/components/dlg/share-and-collaborate/View.vue
  38. 3
      packages/nc-gui/components/erd/Flow.vue
  39. 2
      packages/nc-gui/components/general/ColorPicker.vue
  40. 4
      packages/nc-gui/components/general/CopyUrl.vue
  41. 2
      packages/nc-gui/components/general/DeleteModal.vue
  42. 8
      packages/nc-gui/components/general/OpenLeftSidebarBtn.vue
  43. 10
      packages/nc-gui/components/project/AccessSettings.vue
  44. 10
      packages/nc-gui/components/project/ImportModal.vue
  45. 6
      packages/nc-gui/components/project/View.vue
  46. 6
      packages/nc-gui/components/smartsheet/Details.vue
  47. 2
      packages/nc-gui/components/smartsheet/Toolbar.vue
  48. 2
      packages/nc-gui/components/smartsheet/Topbar.vue
  49. 4
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  50. 14
      packages/nc-gui/components/smartsheet/column/LinkOptions.vue
  51. 8
      packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue
  52. 30
      packages/nc-gui/components/smartsheet/details/Webhooks.vue
  53. 2
      packages/nc-gui/components/smartsheet/grid/GroupBy.vue
  54. 17
      packages/nc-gui/components/smartsheet/grid/Table.vue
  55. 2
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  56. 4
      packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue
  57. 10
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  58. 2
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  59. 2
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  60. 4
      packages/nc-gui/components/smartsheet/topbar/SelectMode.vue
  61. 12
      packages/nc-gui/components/virtual-cell/Links.vue
  62. 27
      packages/nc-gui/components/virtual-cell/components/Header.vue
  63. 15
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  64. 14
      packages/nc-gui/components/virtual-cell/components/ListItems.vue
  65. 6
      packages/nc-gui/composables/useColumnCreateStore.ts
  66. 224
      packages/nc-gui/lang/en.json
  67. 4
      packages/nc-gui/pages/account/index.vue

22
packages/nc-gui/components/account/License.vue

@ -5,6 +5,8 @@ import { extractSdkResponseErrorMsg, useApi, useGlobal } from '#imports'
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
const { t } = useI18n()
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { loadAppInfo } = useGlobal() const { loadAppInfo } = useGlobal()
@ -22,7 +24,7 @@ const loadLicense = async () => {
const setLicense = async () => { const setLicense = async () => {
try { try {
await api.orgLicense.set({ key: key.value }) await api.orgLicense.set({ key: key.value })
message.success('License key updated') message.success(t('success.licenseKeyUpdated'))
await loadAppInfo() await loadAppInfo()
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -35,14 +37,14 @@ loadLicense()
<template> <template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull"> <div class="h-full overflow-y-scroll scrollbar-thin-dull">
<!-- <div class="text-xl mt-4 mb-8 text-center font-weight-bold">License</div>--> <!-- <div class="text-xl mt-4 mb-8 text-center font-weight-bold">License</div>-->
<!-- <div class="mx-auto w-150">--> <!-- <div class="mx-auto w-150">-->
<!-- <div>--> <!-- <div>-->
<!-- <a-textarea v-model:value="key" placeholder="License key" class="!mt-2 !max-w-[600px]"></a-textarea>--> <!-- <a-textarea v-model:value="key" placeholder="License key" class="!mt-2 !max-w-[600px]"></a-textarea>-->
<!-- </div>--> <!-- </div>-->
<!-- <div class="text-center">--> <!-- <div class="text-center">-->
<!-- <a-button class="mt-4 !h-[2.2rem] !rounded-md" @click="setLicense" type="primary">Save license key</a-button>--> <!-- <a-button class="mt-4 !h-[2.2rem] !rounded-md" @click="setLicense" type="primary">Save license key</a-button>-->
<!-- </div>--> <!-- </div>-->
<!-- </div>--> <!-- </div>-->
</div> </div>
</template> </template>

26
packages/nc-gui/components/account/Profile.vue

@ -1,6 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
const { user } = useGlobal() const { user } = useGlobal()
const { t } = useI18n()
const isErrored = ref(false) const isErrored = ref(false)
const isTitleUpdating = ref(false) const isTitleUpdating = ref(false)
const form = ref({ const form = ref({
@ -13,9 +15,9 @@ const formValidator = ref()
const formRules = { const formRules = {
title: [ title: [
{ required: true, message: 'Name required' }, { required: true, message: t('error.nameRequired') },
{ min: 2, message: 'Name must be at least 2 characters long' }, { min: 2, message: t('error.nameMinLength') },
{ max: 60, message: 'Name must be at most 60 characters long' }, { max: 60, message: t('error.nameMaxLength') },
], ],
} }
@ -61,10 +63,10 @@ const onValidate = async (_: any, valid: boolean) => {
<template> <template>
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<div class="flex flex-col w-150"> <div class="flex flex-col w-150">
<div class="flex font-medium text-xl">Profile</div> <div class="flex font-medium text-xl">{{ $t('labels.profile') }}</div>
<div class="mt-5 flex flex-col border-1 rounded-2xl border-gray-200 p-6 gap-y-2"> <div class="mt-5 flex flex-col border-1 rounded-2xl border-gray-200 p-6 gap-y-2">
<div class="flex font-medium text-base">Account details</div> <div class="flex font-medium text-base">{{ $t('labels.accountDetails') }}</div>
<div class="flex text-gray-500">Control your appearance.</div> <div class="flex text-gray-500">{{ $t('labels.controlAppearance') }}</div>
<div class="flex flex-row mt-4"> <div class="flex flex-row mt-4">
<div class="flex h-20 mt-1.5"> <div class="flex h-20 mt-1.5">
<GeneralUserIcon size="xlarge" /> <GeneralUserIcon size="xlarge" />
@ -79,20 +81,20 @@ const onValidate = async (_: any, valid: boolean) => {
@finish="onSubmit" @finish="onSubmit"
@validate="onValidate" @validate="onValidate"
> >
<div class="text-gray-800 mb-1.5">Name</div> <div class="text-gray-800 mb-1.5">{{ $t('general.name') }}</div>
<a-form-item name="title" :rules="formRules.title"> <a-form-item name="title" :rules="formRules.title">
<a-input <a-input
v-model:value="form.title" v-model:value="form.title"
class="w-full !rounded-md !py-1.5" class="w-full !rounded-md !py-1.5"
placeholder="Name" :placeholder="$t('general.name')"
data-testid="nc-account-settings-rename-input" data-testid="nc-account-settings-rename-input"
/> />
</a-form-item> </a-form-item>
<div class="text-gray-800 mb-1.5">Account Email ID</div> <div class="text-gray-800 mb-1.5">{{ $t('labels.accountEmailID') }}</div>
<a-input <a-input
v-model:value="email" v-model:value="email"
class="w-full !rounded-md !py-1.5" class="w-full !rounded-md !py-1.5"
placeholder="Email" :placeholder="$t('general.email')"
disabled disabled
data-testid="nc-account-settings-email-input" data-testid="nc-account-settings-email-input"
/> />
@ -105,8 +107,8 @@ const onValidate = async (_: any, valid: boolean) => {
data-testid="nc-account-settings-save" data-testid="nc-account-settings-save"
@click="onSubmit" @click="onSubmit"
> >
<template #loading> Saving </template> <template #loading> {{ $t('general.saving') }} </template>
Save {{ $t('general.save') }}
</NcButton> </NcButton>
</div> </div>
</a-form> </a-form>

8
packages/nc-gui/components/account/ResetPassword.vue

@ -16,19 +16,13 @@ const form = reactive({
}) })
const formRules = { const formRules = {
currentPassword: [ currentPassword: [{ required: true, message: t('msg.error.signUpRules.passwdRequired') }],
// Current password is required
{ required: true, message: t('msg.error.signUpRules.passwdRequired') },
],
password: [ password: [
// Password is required
{ required: true, message: t('msg.error.signUpRules.passwdRequired') }, { required: true, message: t('msg.error.signUpRules.passwdRequired') },
{ min: 8, message: t('msg.error.signUpRules.passwdLength') }, { min: 8, message: t('msg.error.signUpRules.passwdLength') },
], ],
passwordRepeat: [ passwordRepeat: [
// PasswordRepeat is required
{ required: true, message: t('msg.error.signUpRules.passwdRequired') }, { required: true, message: t('msg.error.signUpRules.passwdRequired') },
// Passwords match
{ {
validator: (_: unknown, _v: string) => { validator: (_: unknown, _v: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

2
packages/nc-gui/components/account/SignupSettings.vue

@ -31,7 +31,7 @@ loadSettings()
<template> <template>
<div data-testid="nc-app-settings"> <div data-testid="nc-app-settings">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">Settings</div> <div class="text-xl mt-4 mb-8 text-center font-weight-bold capitalize">{{ $t('activity.settings') }}</div>
<div class="flex justify-center"> <div class="flex justify-center">
<a-form-item> <a-form-item>
<a-checkbox <a-checkbox

32
packages/nc-gui/components/account/Token.vue

@ -29,7 +29,7 @@ const showNewTokenModal = ref(false)
const currentLimit = ref(10) const currentLimit = ref(10)
const defaultTokenName = 'Untitled token' const defaultTokenName = t('labels.untitledToken')
const selectedTokenData = ref<ApiTokenType>({ const selectedTokenData = ref<ApiTokenType>({
description: defaultTokenName, description: defaultTokenName,
@ -147,9 +147,9 @@ const selectInputOnMount: VNodeRef = (el) =>
const errorMessage = computed(() => { const errorMessage = computed(() => {
const tokenLength = selectedTokenData.value.description?.length const tokenLength = selectedTokenData.value.description?.length
if (!tokenLength) { if (!tokenLength) {
return 'Token name should not be empty' return t('msg.info.tokenNameNotEmpty')
} else if (tokenLength > 255) { } else if (tokenLength > 255) {
return 'Token name should not be more than 255 characters' return t('msg.info.tokenNameMaxLength')
} }
}) })
@ -163,7 +163,7 @@ const handleCancel = () => {
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2"> <div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2">
<div class="max-w-[810px] mx-auto p-4" data-testid="nc-token-list"> <div class="max-w-[810px] mx-auto p-4" data-testid="nc-token-list">
<div class="py-2 flex gap-4 items-center justify-between"> <div class="py-2 flex gap-4 items-center justify-between">
<h6 class="text-2xl my-4 text-left font-bold">API Tokens</h6> <h6 class="text-2xl my-4 text-left font-bold">{{ $t('title.apiTokens') }}</h6>
<NcButton <NcButton
:disabled="showNewTokenModal" :disabled="showNewTokenModal"
class="!rounded-md" class="!rounded-md"
@ -180,14 +180,14 @@ const handleCancel = () => {
</span> </span>
</NcButton> </NcButton>
</div> </div>
<span>Create personal API tokens to use in automation or external apps.</span> <span>{{ $t('msg.apiTokenCreate') }}</span>
<div class="w-[780px] mt-5 border-1 rounded-md h-[530px] overflow-y-scroll"> <div class="w-[780px] mt-5 border-1 rounded-md h-[530px] overflow-y-scroll">
<div> <div>
<div class="flex w-full pl-5 bg-gray-50 border-b-1"> <div class="flex w-full pl-5 bg-gray-50 border-b-1">
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9">Token name</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9">{{ $t('title.tokenName') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9 text-start">Creator</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('title.creator') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-3/9 text-start">Token</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-3/9 text-start">{{ $t('labels.token') }}</span>
<span class="py-3.5 pl-19 text-gray-500 font-medium text-3.5 w-2/9 text-start">Actions</span> <span class="py-3.5 pl-19 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('labels.actions') }}</span>
</div> </div>
<main> <main>
<div v-if="showNewTokenModal"> <div v-if="showNewTokenModal">
@ -223,7 +223,7 @@ const handleCancel = () => {
<NcDivider /> <NcDivider />
</div> </div>
<div v-if="!tokens.length" class="h-118 justify-center flex items-center"> <div v-if="!tokens.length" class="h-118 justify-center flex items-center">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="`${$t('general.no')} ${$t('labels.token')}`" /> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('title.noLabels')" />
</div> </div>
<div v-for="el of tokens" :key="el.id" data-testid="nc-token-list" class="flex border-b-1 pl-5 py-3 justify-between"> <div v-for="el of tokens" :key="el.id" data-testid="nc-token-list" class="flex border-b-1 pl-5 py-3 justify-between">
@ -247,7 +247,7 @@ const handleCancel = () => {
<span class="text-gray-500 font-medium text-3.5 w-2/9"> <span class="text-gray-500 font-medium text-3.5 w-2/9">
<div class="flex justify-end items-center gap-3 pr-5"> <div class="flex justify-end items-center gap-3 pr-5">
<NcTooltip placement="top"> <NcTooltip placement="top">
<template #title>show or hide</template> <template #title>{{ $t('labels.showOrHide') }}</template>
<component <component
:is="iconMap.eye" :is="iconMap.eye"
class="nc-toggle-token-visibility hover::cursor-pointer" class="nc-toggle-token-visibility hover::cursor-pointer"
@ -255,11 +255,11 @@ const handleCancel = () => {
/> />
</NcTooltip> </NcTooltip>
<NcTooltip placement="top" class="h-4"> <NcTooltip placement="top" class="h-4">
<template #title>copy</template> <template #title>{{ $t('general.copy') }}</template>
<component :is="iconMap.copy" class="hover::cursor-pointer" @click="copyToken(el.token)" /> <component :is="iconMap.copy" class="hover::cursor-pointer" @click="copyToken(el.token)" />
</NcTooltip> </NcTooltip>
<NcTooltip placement="top" class="mb-0.5"> <NcTooltip placement="top" class="mb-0.5">
<template #title>delete</template> <template #title>{{ $t('general.delete') }}</template>
<component <component
:is="iconMap.delete" :is="iconMap.delete"
data-testid="nc-token-row-action-icon" data-testid="nc-token-row-action-icon"
@ -283,7 +283,11 @@ const handleCancel = () => {
</div> </div>
</div> </div>
<GeneralDeleteModal v-model:visible="isModalOpen" entity-name="Token" :on-delete="() => deleteToken(tokenToCopy)"> <GeneralDeleteModal
v-model:visible="isModalOpen"
:entity-name="$t('labels.token')"
:on-delete="() => deleteToken(tokenToCopy)"
>
<template #entity-preview> <template #entity-preview>
<span> <span>
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">

8
packages/nc-gui/components/account/UserList.vue

@ -130,13 +130,13 @@ const copyPasswordResetUrl = async (user: User) => {
<template> <template>
<div data-testid="nc-super-user-list"> <div data-testid="nc-super-user-list">
<div class="max-w-[900px] mx-auto"> <div class="max-w-[900px] mx-auto">
<div class="text-xl my-4 text-left font-weight-bold">User Management</div> <div class="text-xl my-4 text-left font-weight-bold">{{ $t('title.userManagement') }}</div>
<div class="py-2 flex gap-4 items-center"> <div class="py-2 flex gap-4 items-center">
<a-input-search <a-input-search
v-model:value="searchText" v-model:value="searchText"
size="middle" size="middle"
class="max-w-[300px]" class="max-w-[300px]"
placeholder="Search Users" :placeholder="$t('labels.searchUsers')"
@blur="loadUsers" @blur="loadUsers"
@keydown.enter="loadUsers" @keydown.enter="loadUsers"
> >
@ -157,7 +157,7 @@ const copyPasswordResetUrl = async (user: User) => {
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<component :is="iconMap.plus" /> <component :is="iconMap.plus" />
Invite new user {{ $t('activity.inviteUser') }}
</div> </div>
</a-button> </a-button>
</div> </div>
@ -186,7 +186,7 @@ const copyPasswordResetUrl = async (user: User) => {
<a-table-column key="roles" :title="$t('objects.role')" data-index="roles"> <a-table-column key="roles" :title="$t('objects.role')" data-index="roles">
<template #default="{ record }"> <template #default="{ record }">
<div> <div>
<div v-if="record.roles.includes('super')" class="font-weight-bold">Super Admin</div> <div v-if="record.roles.includes('super')" class="font-weight-bold">{{ $t('labels.superAdmin') }}</div>
<a-select <a-select
v-else v-else
v-model:value="record.roles" v-model:value="record.roles"

2
packages/nc-gui/components/account/UsersModal.vue

@ -124,7 +124,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div class="flex flex-col mt-1 pb-5"> <div class="flex flex-col mt-1 pb-5">
<div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]"> <div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]">
<component :is="iconMap.account" /> <component :is="iconMap.account" />
<div class="text-xs ml-0.5 mt-0.5">Copy Invite Token</div> <div class="text-xs ml-0.5 mt-0.5">{{ $t('activity.copyInviteToken') }}</div>
</div> </div>
<a-alert class="!mt-2" type="success" show-icon> <a-alert class="!mt-2" type="success" show-icon>

18
packages/nc-gui/components/cell/attachment/Modal.vue

@ -102,17 +102,19 @@ const handleFileDelete = (i: number) => {
@click="open" @click="open"
> >
<MaterialSymbolsAttachFile class="transform group-hover:(text-accent scale-120)" /> <MaterialSymbolsAttachFile class="transform group-hover:(text-accent scale-120)" />
Attach File {{ $t('activity.attachFile') }}
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div v-if="readOnly" class="text-gray-400">[Readonly]</div> <div v-if="readOnly" class="text-gray-400">[{{ $t('labels.readOnly') }}]</div>
Viewing Attachments of {{ $t('labels.viewingAttachmentsOf') }}
<div class="font-semibold underline">{{ column?.title }}</div> <div class="font-semibold underline">{{ column?.title }}</div>
</div> </div>
<div v-if="selectedVisibleItems.includes(true)" class="flex flex-1 items-center gap-3 justify-end mr-[30px]"> <div v-if="selectedVisibleItems.includes(true)" class="flex flex-1 items-center gap-3 justify-end mr-[30px]">
<NcButton type="primary" class="nc-attachment-download-all" @click="bulkDownloadFiles"> Bulk Download </NcButton> <NcButton type="primary" class="nc-attachment-download-all" @click="bulkDownloadFiles">
{{ $t('activity.bulkDownload') }}
</NcButton>
</div> </div>
</div> </div>
</template> </template>
@ -124,7 +126,7 @@ const handleFileDelete = (i: number) => {
class="text-white ring ring-accent ring-opacity-100 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl" class="text-white ring ring-accent ring-opacity-100 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl"
> >
<MaterialSymbolsFileCopyOutline class="text-accent" height="35" width="35" /> <MaterialSymbolsFileCopyOutline class="text-accent" height="35" width="35" />
<div class="text-white text-3xl">Drop here</div> <div class="text-white text-3xl">{{ $t('labels.dropHere') }}</div>
</general-overlay> </general-overlay>
</template> </template>
@ -138,7 +140,7 @@ const handleFileDelete = (i: number) => {
/> />
<a-tooltip v-if="!readOnly"> <a-tooltip v-if="!readOnly">
<template #title> Remove File </template> <template #title> {{ $t('title.removeFile') }} </template>
<component <component
:is="iconMap.closeCircle" :is="iconMap.closeCircle"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic && !isLocked)" v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic && !isLocked)"
@ -148,7 +150,7 @@ const handleFileDelete = (i: number) => {
</a-tooltip> </a-tooltip>
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> Download File </template> <template #title> {{ $t('title.downloadFile') }} </template>
<div class="nc-attachment-download group-hover:(opacity-100)"> <div class="nc-attachment-download group-hover:(opacity-100)">
<component :is="iconMap.download" @click.stop="downloadFile(item)" /> <component :is="iconMap.download" @click.stop="downloadFile(item)" />
@ -156,7 +158,7 @@ const handleFileDelete = (i: number) => {
</a-tooltip> </a-tooltip>
<a-tooltip v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)" placement="bottom"> <a-tooltip v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)" placement="bottom">
<template #title> Rename File </template> <template #title> {{ $t('title.renameFile') }} </template>
<div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]"> <div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]">
<component :is="iconMap.edit" @click.stop="renameFile(item, i)" /> <component :is="iconMap.edit" @click.stop="renameFile(item, i)" />

2
packages/nc-gui/components/cell/attachment/RenameFile.vue

@ -48,7 +48,7 @@ onMounted(() => {
<template> <template>
<GeneralModal v-model:visible="visible" class="nc-attachment-rename-modal !w-[30rem]"> <GeneralModal v-model:visible="visible" class="nc-attachment-rename-modal !w-[30rem]">
<div class="flex flex-col items-center justify-center h-full p-8"> <div class="flex flex-col items-center justify-center h-full p-8">
<div class="text-lg font-semibold self-start mb-4">Rename File</div> <div class="text-lg font-semibold self-start mb-4">{{ $t('title.renameFile') }}</div>
<a-form class="w-full h-full" no-style :model="form" @finish="renameFile(form.title)"> <a-form class="w-full h-full" no-style :model="form" @finish="renameFile(form.title)">
<a-form-item class="w-full" name="title" :rules="rules.title"> <a-form-item class="w-full" name="title" :rules="rules.title">

8
packages/nc-gui/components/cell/attachment/index.vue

@ -168,7 +168,7 @@ const rowHeight = inject(RowHeightInj, ref())
class="nc-attachment-cell-dropzone text-white text-lg ring ring-accent ring-opacity-100 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl" class="nc-attachment-cell-dropzone text-white text-lg ring ring-accent ring-opacity-100 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl"
> >
<MaterialSymbolsFileCopyOutline class="text-accent" /> <MaterialSymbolsFileCopyOutline class="text-accent" />
Drop here {{ $t('labels.dropHere') }}
</general-overlay> </general-overlay>
</template> </template>
@ -182,7 +182,7 @@ const rowHeight = inject(RowHeightInj, ref())
<component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> <component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />
<NcTooltip placement="bottom"> <NcTooltip placement="bottom">
<template #title> Click or drop a file into cell</template> <template #title>{{ $t('activity.attachmentDrop') }} </template>
<div v-if="active || !visibleItems.length" class="flex items-center gap-1"> <div v-if="active || !visibleItems.length" class="flex items-center gap-1">
<MaterialSymbolsAttachFile <MaterialSymbolsAttachFile
@ -192,7 +192,7 @@ const rowHeight = inject(RowHeightInj, ref())
v-if="!visibleItems.length" v-if="!visibleItems.length"
class="group-hover:text-primary text-gray-500 dark:text-gray-200 dark:group-hover:!text-white text-xs" class="group-hover:text-primary text-gray-500 dark:text-gray-200 dark:group-hover:!text-white text-xs"
> >
Add file(s) {{ $t('activity.addFiles') }}
</div> </div>
</div> </div>
</NcTooltip> </NcTooltip>
@ -258,7 +258,7 @@ const rowHeight = inject(RowHeightInj, ref())
<component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> <component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />
<NcTooltip v-else placement="bottom"> <NcTooltip v-else placement="bottom">
<template #title> View attachments</template> <template #title> {{ $t('activity.viewAttachment') }}</template>
<component <component
:is="iconMap.expand" :is="iconMap.expand"

6
packages/nc-gui/components/dashboard/Sidebar/Header.vue

@ -34,11 +34,7 @@ const showSidebarBtn = computed(() => !(isMobileMode.value && !activeViewTitleOr
hide-on-click hide-on-click
> >
<template #title> <template #title>
{{ {{ isLeftSidebarOpen ? `${$t('title.hideSidebar')}` : `${$t('title.showSidebar')}` }}
isLeftSidebarOpen
? `${$t('general.hide')} ${$t('objects.sidebar').toLowerCase()}`
: `${$t('general.show')} ${$t('objects.sidebar').toLowerCase()}`
}}
</template> </template>
<NcButton <NcButton
v-if="showSidebarBtn" v-if="showSidebarBtn"

18
packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue

@ -84,14 +84,14 @@ onMounted(() => {
<NcMenuItem data-testid="nc-sidebar-user-logout" @click="logout"> <NcMenuItem data-testid="nc-sidebar-user-logout" @click="logout">
<GeneralLoader v-if="isLoggingOut" class="!ml-0.5 !mr-0.5 !max-h-4.5 !-mt-0.5" /> <GeneralLoader v-if="isLoggingOut" class="!ml-0.5 !mr-0.5 !max-h-4.5 !-mt-0.5" />
<GeneralIcon v-else icon="signout" class="menu-icon" /> <GeneralIcon v-else icon="signout" class="menu-icon" />
<span class="menu-btn"> Log Out </span> <span class="menu-btn"> {{ $t('general.logout') }}</span>
</NcMenuItem> </NcMenuItem>
<template v-if="!isMobileMode"> <template v-if="!isMobileMode">
<NcDivider /> <NcDivider />
<a href="https://docs.nocodb.com" target="_blank" class="!underline-transparent"> <a href="https://docs.nocodb.com" target="_blank" class="!underline-transparent">
<NcMenuItem> <NcMenuItem>
<GeneralIcon icon="help" class="menu-icon mt-0.5" /> <GeneralIcon icon="help" class="menu-icon mt-0.5" />
<span class="menu-btn"> Help Center </span> <span class="menu-btn"> {{ $t('title.helpCenter') }} </span>
</NcMenuItem> </NcMenuItem>
</a> </a>
</template> </template>
@ -99,19 +99,19 @@ onMounted(() => {
<a href="https://discord.gg/5RgZmkW" target="_blank" class="!underline-transparent"> <a href="https://discord.gg/5RgZmkW" target="_blank" class="!underline-transparent">
<NcMenuItem class="social-icon-wrapper"> <NcMenuItem class="social-icon-wrapper">
<GeneralIcon class="social-icon" icon="discord" /> <GeneralIcon class="social-icon" icon="discord" />
<span class="menu-btn"> Join our Discord </span> <span class="menu-btn"> {{ $t('labels.community.joinDiscord') }} </span>
</NcMenuItem> </NcMenuItem>
</a> </a>
<a href="https://www.reddit.com/r/NocoDB" target="_blank" class="!underline-transparent"> <a href="https://www.reddit.com/r/NocoDB" target="_blank" class="!underline-transparent">
<NcMenuItem class="social-icon-wrapper"> <NcMenuItem class="social-icon-wrapper">
<GeneralIcon class="social-icon" icon="reddit" /> <GeneralIcon class="social-icon" icon="reddit" />
<span class="menu-btn"> /r/NocoDB </span> <span class="menu-btn"> {{ $t('labels.community.joinReddit') }} </span>
</NcMenuItem> </NcMenuItem>
</a> </a>
<a href="https://twitter.com/nocodb" target="_blank" class="!underline-transparent"> <a href="https://twitter.com/nocodb" target="_blank" class="!underline-transparent">
<NcMenuItem class="social-icon-wrapper group"> <NcMenuItem class="social-icon-wrapper group">
<GeneralIcon class="text-gray-500 group-hover:text-gray-800 my-0.5" icon="twitter" /> <GeneralIcon class="text-gray-500 group-hover:text-gray-800 my-0.5" icon="twitter" />
<span class="menu-btn"> Twitter </span> <span class="menu-btn"> {{ $t('labels.twitter') }} </span>
</NcMenuItem> </NcMenuItem>
</a> </a>
<template v-if="!appInfo.ee"> <template v-if="!appInfo.ee">
@ -120,7 +120,7 @@ onMounted(() => {
<NcMenuItem> <NcMenuItem>
<GeneralIcon icon="translate" class="group-hover:text-black nc-language ml-0.25 menu-icon" /> <GeneralIcon icon="translate" class="group-hover:text-black nc-language ml-0.25 menu-icon" />
{{ $t('labels.language') }} {{ $t('labels.language') }}
<div class="flex items-center text-gray-400 text-xs">(Community Translated)</div> <div class="flex items-center text-gray-400 text-xs">{{ $t('labels.community.communityTranslated') }}</div>
<div class="flex-1" /> <div class="flex-1" />
<MaterialSymbolsChevronRightRounded class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" /> <MaterialSymbolsChevronRightRounded class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" />
@ -139,11 +139,11 @@ onMounted(() => {
<NcMenuItem @click="onCopy"> <NcMenuItem @click="onCopy">
<GeneralIcon v-if="isAuthTokenCopied" icon="check" class="group-hover:text-black menu-icon" /> <GeneralIcon v-if="isAuthTokenCopied" icon="check" class="group-hover:text-black menu-icon" />
<GeneralIcon v-else icon="copy" class="menu-icon" /> <GeneralIcon v-else icon="copy" class="menu-icon" />
<template v-if="isAuthTokenCopied"> Copied Auth Token </template> <template v-if="isAuthTokenCopied"> {{ $t('title.copiedAuthToken') }} </template>
<template v-else> Copy Auth Token </template> <template v-else> {{ $t('title.copyAuthToken') }} </template>
</NcMenuItem> </NcMenuItem>
<nuxt-link v-e="['c:navbar:user:email']" class="!no-underline" to="/account/profile"> <nuxt-link v-e="['c:navbar:user:email']" class="!no-underline" to="/account/profile">
<NcMenuItem> <GeneralIcon icon="settings" class="menu-icon" /> Account Settings </NcMenuItem> <NcMenuItem> <GeneralIcon icon="settings" class="menu-icon" /> {{ $t('title.accountSettings') }} </NcMenuItem>
</nuxt-link> </nuxt-link>
</template> </template>
</NcMenu> </NcMenu>

8
packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue

@ -73,7 +73,7 @@ function openQuickImportDialog(type: string) {
@click="openAirtableImportDialog(base.id)" @click="openAirtableImportDialog(base.id)"
> >
<GeneralIcon icon="airtable" class="max-w-3.75 group-hover:text-black" /> <GeneralIcon icon="airtable" class="max-w-3.75 group-hover:text-black" />
<div class="ml-0.5">Airtable</div> <div class="ml-0.5">{{ $t('labels.airtable') }}</div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcMenuItem
@ -82,7 +82,7 @@ function openQuickImportDialog(type: string) {
@click="openQuickImportDialog('csv')" @click="openQuickImportDialog('csv')"
> >
<GeneralIcon icon="csv" class="w-4 group-hover:text-black" /> <GeneralIcon icon="csv" class="w-4 group-hover:text-black" />
CSV file {{ $t('labels.csvFile') }}
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcMenuItem
@ -91,7 +91,7 @@ function openQuickImportDialog(type: string) {
@click="openQuickImportDialog('json')" @click="openQuickImportDialog('json')"
> >
<GeneralIcon icon="code" class="w-4 group-hover:text-black" /> <GeneralIcon icon="code" class="w-4 group-hover:text-black" />
JSON file {{ $t('labels.jsonFile') }}
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcMenuItem
@ -100,7 +100,7 @@ function openQuickImportDialog(type: string) {
@click="openQuickImportDialog('excel')" @click="openQuickImportDialog('excel')"
> >
<GeneralIcon icon="excel" class="max-w-4 group-hover:text-black" /> <GeneralIcon icon="excel" class="max-w-4 group-hover:text-black" />
Microsoft Excel {{ $t('labels.microsoftExcel') }}
</NcMenuItem> </NcMenuItem>
</NcSubMenu> </NcSubMenu>
</template> </template>

8
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -501,7 +501,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
<!-- ERD View --> <!-- ERD View -->
<NcMenuItem key="erd" data-testid="nc-sidebar-project-relations" @click="openProjectErdView(project)"> <NcMenuItem key="erd" data-testid="nc-sidebar-project-relations" @click="openProjectErdView(project)">
<GeneralIcon icon="erd" /> <GeneralIcon icon="erd" />
Relations {{ $t('title.relations') }}
</NcMenuItem> </NcMenuItem>
<!-- Swagger: Rest APIs --> <!-- Swagger: Rest APIs -->
@ -610,7 +610,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
@contextmenu="setMenuContext('base', base)" @contextmenu="setMenuContext('base', base)"
> >
<GeneralBaseLogo :base-type="base.type" class="min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)" /> <GeneralBaseLogo :base-type="base.type" class="min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)" />
Default {{ $t('general.default') }}
</div> </div>
<div <div
v-else v-else
@ -626,7 +626,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
{{ base.alias || '' }} {{ base.alias || '' }}
</div> </div>
<a-tooltip class="xs:(hidden)"> <a-tooltip class="xs:(hidden)">
<template #title>External DB</template> <template #title>{{ $t('objects.externalDb') }}</template>
<div> <div>
<GeneralIcon icon="info" class="text-gray-400 -mt-0.5 hover:text-gray-700 mr-1" /> <GeneralIcon icon="info" class="text-gray-400 -mt-0.5 hover:text-gray-700 mr-1" />
</div> </div>
@ -659,7 +659,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
<!-- ERD View --> <!-- ERD View -->
<NcMenuItem key="erd" @click="openErdView(base)"> <NcMenuItem key="erd" @click="openErdView(base)">
<GeneralIcon icon="erd" /> <GeneralIcon icon="erd" />
Relations {{ $t('title.relations') }}
</NcMenuItem> </NcMenuItem>
<DashboardTreeViewBaseOptions v-if="showBaseOption" v-model:project="project" :base="base" /> <DashboardTreeViewBaseOptions v-if="showBaseOption" v-model:project="project" :base="base" />

2
packages/nc-gui/components/dashboard/TreeView/TableList.vue

@ -140,7 +140,7 @@ const availableTables = computed(() => {
'ml-19.25': baseIndex !== 0, 'ml-19.25': baseIndex !== 0,
}" }"
> >
Empty {{ $t('general.empty') }}
</div> </div>
<div <div
v-if="project.bases?.[baseIndex] && project!.bases[baseIndex].enabled" v-if="project.bases?.[baseIndex] && project!.bases[baseIndex].enabled"

2
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -192,7 +192,7 @@ const isTableOpened = computed(() => {
<template #default> <template #default>
<NcTooltip class="flex" placement="topLeft" hide-on-click :disabled="!canUserEditEmote"> <NcTooltip class="flex" placement="topLeft" hide-on-click :disabled="!canUserEditEmote">
<template #title> <template #title>
{{ 'Change icon' }} {{ $t('general.changeIcon') }}
</template> </template>
<MdiTable <MdiTable

5
packages/nc-gui/components/dashboard/TreeView/ViewsList.vue

@ -36,6 +36,7 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { t } = useI18n()
const isDefaultBase = computed(() => { const isDefaultBase = computed(() => {
const base = project.value?.bases?.find((b) => b.id === table.value.base_id) const base = project.value?.bases?.find((b) => b.id === table.value.base_id)
@ -86,11 +87,11 @@ function markItem(id: string) {
/** validate view title */ /** validate view title */
function validate(view: ViewType) { function validate(view: ViewType) {
if (!view.title || view.title.trim().length < 0) { if (!view.title || view.title.trim().length < 0) {
return 'View name is required' return t('msg.error.viewNameRequired')
} }
if (views.value.some((v) => v.title === view.title && v.id !== view.id)) { if (views.value.some((v) => v.title === view.title && v.id !== view.id)) {
return 'View name should be unique' return t('msg.error.viewNameDuplicate')
} }
return true return true

6
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -279,11 +279,11 @@ function onRef(el: HTMLElement) {
<NcMenu class="min-w-27" :data-testid="`view-sidebar-view-actions-${vModel.alias || vModel.title}`"> <NcMenu class="min-w-27" :data-testid="`view-sidebar-view-actions-${vModel.alias || vModel.title}`">
<NcMenuItem @click.stop="onDblClick"> <NcMenuItem @click.stop="onDblClick">
<GeneralIcon icon="edit" /> <GeneralIcon icon="edit" />
<div class="-ml-0.25">Rename</div> <div class="-ml-0.25">{{ $t('general.rename') }}</div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem @click.stop="onDuplicate"> <NcMenuItem @click.stop="onDuplicate">
<GeneralIcon icon="duplicate" class="nc-view-copy-icon" /> <GeneralIcon icon="duplicate" class="nc-view-copy-icon" />
Duplicate {{ $t('general.duplicate') }}
</NcMenuItem> </NcMenuItem>
<NcDivider /> <NcDivider />
@ -291,7 +291,7 @@ function onRef(el: HTMLElement) {
<template v-if="!vModel.is_default"> <template v-if="!vModel.is_default">
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click.stop="onDelete"> <NcMenuItem class="!text-red-500 !hover:bg-red-50" @click.stop="onDelete">
<GeneralIcon icon="delete" class="text-sm nc-view-delete-icon" /> <GeneralIcon icon="delete" class="text-sm nc-view-delete-icon" />
<div class="-ml-0.25">Delete</div> <div class="-ml-0.25">{{ $t('general.delete') }}</div>
</NcMenuItem> </NcMenuItem>
</template> </template>
</NcMenu> </NcMenu>

4
packages/nc-gui/components/dashboard/TreeView/index.vue

@ -55,6 +55,8 @@ const { loadTables } = projectStore
const { tables } = storeToRefs(projectStore) const { tables } = storeToRefs(projectStore)
const { t } = useI18n()
const { activeTable: _activeTable } = storeToRefs(useTablesStore()) const { activeTable: _activeTable } = storeToRefs(useTablesStore())
const { refreshCommandPalette } = useCommandPalette() const { refreshCommandPalette } = useCommandPalette()
@ -126,7 +128,7 @@ const duplicateTable = async (table: TableType) => {
openTable(newTable!) openTable(newTable!)
} else if (status === JobStatus.FAILED) { } else if (status === JobStatus.FAILED) {
message.error('Failed to duplicate table') message.error(t('msg.error.failedToDuplicateTable'))
await loadTables() await loadTables()
} }
}) })

6
packages/nc-gui/components/dashboard/settings/AppStore.vue

@ -119,21 +119,21 @@ onMounted(async () => {
<a-button v-if="app.parsedInput" size="small" type="primary" @click="showInstallPluginModal(app)"> <a-button v-if="app.parsedInput" size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit"> <div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit">
<IcRoundEdit class="pr-0.5" :height="12" /> <IcRoundEdit class="pr-0.5" :height="12" />
Edit {{ $t('general.edit') }}
</div> </div>
</a-button> </a-button>
<a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)"> <a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset"> <div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset">
<component :is="iconMap.closeCircle" /> <component :is="iconMap.closeCircle" />
<div class="flex ml-0.5">Reset</div> <div class="flex ml-0.5">{{ $t('general.reset') }}</div>
</div> </div>
</a-button> </a-button>
<a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)"> <a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install"> <div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install">
<component :is="iconMap.plus" /> <component :is="iconMap.plus" />
Install {{ $t('general.install') }}
</div> </div>
</a-button> </a-button>
</div> </div>

46
packages/nc-gui/components/dashboard/settings/DataSources.vue

@ -18,6 +18,8 @@ const vReload = useVModel(props, 'reload', emits)
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { t } = useI18n()
const { loadProject } = useProjects() const { loadProject } = useProjects()
const projectStore = useProject() const projectStore = useProject()
@ -135,7 +137,7 @@ const moveBase = async (e: any) => {
id: base.id, id: base.id,
project_id: base.project_id, project_id: base.project_id,
}) })
message.info('Bases are migrated. Please try again.') message.info(t('info.basesMigrated'))
} else { } else {
await $api.base.update(base.project_id as string, base.id as string, { await $api.base.update(base.project_id as string, base.id as string, {
id: base.id, id: base.id,
@ -302,7 +304,7 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex flex-row items-center w-full gap-x-1"> <div class="flex flex-row items-center w-full gap-x-1">
<component :is="iconMap.plus" /> <component :is="iconMap.plus" />
<div class="flex">New Source</div> <div class="flex">{{ $t('activity.newSource') }}</div>
</div> </div>
</NcButton> </NcButton>
</div> </div>
@ -314,10 +316,10 @@ const isEditBaseModalOpen = computed({
> >
<div class="ds-table-head"> <div class="ds-table-head">
<div class="ds-table-row"> <div class="ds-table-row">
<div class="ds-table-col ds-table-enabled cursor-pointer" @dblclick="forceAwaken">Visibility</div> <div class="ds-table-col ds-table-enabled cursor-pointer" @dblclick="forceAwaken">{{ $t('general.visibility') }}</div>
<div class="ds-table-col ds-table-name">Name</div> <div class="ds-table-col ds-table-name">{{ $t('general.name') }}</div>
<div class="ds-table-col ds-table-type">Type</div> <div class="ds-table-col ds-table-type">{{ $t('general.type') }}</div>
<div class="ds-table-col ds-table-actions pl-2">Actions</div> <div class="ds-table-col ds-table-actions pl-2">{{ $t('labels.actions') }}</div>
<div class="ds-table-col ds-table-crud"></div> <div class="ds-table-col ds-table-crud"></div>
</div> </div>
</div> </div>
@ -329,8 +331,8 @@ const isEditBaseModalOpen = computed({
<div class="flex items-center gap-1 cursor-pointer"> <div class="flex items-center gap-1 cursor-pointer">
<a-tooltip> <a-tooltip>
<template #title> <template #title>
<template v-if="sources[0].enabled">Hide in UI</template> <template v-if="sources[0].enabled">{{ $t('activity.hideInUI') }}</template>
<template v-else>Show in UI</template> <template v-else>{{ $t('activity.showInUI') }}</template>
</template> </template>
<a-switch <a-switch
:checked="sources[0].enabled ? true : false" :checked="sources[0].enabled ? true : false"
@ -343,7 +345,7 @@ const isEditBaseModalOpen = computed({
<div class="ds-table-col ds-table-name font-medium"> <div class="ds-table-col ds-table-name font-medium">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<!-- <GeneralBaseLogo :base-type="sources[0].type" /> --> <!-- <GeneralBaseLogo :base-type="sources[0].type" /> -->
Default {{ $t('general.default') }}
</div> </div>
</div> </div>
@ -361,11 +363,11 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<a-tooltip v-if="metadiffbases.includes(sources[0].id)"> <a-tooltip v-if="metadiffbases.includes(sources[0].id)">
<template #title>Out of sync</template> <template #title>{{ $t('activity.outOfSync') }}</template>
<GeneralIcon icon="warning" class="group-hover:text-accent text-primary" /> <GeneralIcon icon="warning" class="group-hover:text-accent text-primary" />
</a-tooltip> </a-tooltip>
<GeneralIcon v-else icon="sync" class="group-hover:text-accent" /> <GeneralIcon v-else icon="sync" class="group-hover:text-accent" />
Sync Metadata {{ $t('activity.metaSync') }}
</div> </div>
</a-button> </a-button>
<a-button <a-button
@ -375,7 +377,7 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="erd" class="group-hover:text-accent" /> <GeneralIcon icon="erd" class="group-hover:text-accent" />
Relations {{ $t('title.relations') }}
</div> </div>
</a-button> </a-button>
<a-button <a-button
@ -385,7 +387,7 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="acl" class="group-hover:text-accent" /> <GeneralIcon icon="acl" class="group-hover:text-accent" />
UI ACL {{ $t('labels.uiAcl') }}
</div> </div>
</a-button> </a-button>
<a-button <a-button
@ -395,7 +397,7 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="book" class="group-hover:text-accent" /> <GeneralIcon icon="book" class="group-hover:text-accent" />
Audit {{ $t('title.audit') }}
</div> </div>
</a-button> </a-button>
</div> </div>
@ -418,8 +420,8 @@ const isEditBaseModalOpen = computed({
<div class="flex items-center gap-1 cursor-pointer"> <div class="flex items-center gap-1 cursor-pointer">
<a-tooltip> <a-tooltip>
<template #title> <template #title>
<template v-if="base.enabled">Hide in UI</template> <template v-if="base.enabled">{{ $t('activity.hideInUI') }}</template>
<template v-else>Show in UI</template> <template v-else>{{ $t('activity.showInUI') }}</template>
</template> </template>
<a-switch :checked="base.enabled ? true : false" @change="toggleBase(base, $event)" /> <a-switch :checked="base.enabled ? true : false" @change="toggleBase(base, $event)" />
</a-tooltip> </a-tooltip>
@ -429,7 +431,7 @@ const isEditBaseModalOpen = computed({
<GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" /> <GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" />
<div v-if="base.is_meta || base.is_local">-</div> <div v-if="base.is_meta || base.is_local">-</div>
<div v-else class="flex items-center gap-1"> <div v-else class="flex items-center gap-1">
{{ base.is_meta || base.is_local ? 'BASE' : base.alias }} {{ base.is_meta || base.is_local ? $t('general.base') : base.alias }}
</div> </div>
</div> </div>
@ -450,7 +452,7 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="erd" class="group-hover:text-accent" /> <GeneralIcon icon="erd" class="group-hover:text-accent" />
Relations {{ $t('title.relations') }}
</div> </div>
</a-button> </a-button>
<a-button <a-button
@ -460,7 +462,7 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="acl" class="group-hover:text-accent" /> <GeneralIcon icon="acl" class="group-hover:text-accent" />
UI ACL {{ $t('labels.uiAcl') }}
</div> </div>
</a-button> </a-button>
<a-button <a-button
@ -471,11 +473,11 @@ const isEditBaseModalOpen = computed({
> >
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<a-tooltip v-if="metadiffbases.includes(base.id)"> <a-tooltip v-if="metadiffbases.includes(base.id)">
<template #title>Out of sync</template> <template #title>{{ $t('activity.outOfSync') }}</template>
<GeneralIcon icon="warning" class="group-hover:text-accent text-primary" /> <GeneralIcon icon="warning" class="group-hover:text-accent text-primary" />
</a-tooltip> </a-tooltip>
<GeneralIcon v-else icon="sync" class="group-hover:text-accent" /> <GeneralIcon v-else icon="sync" class="group-hover:text-accent" />
Sync Metadata {{ $t('tooltip.metaSync') }}
</div> </div>
</a-button> </a-button>
</div> </div>
@ -541,7 +543,7 @@ const isEditBaseModalOpen = computed({
<LazyDashboardSettingsBaseAudit :base-id="activeBaseId" @close="isBaseAuditModalOpen = false" /> <LazyDashboardSettingsBaseAudit :base-id="activeBaseId" @close="isBaseAuditModalOpen = false" />
</div> </div>
</GeneralModal> </GeneralModal>
<GeneralDeleteModal v-model:visible="isDeleteBaseModalOpen" entity-name="base" :on-delete="deleteBase"> <GeneralDeleteModal v-model:visible="isDeleteBaseModalOpen" :entity-name="$t('general.base')" :on-delete="deleteBase">
<template #entity-preview> <template #entity-preview>
<div v-if="toBeDeletedBase" class="flex flex-row items-center py-2 px-3.25 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="toBeDeletedBase" class="flex flex-row items-center py-2 px-3.25 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralBaseLogo :base-type="toBeDeletedBase.type" /> <GeneralBaseLogo :base-type="toBeDeletedBase.type" />

4
packages/nc-gui/components/dashboard/settings/Misc.vue

@ -12,6 +12,8 @@ const { project } = storeToRefs(projectStore)
const _projectId = inject(ProjectIdInj, undefined) const _projectId = inject(ProjectIdInj, undefined)
const projectId = computed(() => _projectId?.value ?? project.value?.id) const projectId = computed(() => _projectId?.value ?? project.value?.id)
const { t } = useI18n()
watch(includeM2M, async () => await loadTables()) watch(includeM2M, async () => await loadTables())
const showNullAndEmptyInFilter = ref() const showNullAndEmptyInFilter = ref()
@ -31,7 +33,7 @@ async function showNullAndEmptyInFilterOnChange(evt: CheckboxChangeEvent) {
if (!evt.target.checked) { if (!evt.target.checked) {
if (await hasEmptyOrNullFilters()) { if (await hasEmptyOrNullFilters()) {
showNullAndEmptyInFilter.value = true showNullAndEmptyInFilter.value = true
message.warning('Null / Empty filters exist. Please remove them first.') message.warning(t('msg.error.nullFilterExists'))
} }
} }
const newProjectMeta = { const newProjectMeta = {

6
packages/nc-gui/components/dashboard/settings/Modal.vue

@ -43,6 +43,8 @@ provide(ProjectIdInj, projectId)
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { t } = useI18n()
const dataSourcesReload = ref(false) const dataSourcesReload = ref(false)
const dataSourcesAwakened = ref(false) const dataSourcesAwakened = ref(false)
@ -107,12 +109,12 @@ const tabsInfo: TabGroup = {
// }, // },
projectSettings: { projectSettings: {
// Project Settings // Project Settings
title: 'Project Settings', title: t('labels.projectSettings'),
icon: iconMap.settings, icon: iconMap.settings,
subTabs: { subTabs: {
misc: { misc: {
// Misc // Misc
title: 'Misc', title: t('general.misc'),
body: Misc, body: Misc,
}, },
}, },

24
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -95,25 +95,25 @@ const tableHeaderRenderer = (label: string) => () => h('div', { class: 'text-gra
const columns = [ const columns = [
{ {
title: tableHeaderRenderer('Table name'), title: tableHeaderRenderer(t('labels.tableName')),
name: 'table_name', name: 'table_name',
}, },
{ {
title: tableHeaderRenderer('View name'), title: tableHeaderRenderer(t('labels.viewName')),
name: 'view_name', name: 'view_name',
}, },
{ {
title: tableHeaderRenderer('Editor'), title: tableHeaderRenderer(t('objects.roleType.editor')),
name: 'editor', name: 'editor',
width: 120, width: 120,
}, },
{ {
title: tableHeaderRenderer('Commenter'), title: tableHeaderRenderer(t('objects.roleType.commenter')),
name: 'commenter', name: 'commenter',
width: 120, width: 120,
}, },
{ {
title: tableHeaderRenderer('Viewer'), title: tableHeaderRenderer(t('objects.roleType.viewer')),
name: 'viewer', name: 'viewer',
width: 120, width: 120,
}, },
@ -125,7 +125,7 @@ const columns = [
<div class="flex flex-col w-[900px]"> <div class="flex flex-col w-[900px]">
<span class="mb-4 first-letter:capital font-bold"> UI ACL : {{ project.title }} </span> <span class="mb-4 first-letter:capital font-bold"> UI ACL : {{ project.title }} </span>
<div class="flex flex-row items-center w-full mb-4 gap-2 justify-between"> <div class="flex flex-row items-center w-full mb-4 gap-2 justify-between">
<a-input v-model:value="searchInput" placeholder="Search models" class="nc-acl-search !w-[400px]"> <a-input v-model:value="searchInput" :placeholder="$t('placeholder.searchModels')" class="nc-acl-search !w-[400px]">
<template #prefix> <template #prefix>
<component :is="iconMap.search" /> <component :is="iconMap.search" />
</template> </template>
@ -134,14 +134,14 @@ const columns = [
<a-button type="text" ghost class="self-start !rounded-md nc-acl-reload" @click="loadTableList"> <a-button type="text" ghost class="self-start !rounded-md nc-acl-reload" @click="loadTableList">
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': isLoading }" /> <component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
Reload {{ $t('general.reload') }}
</div> </div>
</a-button> </a-button>
<NcButton size="large" class="z-10 !rounded-lg !px-2 mr-2.5" type="primary" @click="saveUIAcl"> <NcButton size="large" class="z-10 !rounded-lg !px-2 mr-2.5" type="primary" @click="saveUIAcl">
<div class="flex flex-row items-center w-full gap-x-1"> <div class="flex flex-row items-center w-full gap-x-1">
<component :is="iconMap.save" /> <component :is="iconMap.save" />
<div class="flex">Save</div> <div class="flex">{{ $t('general.save') }}</div>
</div> </div>
</NcButton> </NcButton>
</div> </div>
@ -196,9 +196,13 @@ const columns = [
<a-tooltip> <a-tooltip>
<template #title> <template #title>
<span v-if="record.disabled[role]"> <span v-if="record.disabled[role]">
Click to make '{{ record.title }}' visible for role:{{ role }} in UI dashboard</span {{ $t('labels.clickToMake') }} '{{ record.title }}' {{ $t('labels.visibleForRole') }} {{ role }}
{{ $t('labels.inUI') }} dashboard</span
>
<span v-else
>{{ $t('labels.clickToHide') }}'{{ record.title }}' {{ $t('labels.forRole') }}:{{ role }}
{{ $t('labels.inUI') }}</span
> >
<span v-else>Click to hide '{{ record.title }}' for role:{{ role }} in UI dashboard</span>
</template> </template>
<a-checkbox <a-checkbox

6
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -372,13 +372,13 @@ watch(
<div class="text-lg font-semibold self-start mb-4">{{ t('msg.info.dbConnected') }}</div> <div class="text-lg font-semibold self-start mb-4">{{ t('msg.info.dbConnected') }}</div>
<div class="flex gap-x-2 mt-5 ml-7 pt-2.5 justify-end"> <div class="flex gap-x-2 mt-5 ml-7 pt-2.5 justify-end">
<NcButton key="back" type="secondary" @click="isConnSuccess = false">{{ $t('general.cancel') }}</NcButton> <NcButton key="back" type="secondary" @click="isConnSuccess = false">{{ $t('general.cancel') }}</NcButton>
<NcButton key="submit" type="primary" @click="createBase">Ok & Add Base</NcButton> <NcButton key="submit" type="primary" @click="createBase"> {{ $t('activity.addBase') }}</NcButton>
</div> </div>
</div> </div>
</GeneralModal> </GeneralModal>
<div class="create-base bg-white relative flex flex-col justify-center gap-2 w-full"> <div class="create-base bg-white relative flex flex-col justify-center gap-2 w-full">
<h1 class="prose-2xl font-bold self-start mb-4 flex items-center gap-2"> <h1 class="prose-2xl font-bold self-start mb-4 flex items-center gap-2">
New Base {{ $t('title.newBase') }}
<DashboardSettingsDataSourcesInfo /> <DashboardSettingsDataSourcesInfo />
<span class="flex-grow"></span> <span class="flex-grow"></span>
</h1> </h1>
@ -635,7 +635,7 @@ watch(
<div class="text-lg font-semibold self-start mb-4">{{ t('msg.info.dbConnected') }}</div> <div class="text-lg font-semibold self-start mb-4">{{ t('msg.info.dbConnected') }}</div>
<div class="flex gap-x-2 mt-5 ml-7 pt-2.5 justify-end"> <div class="flex gap-x-2 mt-5 ml-7 pt-2.5 justify-end">
<NcButton key="back" type="secondary" @click="isConnSuccess = false">{{ $t('general.cancel') }}</NcButton> <NcButton key="back" type="secondary" @click="isConnSuccess = false">{{ $t('general.cancel') }}</NcButton>
<NcButton key="submit" type="primary" @click="createBase">Ok & Add Base</NcButton> <NcButton key="submit" type="primary" @click="createBase">{{ $t('activity.addBase') }}</NcButton>
</div> </div>
</div> </div>
</GeneralModal> </GeneralModal>

6
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -338,7 +338,7 @@ onMounted(async () => {
<template> <template>
<div class="edit-base bg-white relative flex flex-col justify-start gap-2 w-full p-2"> <div class="edit-base bg-white relative flex flex-col justify-start gap-2 w-full p-2">
<h1 class="prose-2xl font-bold self-start">Edit Base</h1> <h1 class="prose-2xl font-bold self-start">{{ $t('activity.editBase') }}</h1>
<a-form <a-form
ref="form" ref="form"
@ -562,7 +562,7 @@ onMounted(async () => {
</a-form-item> </a-form-item>
<div class="w-full flex items-center mt-2 text-[#e65100]"> <div class="w-full flex items-center mt-2 text-[#e65100]">
<component :is="iconMap.warning" class="mr-2 mb-5.9" /> <component :is="iconMap.warning" class="mr-2 mb-5.9" />
<div>Please make sure database you are trying to connect is valid! This operation can cause schema loss!!</div> <div>{{ $t('msg.warning.dbValid') }}</div>
</div> </div>
</a-form> </a-form>
@ -596,7 +596,7 @@ onMounted(async () => {
<div class="text-lg font-semibold self-start mb-4">{{ t('msg.info.dbConnected') }}</div> <div class="text-lg font-semibold self-start mb-4">{{ t('msg.info.dbConnected') }}</div>
<div class="flex gap-x-2 mt-5 ml-7 pt-2.5 justify-end"> <div class="flex gap-x-2 mt-5 ml-7 pt-2.5 justify-end">
<NcButton key="back" type="secondary" @click="isConnSuccess = false">{{ $t('general.cancel') }}</NcButton> <NcButton key="back" type="secondary" @click="isConnSuccess = false">{{ $t('general.cancel') }}</NcButton>
<NcButton key="submit" type="primary" @click="editBase">Ok & Edit Base</NcButton> <NcButton key="submit" type="primary" @click="editBase">{{ $t('activity.okEditBase') }}</NcButton>
</div> </div>
</div> </div>
</GeneralModal> </GeneralModal>

8
packages/nc-gui/components/dlg/AirtableImport.vue

@ -270,7 +270,7 @@ onMounted(async () => {
> >
<div class="px-5"> <div class="px-5">
<!-- Quick Import --> <!-- Quick Import -->
<div class="mt-5 prose-xl font-weight-bold" @dblclick="enableAbort = true">{{ $t('title.quickImport') }} - AIRTABLE</div> <div class="mt-5 prose-xl font-weight-bold" @dblclick="enableAbort = true">{{ $t('title.quickImportAirtable') }}</div>
<div v-if="step === 1"> <div v-if="step === 1">
<div class="mb-4"> <div class="mb-4">
@ -353,7 +353,7 @@ onMounted(async () => {
<!-- Import Formula Columns --> <!-- Import Formula Columns -->
<a-tooltip placement="top"> <a-tooltip placement="top">
<template #title> <template #title>
<span>Coming Soon!</span> <span>{{ $t('title.comingSoon') }}</span>
</template> </template>
<a-checkbox v-model:checked="syncSource.details.options.syncFormula" disabled> <a-checkbox v-model:checked="syncSource.details.options.syncFormula" disabled>
{{ $t('labels.importFormulaColumns') }} {{ $t('labels.importFormulaColumns') }}
@ -420,7 +420,9 @@ onMounted(async () => {
<a-button v-if="showGoToDashboardButton" class="mt-4" size="large" @click="dialogShow = false"> <a-button v-if="showGoToDashboardButton" class="mt-4" size="large" @click="dialogShow = false">
{{ $t('labels.goToDashboard') }} {{ $t('labels.goToDashboard') }}
</a-button> </a-button>
<a-button v-else-if="enableAbort" class="mt-4" size="large" danger @click="abort()">ABORT</a-button> <a-button v-else-if="enableAbort" class="mt-4 uppercase" size="large" danger @click="abort()">{{
$t('general.abort')
}}</a-button>
</div> </div>
</div> </div>
</div> </div>

8
packages/nc-gui/components/dlg/ProjectDuplicate.vue

@ -81,16 +81,16 @@ const isEaster = ref(false)
{{ $t('general.duplicate') }} {{ $t('objects.project') }} {{ $t('general.duplicate') }} {{ $t('objects.project') }}
</div> </div>
<div class="mt-4">Are you sure you want to duplicate the `{{ project.title }}` project?</div> <div class="mt-4">{{ $t('msg.warning.duplicateProject') }}</div>
<div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div> <div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div>
<a-divider class="!m-0 !p-0 !my-2" /> <a-divider class="!m-0 !p-0 !my-2" />
<div class="text-xs p-2"> <div class="text-xs p-2">
<a-checkbox v-model:checked="options.includeData">Include data</a-checkbox> <a-checkbox v-model:checked="options.includeData">{{ $t('labels.includeData') }}</a-checkbox>
<a-checkbox v-model:checked="options.includeViews">Include views</a-checkbox> <a-checkbox v-model:checked="options.includeViews">{{ $t('labels.includeView') }}</a-checkbox>
<a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">Include webhooks</a-checkbox> <a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">{{ $t('labels.includeWebhook') }}</a-checkbox>
</div> </div>
</div> </div>
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> <div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end">

18
packages/nc-gui/components/dlg/QuickImport.vue

@ -119,7 +119,7 @@ const { validate, validateInfos } = useForm(importState, validators)
const importMeta = computed(() => { const importMeta = computed(() => {
if (IsImportTypeExcel.value) { if (IsImportTypeExcel.value) {
return { return {
header: `${t('title.quickImport')} - EXCEL`, header: `${t('title.quickImportExcel')}`,
uploadHint: t('msg.info.excelSupport'), uploadHint: t('msg.info.excelSupport'),
urlInputLabel: t('msg.info.excelURL'), urlInputLabel: t('msg.info.excelURL'),
loadUrlDirective: ['c:quick-import:excel:load-url'], loadUrlDirective: ['c:quick-import:excel:load-url'],
@ -127,7 +127,7 @@ const importMeta = computed(() => {
} }
} else if (isImportTypeCsv.value) { } else if (isImportTypeCsv.value) {
return { return {
header: `${t('title.quickImport')} - CSV`, header: `${t('title.quickImportCSV')}`,
uploadHint: '', uploadHint: '',
urlInputLabel: t('msg.info.csvURL'), urlInputLabel: t('msg.info.csvURL'),
loadUrlDirective: ['c:quick-import:csv:load-url'], loadUrlDirective: ['c:quick-import:csv:load-url'],
@ -135,7 +135,7 @@ const importMeta = computed(() => {
} }
} else if (isImportTypeJson.value) { } else if (isImportTypeJson.value) {
return { return {
header: `${t('title.quickImport')} - JSON`, header: `${t('title.quickImportJSON')}`,
uploadHint: '', uploadHint: '',
acceptTypes: '.json', acceptTypes: '.json',
} }
@ -598,7 +598,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
<template #tab> <template #tab>
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<component :is="iconMap.json" /> <component :is="iconMap.json" />
JSON Editor {{ $t('title.jsonEditor') }}
</span> </span>
</template> </template>
@ -611,7 +611,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
<template #tab> <template #tab>
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<component :is="iconMap.link" /> <component :is="iconMap.link" />
URL {{ $t('datatype.URL') }}
</span> </span>
</template> </template>
@ -645,7 +645,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
<a-form-item v-if="isImportTypeCsv || IsImportTypeExcel" class="!my-2"> <a-form-item v-if="isImportTypeCsv || IsImportTypeExcel" class="!my-2">
<a-checkbox v-model:checked="importState.parserConfig.firstRowAsHeaders"> <a-checkbox v-model:checked="importState.parserConfig.firstRowAsHeaders">
<span class="caption">Use First Row as Headers</span> <span class="caption">{{ $t('labels.firstRowAsHeaders') }}</span>
</a-checkbox> </a-checkbox>
</a-form-item> </a-form-item>
@ -665,7 +665,9 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
</div> </div>
</a-spin> </a-spin>
<template #footer> <template #footer>
<a-button v-if="templateEditorModal" key="back" class="!rounded-md" @click="templateEditorModal = false">Back </a-button> <a-button v-if="templateEditorModal" key="back" class="!rounded-md" @click="templateEditorModal = false"
>{{ $t('general.back') }}
</a-button>
<a-button v-else key="cancel" class="!rounded-md" @click="dialogShow = false">{{ $t('general.cancel') }} </a-button> <a-button v-else key="cancel" class="!rounded-md" @click="dialogShow = false">{{ $t('general.cancel') }} </a-button>
@ -676,7 +678,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
:disabled="disableFormatJsonButton" :disabled="disableFormatJsonButton"
@click="formatJson" @click="formatJson"
> >
Format JSON {{ $t('labels.formatJson') }}
</a-button> </a-button>
<a-button <a-button

6
packages/nc-gui/components/dlg/TableCreate.vue

@ -168,9 +168,9 @@ onMounted(() => {
<template #label="{ value }"> <template #label="{ value }">
<a-tooltip v-if="value === 'id'" placement="top" class="!flex"> <a-tooltip v-if="value === 'id'" placement="top" class="!flex">
<template #title> <template #title>
<span>ID column is required, you can rename this later if required.</span> <span>{{ $t('msg.idColumnRequired') }}</span>
</template> </template>
ID {{ $t('datatype.ID') }}
</a-tooltip> </a-tooltip>
<div v-else class="flex"> <div v-else class="flex">
{{ value }} {{ value }}
@ -190,7 +190,7 @@ onMounted(() => {
@click="_createTable" @click="_createTable"
> >
{{ $t('activity.createTable') }} {{ $t('activity.createTable') }}
<template #loading> Creating Table </template> <template #loading> {{ $t('title.creatingTable') }} </template>
</NcButton> </NcButton>
</div> </div>
</a-form> </a-form>

8
packages/nc-gui/components/dlg/TableDuplicate.vue

@ -70,16 +70,16 @@ const isEaster = ref(false)
{{ $t('general.duplicate') }} {{ $t('objects.table') }} {{ $t('general.duplicate') }} {{ $t('objects.table') }}
</div> </div>
<div class="mt-4">Are you sure you want to duplicate the `{{ table.title }}` table?</div> <div class="mt-4">{{ $t('msg.warning.duplicateProject') }}</div>
<div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div> <div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div>
<a-divider class="!m-0 !p-0 !my-2" /> <a-divider class="!m-0 !p-0 !my-2" />
<div class="text-xs p-2"> <div class="text-xs p-2">
<a-checkbox v-model:checked="options.includeData">Include data</a-checkbox> <a-checkbox v-model:checked="options.includeData">{{ $t('labels.includeData') }}a</a-checkbox>
<a-checkbox v-model:checked="options.includeViews">Include views</a-checkbox> <a-checkbox v-model:checked="options.includeViews">{{ $t('labels.includeView') }}</a-checkbox>
<a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">Include webhooks</a-checkbox> <a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">{{ $t('labels.includeWebhook') }}</a-checkbox>
</div> </div>
</div> </div>
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> <div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end">

4
packages/nc-gui/components/dlg/TableRename.vue

@ -213,8 +213,8 @@ const renameTable = async (undo = false, disableTitleDiffCheck?: boolean | undef
:loading="loading" :loading="loading"
@click="() => renameTable()" @click="() => renameTable()"
> >
Rename Table {{ $t('title.renameTable') }}
<template #loading> Renaming Table </template> <template #loading> {{ $t('title.renamingTable') }}</template>
</NcButton> </NcButton>
</div> </div>
</div> </div>

29
packages/nc-gui/components/dlg/ViewCreate.vue

@ -238,8 +238,21 @@ onMounted(async () => {
<template #header> <template #header>
<div class="flex flex-row items-center gap-x-1.5"> <div class="flex flex-row items-center gap-x-1.5">
<GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" /> <GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" />
{{ $t(`general.${selectedViewId ? 'duplicate' : 'create'}`) }} <span class="capitalize">{{ typeAlias }}</span> <template v-if="form.type === ViewTypes.GRID">
{{ $t('objects.view') }} {{ $t('labels.duplicateGridView') }}
</template>
<template v-else-if="form.type === ViewTypes.GALLERY">
{{ $t('labels.duplicateGalleryView') }}
</template>
<template v-else-if="form.type === ViewTypes.FORM">
{{ $t('labels.duplicateFormView') }}
</template>
<template v-else-if="form.type === ViewTypes.KANBAN">
{{ $t('labels.duplicateKanbanView') }}
</template>
<template v-else>
{{ $t('labels.duplicateView') }}
</template>
</div> </div>
</template> </template>
<div class="mt-2"> <div class="mt-2">
@ -266,8 +279,8 @@ onMounted(async () => {
:disabled="groupingFieldColumnId || isMetaLoading" :disabled="groupingFieldColumnId || isMetaLoading"
:loading="isMetaLoading" :loading="isMetaLoading"
:options="viewSelectFieldOptions" :options="viewSelectFieldOptions"
placeholder="Select a Grouping Field" :placeholder="$t('placeholder.selectGroupField')"
not-found-content="No Single Select Field can be found. Please create one first." :not-found-content="$t('placeholder.selectGroupFieldNotFound')"
/> />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
@ -282,8 +295,8 @@ onMounted(async () => {
:options="viewSelectFieldOptions" :options="viewSelectFieldOptions"
:disabled="groupingFieldColumnId || isMetaLoading" :disabled="groupingFieldColumnId || isMetaLoading"
:loading="isMetaLoading" :loading="isMetaLoading"
placeholder="Select a GeoData Field" :placeholder="$t('placeholder.selectGeoField')"
not-found-content="No GeoData Field can be found. Please create one first." :not-found-content="$t('placeholder.selectGeoFieldNotFound')"
/> />
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -294,8 +307,8 @@ onMounted(async () => {
</NcButton> </NcButton>
<NcButton type="primary" :loading="isViewCreating" @click="onSubmit"> <NcButton type="primary" :loading="isViewCreating" @click="onSubmit">
Create View {{ $t('labels.createView') }}
<template #loading> Creating View </template> <template #loading> {{ $t('labels.creatingView') }}</template>
</NcButton> </NcButton>
</div> </div>
</div> </div>

10
packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue

@ -4,6 +4,8 @@ const { loadUsers, users } = useManageUsers()
const formRef = ref() const formRef = ref()
const { t } = useI18n()
const useForm = Form.useForm const useForm = Form.useForm
const validators = computed(() => { const validators = computed(() => {
return { return {
@ -11,12 +13,16 @@ const validators = computed(() => {
{ {
validator: (rule: any, value: string, callback: (errMsg?: string) => void) => { validator: (rule: any, value: string, callback: (errMsg?: string) => void) => {
if (!value || value.length === 0) { if (!value || value.length === 0) {
callback('Email is required') callback(t('msg.error.signUpRules.emailReqd'))
return return
} }
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !validateEmail(e)) const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !validateEmail(e))
if (invalidEmails.length > 0) { if (invalidEmails.length > 0) {
callback(`${invalidEmails.length > 1 ? ' Invalid emails:' : 'Invalid email:'} ${invalidEmails.join(', ')} `) callback(
`${
invalidEmails.length > 1 ? t('msg.error.signUpRules.invalidEmails') : t('msg.error.signUpRules.invalidEmail')
} ${invalidEmails.join(', ')} `,
)
} else { } else {
callback() callback()
} }

4
packages/nc-gui/components/dlg/share-and-collaborate/ShareBase.vue

@ -118,13 +118,13 @@ const onRoleToggle = async () => {
<div class="flex flex-col py-2 px-3 gap-2 w-full" data-testid="nc-share-base-sub-modal"> <div class="flex flex-col py-2 px-3 gap-2 w-full" data-testid="nc-share-base-sub-modal">
<div class="flex flex-col w-full p-3 border-1 border-gray-100 rounded-md"> <div class="flex flex-col w-full p-3 border-1 border-gray-100 rounded-md">
<div class="flex flex-row w-full justify-between"> <div class="flex flex-row w-full justify-between">
<div class="text-black font-medium">Enable Public Access</div> <div class="text-black font-medium">{{ $t('activity.enablePublicAccess') }}</div>
<a-switch :checked="isSharedBaseEnabled" :loading="isToggleBaseLoading" class="ml-2" @click="toggleSharedBase" /> <a-switch :checked="isSharedBaseEnabled" :loading="isToggleBaseLoading" class="ml-2" @click="toggleSharedBase" />
</div> </div>
<div v-if="isSharedBaseEnabled" class="flex flex-col w-full mt-3 border-t-1 pt-3 border-gray-100"> <div v-if="isSharedBaseEnabled" class="flex flex-col w-full mt-3 border-t-1 pt-3 border-gray-100">
<GeneralCopyUrl v-model:url="url" /> <GeneralCopyUrl v-model:url="url" />
<div class="flex flex-row justify-between mt-3 bg-gray-50 px-3 py-2 rounded-md"> <div class="flex flex-row justify-between mt-3 bg-gray-50 px-3 py-2 rounded-md">
<div class="text-black">Editing access</div> <div class="text-black">{{ $t('activity.editingAccess') }}</div>
<a-switch <a-switch
:loading="isRoleToggleLoading" :loading="isRoleToggleLoading"
:checked="base?.role === ShareBaseRole.Editor" :checked="base?.role === ShareBaseRole.Editor"

12
packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue

@ -268,7 +268,7 @@ const isPublicShareDisabled = computed(() => {
<div class="flex flex-col py-2 px-3 mb-1"> <div class="flex flex-col py-2 px-3 mb-1">
<div class="flex flex-col w-full mt-2.5 px-3 py-2.5 border-gray-200 border-1 rounded-md gap-y-2"> <div class="flex flex-col w-full mt-2.5 px-3 py-2.5 border-gray-200 border-1 rounded-md gap-y-2">
<div class="flex flex-row w-full justify-between py-0.5"> <div class="flex flex-row w-full justify-between py-0.5">
<div class="flex" :style="{ fontWeight: 500 }">Enable public viewing</div> <div class="flex" :style="{ fontWeight: 500 }">{{ $t('activity.enabledPublicViewing') }}</div>
<a-switch <a-switch
data-testid="share-view-toggle" data-testid="share-view-toggle"
:checked="isPublicShared" :checked="isPublicShared"
@ -284,7 +284,7 @@ const isPublicShareDisabled = computed(() => {
</div> </div>
<div class="flex flex-col justify-between mt-1 py-2 px-3 bg-gray-50 rounded-md"> <div class="flex flex-col justify-between mt-1 py-2 px-3 bg-gray-50 rounded-md">
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<div class="flex text-black">Restrict access with password</div> <div class="flex text-black">{{ $t('activity.restrictAccessWithPassword') }}</div>
<a-switch <a-switch
data-testid="share-password-toggle" data-testid="share-password-toggle"
:checked="passwordProtected" :checked="passwordProtected"
@ -317,7 +317,7 @@ const isPublicShareDisabled = computed(() => {
" "
class="flex flex-row justify-between" class="flex flex-row justify-between"
> >
<div class="flex text-black">Allow Download</div> <div class="flex text-black">{{ $t('activity.allowDownload') }}</div>
<a-switch <a-switch
v-model:checked="allowCSVDownload" v-model:checked="allowCSVDownload"
data-testid="share-download-toggle" data-testid="share-download-toggle"
@ -328,14 +328,14 @@ const isPublicShareDisabled = computed(() => {
<div v-if="activeView?.type === ViewTypes.FORM" class="flex flex-row justify-between"> <div v-if="activeView?.type === ViewTypes.FORM" class="flex flex-row justify-between">
<!-- use RTL orientation in form - todo: i18n --> <!-- use RTL orientation in form - todo: i18n -->
<div class="text-black">Survey Mode</div> <div class="text-black">{{ $t('activity.surveyMode') }}</div>
<a-switch v-model:checked="surveyMode" data-testid="nc-modal-share-view__surveyMode"> <a-switch v-model:checked="surveyMode" data-testid="nc-modal-share-view__surveyMode">
<!-- todo i18n --> <!-- todo i18n -->
</a-switch> </a-switch>
</div> </div>
<div v-if="activeView?.type === ViewTypes.FORM" class="flex flex-row justify-between"> <div v-if="activeView?.type === ViewTypes.FORM" class="flex flex-row justify-between">
<!-- use RTL orientation in form - todo: i18n --> <!-- use RTL orientation in form - todo: i18n -->
<div class="text-black">RTL Orientation</div> <div class="text-black">{{ $t('activity.rtlOrientation') }}</div>
<a-switch v-model:checked="withRTL" data-testid="nc-modal-share-view__RTL"> <a-switch v-model:checked="withRTL" data-testid="nc-modal-share-view__RTL">
<!-- todo i18n --> <!-- todo i18n -->
</a-switch> </a-switch>
@ -343,7 +343,7 @@ const isPublicShareDisabled = computed(() => {
<div v-if="activeView?.type === ViewTypes.FORM" class="flex flex-col justify-between gap-y-1 bg-gray-50 rounded-md"> <div v-if="activeView?.type === ViewTypes.FORM" class="flex flex-col justify-between gap-y-1 bg-gray-50 rounded-md">
<!-- todo: i18n --> <!-- todo: i18n -->
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<div class="text-black">Use Theme</div> <div class="text-black">{{ $t('activity.useTheme') }}</div>
<a-switch <a-switch
data-testid="share-theme-toggle" data-testid="share-theme-toggle"
:checked="viewTheme" :checked="viewTheme"

14
packages/nc-gui/components/dlg/share-and-collaborate/View.vue

@ -133,16 +133,16 @@ watch(showShareModal, (val) => {
</div> </div>
<div v-else class="flex flex-col px-1"> <div v-else class="flex flex-col px-1">
<div class="flex flex-row justify-between items-center pb-1 mx-4 mt-3"> <div class="flex flex-row justify-between items-center pb-1 mx-4 mt-3">
<div class="flex text-base font-medium">Share</div> <div class="flex text-base font-medium">{{ $t('activity.share') }}</div>
</div> </div>
<div v-if="isViewToolbar" class="share-view"> <div v-if="isViewToolbar && activeView" class="share-view">
<div class="flex flex-row items-center gap-x-2 px-4 pt-3 pb-3 select-none"> <div class="flex flex-row items-center gap-x-2 px-4 pt-3 pb-3 select-none">
<component <component
:is="viewIcons[view?.type]?.icon" :is="viewIcons[view?.type]?.icon"
class="nc-view-icon group-hover" class="nc-view-icon group-hover"
:style="{ color: viewIcons[view?.type]?.color }" :style="{ color: viewIcons[view?.type]?.color }"
/> />
<div>Share View</div> <div>{{ $t('activity.shareView') }}</div>
<div <div
class="max-w-79/100 ml-2 px-2 py-0.5 rounded-md bg-gray-100 capitalize text-ellipsis overflow-hidden" class="max-w-79/100 ml-2 px-2 py-0.5 rounded-md bg-gray-100 capitalize text-ellipsis overflow-hidden"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap' }"
@ -156,7 +156,7 @@ watch(showShareModal, (val) => {
<div class="flex flex-row items-center gap-x-2 px-4 pt-3 pb-3 select-none"> <div class="flex flex-row items-center gap-x-2 px-4 pt-3 pb-3 select-none">
<GeneralProjectIcon :type="project.type" class="nc-view-icon group-hover" /> <GeneralProjectIcon :type="project.type" class="nc-view-icon group-hover" />
<div>Share Base</div> <div>{{ $t('activity.shareBase.label') }}</div>
<div <div
class="max-w-79/100 ml-2 px-2 py-0.5 rounded-md bg-gray-100 capitalize text-ellipsis overflow-hidden" class="max-w-79/100 ml-2 px-2 py-0.5 rounded-md bg-gray-100 capitalize text-ellipsis overflow-hidden"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap' }"
@ -167,13 +167,15 @@ watch(showShareModal, (val) => {
<LazyDlgShareAndCollaborateShareBase /> <LazyDlgShareAndCollaborateShareBase />
</div> </div>
<div class="flex flex-row justify-end mx-3 mt-1 mb-2 pt-4 gap-x-2"> <div class="flex flex-row justify-end mx-3 mt-1 mb-2 pt-4 gap-x-2">
<NcButton type="secondary" data-testid="docs-cancel-btn" @click="showShareModal = false"> Close </NcButton> <NcButton type="secondary" data-testid="docs-cancel-btn" @click="showShareModal = false">
{{ $t('general.close') }}
</NcButton>
<NcButton <NcButton
data-testid="docs-share-manage-access" data-testid="docs-share-manage-access"
type="secondary" type="secondary"
:loading="isOpeningManageAccess" :loading="isOpeningManageAccess"
@click="openManageAccess" @click="openManageAccess"
>Manage project access</NcButton >{{ $t('activity.manageProjectAccess') }}</NcButton
> >
<!-- <a-button <!-- <a-button

3
packages/nc-gui/components/erd/Flow.vue

@ -107,8 +107,7 @@ onScopeDispose($destroy)
class="color-transition z-5 cursor-pointer rounded shadow-sm text-slate-400 font-semibold px-4 py-2 bg-slate-100/50 hover:(text-slate-900 ring ring-accent ring-opacity-100 bg-slate-100/90)" class="color-transition z-5 cursor-pointer rounded shadow-sm text-slate-400 font-semibold px-4 py-2 bg-slate-100/50 hover:(text-slate-900 ring ring-accent ring-opacity-100 bg-slate-100/90)"
@click="zoomIn" @click="zoomIn"
> >
<!-- todo: i18n --> {{ $t('labels.zoomInToViewColumns') }}
Zoom in to view columns
</Panel> </Panel>
</Transition> </Transition>

2
packages/nc-gui/components/general/ColorPicker.vue

@ -63,7 +63,7 @@ watch(picked, (n, _o) => {
@click="isPickerOn = !isPickerOn" @click="isPickerOn = !isPickerOn"
> >
<GeneralTooltip> <GeneralTooltip>
<template #title>More colors</template> <template #title>{{ $t('activity.moreColors') }}</template>
<GeneralIcon class="mt-1.5" :icon="isPickerOn ? 'minus' : 'plus'" /> <GeneralIcon class="mt-1.5" :icon="isPickerOn ? 'minus' : 'plus'" />
</GeneralTooltip> </GeneralTooltip>
</button> </button>

4
packages/nc-gui/components/general/CopyUrl.vue

@ -56,8 +56,8 @@ const copyUrl = async () => {
<MdiCheck v-if="isCopied.link" class="h-3.5" /> <MdiCheck v-if="isCopied.link" class="h-3.5" />
<MdiContentCopy v-else class="h-3.5" /> <MdiContentCopy v-else class="h-3.5" />
<div class="flex text-xs" :style="{ fontWeight: 500 }"> <div class="flex text-xs" :style="{ fontWeight: 500 }">
<template v-if="isCopied.link"> Link Copied </template> <template v-if="isCopied.link"> {{ $t('activity.copiedLink') }} </template>
<template v-else> Copy Link </template> <template v-else> {{ $t('activity.copyUrl') }} </template>
</div> </div>
</div> </div>
</div> </div>

2
packages/nc-gui/components/general/DeleteModal.vue

@ -47,7 +47,7 @@ onKeyStroke('Enter', () => {
</div> </div>
<div class="mb-3 text-gray-800"> <div class="mb-3 text-gray-800">
Are you sure you want to delete the following <span class="-ml-0.3">{{ props.entityName.toLowerCase() }}?</span> {{ $t('msg.areYouSureUWantTo') }}<span class="ml-1">{{ props.entityName.toLowerCase() }}?</span>
</div> </div>
<slot name="entity-preview"></slot> <slot name="entity-preview"></slot>

8
packages/nc-gui/components/general/OpenLeftSidebarBtn.vue

@ -16,16 +16,12 @@ const onClick = () => {
hide-on-click hide-on-click
class="transition-all duration-150" class="transition-all duration-150"
:class="{ :class="{
'opacity-0 w-0': !isMobileMode && isLeftSidebarOpen, 'opacity-0 w-0 pointer-events-none': !isMobileMode && isLeftSidebarOpen,
'opacity-100 max-w-10': isMobileMode || !isLeftSidebarOpen, 'opacity-100 max-w-10': isMobileMode || !isLeftSidebarOpen,
}" }"
> >
<template #title> <template #title>
{{ {{ isLeftSidebarOpen ? `${$t('title.hideSidebar')}` : `${$t('title.showSidebar')}` }}
isLeftSidebarOpen
? `${$t('general.hide')} ${$t('objects.sidebar').toLowerCase()}`
: `${$t('general.show')} ${$t('objects.sidebar').toLowerCase()}`
}}
</template> </template>
<NcButton <NcButton
:type="isMobileMode ? 'secondary' : 'text'" :type="isMobileMode ? 'secondary' : 'text'"

10
packages/nc-gui/components/project/AccessSettings.vue

@ -160,7 +160,7 @@ onMounted(async () => {
</div> </div>
<template v-else> <template v-else>
<div class="w-full flex flex-row justify-between items-baseline mt-6.5 mb-2 pr-0.25 ml-2"> <div class="w-full flex flex-row justify-between items-baseline mt-6.5 mb-2 pr-0.25 ml-2">
<a-input v-model:value="userSearchText" class="!max-w-90 !rounded-md" placeholder="Search members"> <a-input v-model:value="userSearchText" class="!max-w-90 !rounded-md" :placeholder="$t('title.searchMembers')">
<template #prefix> <template #prefix>
<PhMagnifyingGlassBold class="!h-3.5 text-gray-500" /> <PhMagnifyingGlassBold class="!h-3.5 text-gray-500" />
</template> </template>
@ -174,13 +174,13 @@ onMounted(async () => {
v-else-if="!collaborators?.length" v-else-if="!collaborators?.length"
class="nc-collaborators-list w-full h-full flex flex-col items-center justify-center mt-36" class="nc-collaborators-list w-full h-full flex flex-col items-center justify-center mt-36"
> >
<Empty description="No members found" /> <Empty :description="$t('title.noMembersFound')" />
</div> </div>
<div v-else class="nc-collaborators-list nc-scrollbar-md"> <div v-else class="nc-collaborators-list nc-scrollbar-md">
<div class="nc-collaborators-list-header"> <div class="nc-collaborators-list-header">
<div class="flex w-3/5">Users</div> <div class="flex w-3/5">{{ $t('objects.users') }}</div>
<div class="flex w-2/5">Date Joined</div> <div class="flex w-2/5">{{ $t('title.dateJoined') }}</div>
<div class="flex w-1/5">Access</div> <div class="flex w-1/5">{{ $t('general.access') }}</div>
<div class="flex w-1/5"></div> <div class="flex w-1/5"></div>
<div class="flex w-1/5"></div> <div class="flex w-1/5"></div>
</div> </div>

10
packages/nc-gui/components/project/ImportModal.vue

@ -69,25 +69,25 @@ const onClick = (type: 'airtable' | 'csv' | 'excel' | 'json') => {
<template> <template>
<GeneralModal v-model:visible="visible" width="35rem"> <GeneralModal v-model:visible="visible" width="35rem">
<div class="flex flex-col px-8 pt-6 pb-9"> <div class="flex flex-col px-8 pt-6 pb-9">
<div class="text-lg font-medium mb-6">Import</div> <div class="text-lg font-medium mb-6">{{ $t('general.import') }}</div>
<div class="row mb-10"> <div class="row mb-10">
<div class="nc-project-view-import-sub-btn" @click="onClick('airtable')"> <div class="nc-project-view-import-sub-btn" @click="onClick('airtable')">
<GeneralIcon icon="airtable" /> <GeneralIcon icon="airtable" />
<div class="label">Airtable</div> <div class="label">{{ $t('labels.airtable') }}</div>
</div> </div>
<div class="nc-project-view-import-sub-btn" @click="onClick('csv')"> <div class="nc-project-view-import-sub-btn" @click="onClick('csv')">
<GeneralIcon icon="csv" /> <GeneralIcon icon="csv" />
<div class="label">CSV</div> <div class="label">{{ $t('labels.csv') }}</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="nc-project-view-import-sub-btn" @click="onClick('excel')"> <div class="nc-project-view-import-sub-btn" @click="onClick('excel')">
<GeneralIcon icon="excelColored" /> <GeneralIcon icon="excelColored" />
<div class="label">Excel</div> <div class="label">{{ $t('labels.excel') }}</div>
</div> </div>
<div class="nc-project-view-import-sub-btn" @click="onClick('json')"> <div class="nc-project-view-import-sub-btn" @click="onClick('json')">
<GeneralIcon icon="code" /> <GeneralIcon icon="code" />
<div class="label">JSON</div> <div class="label">{{ $t('labels.json') }}</div>
</div> </div>
</div> </div>
</div> </div>

6
packages/nc-gui/components/project/View.vue

@ -80,7 +80,7 @@ watch(
<template #tab> <template #tab>
<div class="tab-title" data-testid="proj-view-tab__all-tables"> <div class="tab-title" data-testid="proj-view-tab__all-tables">
<NcLayout /> <NcLayout />
<div>All tables</div> <div>{{ $t('labels.allTables') }}</div>
<div <div
class="flex pl-1.25 px-1.5 py-0.75 rounded-md text-xs" class="flex pl-1.25 px-1.5 py-0.75 rounded-md text-xs"
:class="{ :class="{
@ -101,7 +101,7 @@ watch(
<template #tab> <template #tab>
<div class="tab-title" data-testid="proj-view-tab__access-settings"> <div class="tab-title" data-testid="proj-view-tab__access-settings">
<GeneralIcon icon="users" class="!h-3.5 !w-3.5" /> <GeneralIcon icon="users" class="!h-3.5 !w-3.5" />
<div>Members</div> <div>{{ $t('labels.members') }}</div>
</div> </div>
</template> </template>
<ProjectAccessSettings /> <ProjectAccessSettings />
@ -110,7 +110,7 @@ watch(
<template #tab> <template #tab>
<div class="tab-title" data-testid="proj-view-tab__data-sources"> <div class="tab-title" data-testid="proj-view-tab__data-sources">
<GeneralIcon icon="database" /> <GeneralIcon icon="database" />
<div>Data sources</div> <div>{{ $t('labels.dataSources') }}</div>
</div> </div>
</template> </template>
<DashboardSettingsDataSources v-model:state="baseSettingsState" /> <DashboardSettingsDataSources v-model:state="baseSettingsState" />

6
packages/nc-gui/components/smartsheet/Details.vue

@ -33,7 +33,7 @@ watch(
<template #tab> <template #tab>
<div class="tab" data-testid="nc-relations-tab"> <div class="tab" data-testid="nc-relations-tab">
<GeneralIcon icon="erd" class="tab-icon" :class="{}" /> <GeneralIcon icon="erd" class="tab-icon" :class="{}" />
<div>Relations</div> <div>{{ $t('title.relations') }}</div>
</div> </div>
</template> </template>
<SmartsheetDetailsErd /> <SmartsheetDetailsErd />
@ -43,7 +43,7 @@ watch(
<template #tab> <template #tab>
<div class="tab" data-testid="nc-apis-tab"> <div class="tab" data-testid="nc-apis-tab">
<GeneralIcon icon="code" class="tab-icon" :class="{}" /> <GeneralIcon icon="code" class="tab-icon" :class="{}" />
<div>APIs</div> <div>{{ $t('labels.apis') }}</div>
</div> </div>
</template> </template>
<SmartsheetDetailsApi /> <SmartsheetDetailsApi />
@ -53,7 +53,7 @@ watch(
<template #tab> <template #tab>
<div class="tab" data-testid="nc-webhooks-tab"> <div class="tab" data-testid="nc-webhooks-tab">
<GeneralIcon icon="webhook" class="tab-icon" :class="{}" /> <GeneralIcon icon="webhook" class="tab-icon" :class="{}" />
<div>Webhooks</div> <div>{{ $t('objects.webhooks') }}</div>
</div> </div>
</template> </template>
<SmartsheetDetailsWebhooks /> <SmartsheetDetailsWebhooks />

2
packages/nc-gui/components/smartsheet/Toolbar.vue

@ -16,7 +16,7 @@ const { allowCSVDownload } = useSharedView()
<template> <template>
<div <div
class="nc-table-toolbar py-1 px-1 xs:(px-1) flex gap-2 items-center border-b border-gray-200 overflow-hidden xs:(min-h-14) max-h-[var(--topbar-height)] min-h-[var(--topbar-height)] z-7" class="nc-table-toolbar py-1 px-2.25 xs:(px-1) flex gap-2 items-center border-b border-gray-200 overflow-hidden xs:(min-h-14) max-h-[var(--topbar-height)] min-h-[var(--topbar-height)] z-7"
> >
<template v-if="isViewsLoading"> <template v-if="isViewsLoading">
<a-skeleton-input :active="true" class="!w-44 !h-4 ml-2 !rounded overflow-hidden" /> <a-skeleton-input :active="true" class="!w-44 !h-4 ml-2 !rounded overflow-hidden" />

2
packages/nc-gui/components/smartsheet/Topbar.vue

@ -19,7 +19,7 @@ const isSharedBase = computed(() => route.value.params.typeOrId === 'base')
<template> <template>
<div <div
class="nc-table-topbar h-20 py-1 flex gap-2 items-center border-b border-gray-200 overflow-hidden relative max-h-[var(--topbar-height)] min-h-[var(--topbar-height)] md:(pr-2 pl-2.5) xs:(px-1)" class="nc-table-topbar h-20 py-1 flex gap-2 items-center border-b border-gray-200 overflow-hidden relative max-h-[var(--topbar-height)] min-h-[var(--topbar-height)] md:(pr-2 pl-2) xs:(px-1)"
style="z-index: 7" style="z-index: 7"
> >
<template v-if="isViewsLoading"> <template v-if="isViewsLoading">

4
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -258,7 +258,7 @@ if (props.fromTableExplorer) {
<div class="flex gap-1 items-center"> <div class="flex gap-1 items-center">
<component :is="opt.icon" class="text-gray-700 mx-1" /> <component :is="opt.icon" class="text-gray-700 mx-1" />
{{ opt.name }} {{ opt.name }}
<span v-if="opt.deprecated" class="!text-xs !text-gray-300">(Deprecated)</span> <span v-if="opt.deprecated" class="!text-xs !text-gray-300">({{ $t('general.deprecated') }})</span>
</div> </div>
</a-select-option> </a-select-option>
</a-select> </a-select>
@ -297,7 +297,7 @@ if (props.fromTableExplorer) {
class="ml-1 mb-1" class="ml-1 mb-1"
> >
<span class="text-[10px] text-gray-600"> <span class="text-[10px] text-gray-600">
{{ `Accept only valid ${formState.uidt}` }} {{ `${$t('msg.acceptOnlyValid')} ${formState.uidt}` }}
</span> </span>
</a-checkbox> </a-checkbox>
<div class="!my-3"> <div class="!my-3">

14
packages/nc-gui/components/smartsheet/column/LinkOptions.vue

@ -7,6 +7,8 @@ const props = defineProps<{
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value'])
const { t } = useI18n()
const vModel = useVModel(props, 'value', emit) const vModel = useVModel(props, 'value', emit)
const { validateInfos, setAdditionalValidations } = useColumnCreateStoreOrThrow() const { validateInfos, setAdditionalValidations } = useColumnCreateStoreOrThrow()
@ -17,7 +19,7 @@ setAdditionalValidations({
validator: (_, value: string) => { validator: (_, value: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (value?.length > 59) { if (value?.length > 59) {
return reject(new Error('The length exceeds the max 59 characters')) return reject(t('msg.length59Required'))
} }
resolve(true) resolve(true)
}) })
@ -29,7 +31,7 @@ setAdditionalValidations({
validator: (_, value: string) => { validator: (_, value: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (value?.length > 59) { if (value?.length > 59) {
return reject(new Error('The length exceeds the max 59 characters')) return reject(t('msg.length59Required'))
} }
resolve(true) resolve(true)
}) })
@ -49,14 +51,14 @@ vModel.value.meta = {
<template> <template>
<a-row class="my-2" gutter="8"> <a-row class="my-2" gutter="8">
<a-col :span="12"> <a-col :span="12">
<a-form-item v-bind="validateInfos['meta.singular']" label="Singular Label"> <a-form-item v-bind="validateInfos['meta.singular']" :label="$t('labels.singularLabel')">
<a-input v-model:value="vModel.meta.singular" placeholder="Link" class="!w-full nc-link-singular" /> <a-input v-model:value="vModel.meta.singular" :placeholder="$t('general.link')" class="!w-full nc-link-singular" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-form-item v-bind="validateInfos['meta.plural']" label="Plural Label"> <a-form-item v-bind="validateInfos['meta.plural']" :label="$t('labels.pluralLabel')">
<a-input v-model:value="vModel.meta.plural" placeholder="Links" class="!w-full nc-link-plural" /> <a-input v-model:value="vModel.meta.plural" :placeholder="$t('general.links')" class="!w-full nc-link-plural" />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>

8
packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue

@ -58,8 +58,8 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
<div class="border-2 p-6"> <div class="border-2 p-6">
<a-form-item v-bind="validateInfos.type" class="nc-ltar-relation-type"> <a-form-item v-bind="validateInfos.type" class="nc-ltar-relation-type">
<a-radio-group v-model:value="vModel.type" name="type" v-bind="validateInfos.type"> <a-radio-group v-model:value="vModel.type" name="type" v-bind="validateInfos.type">
<a-radio value="hm">Has Many</a-radio> <a-radio value="hm">{{ $t('title.hasMany') }}</a-radio>
<a-radio value="mm">Many To Many</a-radio> <a-radio value="mm">{{ $t('title.manyToMany') }}</a-radio>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
@ -132,7 +132,9 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
<div class="flex flex-row"> <div class="flex flex-row">
<a-form-item> <a-form-item>
<a-checkbox v-model:checked="vModel.virtual" name="virtual" @change="onDataTypeChange">Virtual Relation</a-checkbox> <a-checkbox v-model:checked="vModel.virtual" name="virtual" @change="onDataTypeChange">{{
$t('title.virtualRelation')
}}</a-checkbox>
</a-form-item> </a-form-item>
</div> </div>
</template> </template>

30
packages/nc-gui/components/smartsheet/details/Webhooks.vue

@ -156,11 +156,11 @@ watch(
<GeneralIcon icon="arrowLeft" /> <GeneralIcon icon="arrowLeft" />
</NcButton> </NcButton>
<div class="flex flex-row ml-2"> <div class="flex flex-row ml-2">
<NuxtLink class="link" :to="webhookMainUrl">Webhooks</NuxtLink> <NuxtLink class="link" :to="webhookMainUrl">{{ $t('objects.webhooks') }}</NuxtLink>
</div> </div>
<template v-if="selectedHook || isDraftMode"> <template v-if="selectedHook || isDraftMode">
<div class="flex text-gray-400">/</div> <div class="flex text-gray-400">/</div>
<div class="flex link">{{ selectedHook ? selectedHook.title : 'Create' }}</div> <div class="flex link">{{ selectedHook ? selectedHook.title : $t('general.create') }}</div>
</template> </template>
<div <div
v-if="selectedHook" v-if="selectedHook"
@ -170,7 +170,7 @@ watch(
'bg-gray-100 text-gray-500': !selectedHook.active, 'bg-gray-100 text-gray-500': !selectedHook.active,
}" }"
> >
{{ selectedHook.active ? 'Active' : 'Inactive' }} {{ selectedHook.active ? $t('general.active') : $t('general.inactive') }}
</div> </div>
</div> </div>
<NcButton <NcButton
@ -182,7 +182,7 @@ watch(
@click="createWebhook()" @click="createWebhook()"
> >
<div class="flex flex-row items-center justify-between w-full text-brand-500"> <div class="flex flex-row items-center justify-between w-full text-brand-500">
<span class="ml-1">New Webhook</span> <span class="ml-1">{{ $t('activity.newWebhook') }}</span>
<GeneralIcon icon="plus" /> <GeneralIcon icon="plus" />
</div> </div>
</NcButton> </NcButton>
@ -190,24 +190,24 @@ watch(
<div v-if="!selectedHookId && !isDraftMode" class="flex flex-col h-full w-full items-center"> <div v-if="!selectedHookId && !isDraftMode" class="flex flex-col h-full w-full items-center">
<div v-if="hooks.length === 0" class="flex flex-col px-1.5 py-2.5 ml-1 h-full justify-center items-center gap-y-6"> <div v-if="hooks.length === 0" class="flex flex-col px-1.5 py-2.5 ml-1 h-full justify-center items-center gap-y-6">
<GeneralIcon icon="webhook" class="flex text-5xl h-10" style="-webkit-text-stroke: 0.5px" /> <GeneralIcon icon="webhook" class="flex text-5xl h-10" style="-webkit-text-stroke: 0.5px" />
<div class="flex text-gray-600 font-medium text-lg">Get started with web-hooks!</div> <div class="flex text-gray-600 font-medium text-lg">{{ $t('msg.createWebhookMsg1') }}</div>
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<div class="flex">Create web-hooks to power you automations,</div> <div class="flex">{{ $t('msg.createWebhookMsg2') }}</div>
<div class="flex">Get notified as soon as there are changes in your data</div> <div class="flex">{{ $t('msg.createWebhookMsg3') }}</div>
</div> </div>
<NcButton v-e="['c:actions:webhook']" class="flex max-w-40" type="primary" @click="createWebhook()"> <NcButton v-e="['c:actions:webhook']" class="flex max-w-40" type="primary" @click="createWebhook()">
<div class="flex flex-row items-center justify-between w-full"> <div class="flex flex-row items-center justify-between w-full">
<span class="ml-1">New Webhook</span> <span class="ml-1">{{ $t('activity.newWebhook') }}</span>
<GeneralIcon icon="plus" /> <GeneralIcon icon="plus" />
</div> </div>
</NcButton> </NcButton>
</div> </div>
<div v-else class="flex flex-col pb-2 mt-3 mb-2.5 w-full max-w-200"> <div v-else class="flex flex-col pb-2 mt-3 mb-2.5 w-full max-w-200">
<div class="flex flex-row nc-view-sidebar-webhook-header pl-3 pr-2 !py-2.5"> <div class="flex flex-row nc-view-sidebar-webhook-header pl-3 pr-2 !py-2.5">
<div class="nc-view-sidebar-webhook-item-toggle header">Activate</div> <div class="nc-view-sidebar-webhook-item-toggle header">{{ $t('general.activate') }}</div>
<div class="nc-view-sidebar-webhook-item-title header">Title</div> <div class="nc-view-sidebar-webhook-item-title header">{{ $t('general.title') }}</div>
<div class="nc-view-sidebar-webhook-item-event header">Event</div> <div class="nc-view-sidebar-webhook-item-event header">{{ $t('general.event') }}</div>
<div class="nc-view-sidebar-webhook-item-action header">Action</div> <div class="nc-view-sidebar-webhook-item-action header">{{ $t('general.action') }}</div>
</div> </div>
<div v-for="hook in hooks" :key="hook.id" class="nc-view-sidebar-webhook-item"> <div v-for="hook in hooks" :key="hook.id" class="nc-view-sidebar-webhook-item">
<div <div
@ -255,13 +255,13 @@ watch(
:centered="false" :centered="false"
@click="copyWebhook(hook)" @click="copyWebhook(hook)"
> >
<template #loading> Duplicating </template> <template #loading> {{ $t('general.duplicating') }} </template>
<div class="flex items-center gap-x-1"><GeneralIcon icon="copy" /> Duplicate</div> <div class="flex items-center gap-x-1"><GeneralIcon icon="copy" /> {{ $t('general.duplicate') }}</div>
</NcButton> </NcButton>
<NcButton type="text" class="w-full !rounded-none" :centered="false" @click="openDeleteModal(hook.id!)"> <NcButton type="text" class="w-full !rounded-none" :centered="false" @click="openDeleteModal(hook.id!)">
<div class="flex items-center justify-start gap-x-1 !text-red-500"> <div class="flex items-center justify-start gap-x-1 !text-red-500">
<GeneralIcon icon="delete" /> <GeneralIcon icon="delete" />
Delete {{ $t('general.delete') }}
</div> </div>
</NcButton> </NcButton>
</div> </div>

2
packages/nc-gui/components/smartsheet/grid/GroupBy.vue

@ -191,7 +191,7 @@ const onScroll = (e: Event) => {
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex gap-2"> <div class="flex gap-2">
<div class="text-xs nc-group-column-title">{{ grp.column.title }}</div> <div class="text-xs nc-group-column-title">{{ grp.column.title }}</div>
<div class="text-xs text-gray-400 nc-group-row-count">(Count: {{ grp.count }})</div> <div class="text-xs text-gray-400 nc-group-row-count">({{ $t('datatype.Count') }}: {{ grp.count }})</div>
</div> </div>
<div class="flex mt-1"> <div class="flex mt-1">
<template v-if="grp.column.uidt === 'MultiSelect'"> <template v-if="grp.column.uidt === 'MultiSelect'">

17
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -1247,7 +1247,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<template #title> <template #title>
<div class="flex flex-row items-center py-3"> <div class="flex flex-row items-center py-3">
<MdiTableColumnPlusAfter class="flex h-[1rem] text-gray-500" /> <MdiTableColumnPlusAfter class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">Predict Columns</div> <div class="text-xs pl-2">{{ $t('activity.predictColumns') }}</div>
<MdiChevronRight class="text-gray-500 ml-2" /> <MdiChevronRight class="text-gray-500 ml-2" />
</div> </div>
</template> </template>
@ -1276,14 +1276,14 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<div class="flex flex-row items-center py-3" @click="predictNextColumn"> <div class="flex flex-row items-center py-3" @click="predictNextColumn">
<MdiReload v-if="predictingNextColumn" class="animate-infinite animate-spin" /> <MdiReload v-if="predictingNextColumn" class="animate-infinite animate-spin" />
<MdiTableColumnPlusAfter v-else class="flex h-[1rem] text-gray-500" /> <MdiTableColumnPlusAfter v-else class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">Predict Columns</div> <div class="text-xs pl-2">{{ $t('activity.predictColumns') }}</div>
</div> </div>
</NcMenuItem> </NcMenuItem>
<a-sub-menu v-if="predictedNextFormulas" key="predict-formula"> <a-sub-menu v-if="predictedNextFormulas" key="predict-formula">
<template #title> <template #title>
<div class="flex flex-row items-center py-3"> <div class="flex flex-row items-center py-3">
<MdiCalculatorVariant class="flex h-[1rem] text-gray-500" /> <MdiCalculatorVariant class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">Predict Formulas</div> <div class="text-xs pl-2">{{ $t('activity.predictFormulas') }}</div>
<MdiChevronRight class="text-gray-500 ml-2" /> <MdiChevronRight class="text-gray-500 ml-2" />
</div> </div>
</template> </template>
@ -1306,7 +1306,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<div class="flex flex-row items-center py-3" @click="predictNextFormulas"> <div class="flex flex-row items-center py-3" @click="predictNextFormulas">
<MdiReload v-if="predictingNextFormulas" class="animate-infinite animate-spin" /> <MdiReload v-if="predictingNextFormulas" class="animate-infinite animate-spin" />
<MdiCalculatorVariant v-else class="flex h-[1rem] text-gray-500" /> <MdiCalculatorVariant v-else class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">Predict Formulas</div> <div class="text-xs pl-2">{{ $t('activity.predictFormulas') }}</div>
</div> </div>
</NcMenuItem> </NcMenuItem>
</NcMenu> </NcMenu>
@ -1513,8 +1513,8 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
@click="emits('bulkUpdateDlg')" @click="emits('bulkUpdateDlg')"
> >
<component :is="iconMap.edit" /> <component :is="iconMap.edit" />
<!-- TODO i18n -->
Update Selected Rows {{ $t('title.updateSelectedRows') }}
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcMenuItem
@ -1575,7 +1575,8 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
@click="clearSelectedRangeOfCells()" @click="clearSelectedRangeOfCells()"
> >
<GeneralIcon icon="closeBox" class="text-gray-500" /> <GeneralIcon icon="closeBox" class="text-gray-500" />
Clear
{{ $t('general.clear') }}
</NcMenuItem> </NcMenuItem>
<NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected))" /> <NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected))" />
<NcMenuItem <NcMenuItem
@ -1592,7 +1593,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<NcMenuItem v-e="['a:row:delete']" class="nc-project-menu-item text-red-600" @click="deleteSelectedRangeOfRows"> <NcMenuItem v-e="['a:row:delete']" class="nc-project-menu-item text-red-600" @click="deleteSelectedRangeOfRows">
<GeneralIcon icon="delete" class="text-gray-500 text-error" /> <GeneralIcon icon="delete" class="text-gray-500 text-error" />
<!-- Delete Rows --> <!-- Delete Rows -->
Delete rows {{ $t('activity.deleteRows') }}
</NcMenuItem> </NcMenuItem>
</div> </div>
</NcMenu> </NcMenu>

2
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -488,7 +488,7 @@ onMounted(async () => {
'ml-0.5': !nested, 'ml-0.5': !nested,
}" }"
> >
No filters added {{ $t('title.noFiltersAdded') }}
</div> </div>
<slot /> <slot />

4
packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue

@ -89,10 +89,10 @@ const onArrowUp = () => {
@keydown.enter.prevent="onClick(options[activeFieldIndex])" @keydown.enter.prevent="onClick(options[activeFieldIndex])"
> >
<div class="flex pb-3 px-4 border-b-1 border-gray-100"> <div class="flex pb-3 px-4 border-b-1 border-gray-100">
<input ref="inputRef" v-model="search" class="w-full focus:outline-none" placeholder="Select Field to Sort" /> <input ref="inputRef" v-model="search" class="w-full focus:outline-none" :placeholder="$t('msg.selectFieldToSort')" />
</div> </div>
<div class="flex-col w-full max-h-100 nc-scrollbar-md !overflow-y-auto"> <div class="flex-col w-full max-h-100 nc-scrollbar-md !overflow-y-auto">
<div v-if="!options.length" class="flex text-gray-500 px-4 py-2.25">Empty</div> <div v-if="!options.length" class="flex text-gray-500 px-4 py-2.25">{{ $t('general.empty') }}</div>
<div <div
v-for="(option, index) in options" v-for="(option, index) in options"
:key="index" :key="index"

10
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -305,7 +305,7 @@ useMenuCloseOnEsc(open)
<!-- Fields --> <!-- Fields -->
<span v-if="!isMobileMode" class="text-capitalize text-sm font-medium"> <span v-if="!isMobileMode" class="text-capitalize text-sm font-medium">
<template v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY"> <template v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY">
Edit Cards {{ $t('title.editCards') }}
</template> </template>
<template v-else> <template v-else>
{{ $t('objects.fields') }} {{ $t('objects.fields') }}
@ -352,7 +352,7 @@ useMenuCloseOnEsc(open)
v-if="!fields?.filter((el) => el.title.toLowerCase().includes(filterQuery.toLowerCase())).length" v-if="!fields?.filter((el) => el.title.toLowerCase().includes(filterQuery.toLowerCase())).length"
class="px-0.5 py-2 text-gray-500" class="px-0.5 py-2 text-gray-500"
> >
No fields found {{ $t('title.noFieldsFound') }}
</div> </div>
<Draggable v-model="fields" item-key="id" @change="onMove($event)"> <Draggable v-model="fields" item-key="id" @change="onMove($event)">
<template #item="{ element: field }"> <template #item="{ element: field }">
@ -401,7 +401,7 @@ useMenuCloseOnEsc(open)
<div class="flex items"> <div class="flex items">
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> <template #title>
<span class="text-sm">Display Value</span> <span class="text-sm">$t('title.displayValue') </span>
</template> </template>
</a-tooltip> </a-tooltip>
@ -426,7 +426,7 @@ useMenuCloseOnEsc(open)
class="nc-fields-show-all-fields !text-gray-500 !w-1/2" class="nc-fields-show-all-fields !text-gray-500 !w-1/2"
@click="showAllColumns = !showAllColumns" @click="showAllColumns = !showAllColumns"
> >
{{ showAllColumns ? 'Hide all' : $t('general.showAll') }} {{ $t('objects.fields').toLowerCase() }} {{ showAllColumns ? $t('title.hideAll') : $t('general.showAll') }} {{ $t('objects.fields').toLowerCase() }}
</NcButton> </NcButton>
<NcButton <NcButton
v-if="!isPublic && !filterQuery" v-if="!isPublic && !filterQuery"
@ -435,7 +435,7 @@ useMenuCloseOnEsc(open)
class="nc-fields-show-system-fields !text-gray-500 !w-1/2" class="nc-fields-show-system-fields !text-gray-500 !w-1/2"
@click="showSystemField = !showSystemField" @click="showSystemField = !showSystemField"
> >
{{ showSystemField ? 'Hide system fields' : $t('activity.showSystemFields') }} {{ showSystemField ? $t('title.hideSystemFields') : $t('activity.showSystemFields') }}
</NcButton> </NcButton>
</div> </div>
</div> </div>

2
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -135,7 +135,7 @@ watch(columns, () => {
v-model:value="search.query" v-model:value="search.query"
size="small" size="small"
class="text-xs w-40" class="text-xs w-40"
:placeholder="`${$t('general.search')} in ${columns?.find((column) => column.value === search.field)?.label}`" :placeholder="`${$t('general.searchIn')} ${columns?.find((column) => column.value === search.field)?.label}`"
:bordered="false" :bordered="false"
data-testid="search-data-input" data-testid="search-data-input"
@press-enter="onPressEnter" @press-enter="onPressEnter"

2
packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue

@ -128,7 +128,7 @@ watch(open, () => {
<SmartsheetToolbarCreateSort v-if="!sorts.length" :is-parent-open="open" @created="addSort" /> <SmartsheetToolbarCreateSort v-if="!sorts.length" :is-parent-open="open" @created="addSort" />
<div <div
v-else v-else
:class="{ ' min-w-102': sorts.length }" :class="{ 'min-w-102': sorts.length }"
class="py-6 pl-6 nc-filter-list max-h-[max(80vh,30rem)]" class="py-6 pl-6 nc-filter-list max-h-[max(80vh,30rem)]"
data-testid="nc-sorts-menu" data-testid="nc-sorts-menu"
> >

4
packages/nc-gui/components/smartsheet/topbar/SelectMode.vue

@ -17,7 +17,7 @@ const { onViewsTabChange } = useViewsStore()
> >
<GeneralViewIcon v-if="activeView?.type" class="tab-icon" :meta="{ type: activeView?.type }" ignore-color /> <GeneralViewIcon v-if="activeView?.type" class="tab-icon" :meta="{ type: activeView?.type }" ignore-color />
<GeneralLoader v-else class="tab-icon" /> <GeneralLoader v-else class="tab-icon" />
<div class="tab-title nc-tab">Data</div> <div class="tab-title nc-tab">{{ $t('general.data') }}</div>
</div> </div>
<div <div
class="tab" class="tab"
@ -34,7 +34,7 @@ const { onViewsTabChange } = useViewsStore()
fontWeight: 500, fontWeight: 500,
}" }"
/> />
<div class="tab-title nc-tab">Details</div> <div class="tab-title nc-tab">{{ $t('general.details') }}</div>
</div> </div>
</div> </div>
</template> </template>

12
packages/nc-gui/components/virtual-cell/Links.vue

@ -29,6 +29,8 @@ const childListDlg = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const { t } = useI18n()
const { state, isNew } = useSmartsheetRowStoreOrThrow() const { state, isNew } = useSmartsheetRowStoreOrThrow()
const { relatedTableMeta, loadRelatedTableMeta, relatedTableDisplayValueProp } = useProvideLTARStore( const { relatedTableMeta, loadRelatedTableMeta, relatedTableDisplayValueProp } = useProvideLTARStore(
@ -47,18 +49,18 @@ loadRelatedTableMeta()
const textVal = computed(() => { const textVal = computed(() => {
if (isForm?.value) { if (isForm?.value) {
return state.value?.[colTitle.value]?.length return state.value?.[colTitle.value]?.length
? `${+state.value?.[colTitle.value]?.length} records Linked` ? `${+state.value?.[colTitle.value]?.length} ${t('msg.recordsLinked')}`
: 'No records linked' : t('msg.noRecordsLinked')
} }
const parsedValue = +value?.value || 0 const parsedValue = +value?.value || 0
if (!parsedValue) { if (!parsedValue) {
return 'No records linked' return t('msg.noRecordsLinked')
} else if (parsedValue === 1) { } else if (parsedValue === 1) {
return `1 ${column.value?.meta?.singular || 'Link'}` return `1 ${column.value?.meta?.singular || t('general.link')}`
} else { } else {
return `${parsedValue} ${column.value?.meta?.plural || 'Links'}` return `${parsedValue} ${column.value?.meta?.plural || t('general.links')}`
} }
}) })

27
packages/nc-gui/components/virtual-cell/components/Header.vue

@ -14,34 +14,37 @@ const { relation, relatedTableTitle, displayValue, showHeader, tableTitle } = de
displayValue?: string displayValue?: string
}>() }>()
const { t } = useI18n()
const relationMeta = computed(() => { const relationMeta = computed(() => {
if (relation === 'hm') { if (relation === 'hm') {
return { return {
title: 'Has Many Relation', title: t('msg.hm.title'),
icon: HasManyIcon, icon: HasManyIcon,
tooltip_desc: 'A single record from table ', tooltip_desc: t('msg.hm.tooltip_desc'),
tooltip_desc2: ' can be linked with a multiple records from table ', tooltip_desc2: t('msg.hm.tooltip_desc2'),
} }
} else if (relation === 'mm') { } else if (relation === 'mm') {
return { return {
title: 'Many to Many Relation', title: t('msg.mm.title'),
icon: ManytoManyIcon, icon: ManytoManyIcon,
tooltip_desc: 'Multiple records from table ',
tooltip_desc2: ' can be linked with multiple records from table ', tooltip_desc: t('msg.mm.tooltip_desc'),
tooltip_desc2: t('msg.mm.tooltip_desc2'),
} }
} else if (relation === 'bt') { } else if (relation === 'bt') {
return { return {
title: 'Belongs to Relation', title: t('msg.bt.title'),
icon: BelongsToIcon, icon: BelongsToIcon,
tooltip_desc: 'A single record from table ', tooltip_desc: t('msg.bt.tooltip_desc'),
tooltip_desc2: ' can be linked with a record from table ', tooltip_desc2: t('msg.bt.tooltip_desc2'),
} }
} else { } else {
return { return {
title: 'One to One Relation', title: t('msg.oo.title'),
icon: OnetoOneIcon, icon: OnetoOneIcon,
tooltip_desc: 'A single record from table ', tooltip_desc: t('msg.oo.tooltip_desc'),
tooltip_desc2: ' can be linked with a single record from table ', tooltip_desc2: t('msg.oo.tooltip_desc2'),
} }
} }
}) })

15
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -242,7 +242,7 @@ onKeyStroke('Escape', () => {
> >
<InboxIcon class="w-16 h-16 mx-auto" /> <InboxIcon class="w-16 h-16 mx-auto" />
<p> <p>
No records are linked from table {{ $t('msg.noRecordsAreLinkedFromTable') }}
{{ relatedTableMeta?.title }} {{ relatedTableMeta?.title }}
</p> </p>
<NcButton <NcButton
@ -250,7 +250,7 @@ onKeyStroke('Escape', () => {
data-testid="nc-child-list-button-link-to" data-testid="nc-child-list-button-link-to"
@click="emit('attachRecord')" @click="emit('attachRecord')"
> >
<div class="flex items-center gap-1"><MdiPlus /> Link more records</div> <div class="flex items-center gap-1"><MdiPlus /> {{ $t('title.linkMoreRecords') }}</div>
</NcButton> </NcButton>
</div> </div>
@ -258,10 +258,13 @@ onKeyStroke('Escape', () => {
<div class="flex flex-row justify-between bg-white relative pt-1"> <div class="flex flex-row justify-between bg-white relative pt-1">
<div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50"> <div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">
{{ childrenListCount || 0 }} records {{ childrenListCount !== 0 ? 'are' : '' }} linked {{ childrenListCount || 0 }} {{ $t('objects.records') }} {{ childrenListCount !== 0 ? $t('general.are') : '' }}
{{ $t('general.linked') }}
</div> </div>
<div v-else class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50"> <div v-else class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">
{{ state?.[colTitle]?.length || 0 }} records {{ state?.[colTitle]?.length !== 0 ? 'are' : '' }} linked {{ state?.[colTitle]?.length || 0 }} {{ $t('objects.records') }}
{{ state?.[colTitle]?.length !== 0 ? $t('general.are') : '' }}
{{ $t('general.linked') }}
</div> </div>
<div class="flex absolute items-center py-2 justify-center w-full"> <div class="flex absolute items-center py-2 justify-center w-full">
<a-pagination <a-pagination
@ -277,13 +280,13 @@ onKeyStroke('Escape', () => {
/> />
</div> </div>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<NcButton v-if="!isForm" type="ghost" class="nc-close-btn" @click="vModel = false"> Finish </NcButton> <NcButton v-if="!isForm" type="ghost" class="nc-close-btn" @click="vModel = false"> {{ $t('general.finish') }} </NcButton>
<NcButton <NcButton
v-if="!readonly && childrenListCount > 0" v-if="!readonly && childrenListCount > 0"
data-testid="nc-child-list-button-link-to" data-testid="nc-child-list-button-link-to"
@click="emit('attachRecord')" @click="emit('attachRecord')"
> >
<div class="flex items-center gap-1"><MdiPlus /> Link more records</div> <div class="flex items-center gap-1"><MdiPlus /> {{ $t('title.linkMoreRecords') }}</div>
</NcButton> </NcButton>
</div> </div>
</div> </div>

14
packages/nc-gui/components/virtual-cell/components/ListItems.vue

@ -176,7 +176,7 @@ onKeyStroke('Escape', () => {
<a-input <a-input
ref="filterQueryRef" ref="filterQueryRef"
v-model:value="childrenExcludedListPagination.query" v-model:value="childrenExcludedListPagination.query"
:placeholder="`Search in ${relatedTableMeta?.title}`" :placeholder="`${$t('general.searchIn')} ${relatedTableMeta?.title}`"
class="w-full !rounded-md nc-excluded-search" class="w-full !rounded-md nc-excluded-search"
size="small" size="small"
:bordered="false" :bordered="false"
@ -193,7 +193,7 @@ onKeyStroke('Escape', () => {
<!-- Add new record --> <!-- Add new record -->
<NcButton <NcButton
v-if="!isPublic" v-if="!isPublic"
type="ghost" type="secondary"
size="xl" size="xl"
class="!text-brand-500" class="!text-brand-500"
@click=" @click="
@ -203,7 +203,7 @@ onKeyStroke('Escape', () => {
} }
" "
> >
<div class="flex items-center gap-1"><MdiPlus /> New Record</div> <div class="flex items-center gap-1"><MdiPlus /> {{ $t('activity.newRecord') }}</div>
</NcButton> </NcButton>
</div> </div>
@ -271,7 +271,7 @@ onKeyStroke('Escape', () => {
<div v-else class="py-2 h-[420px] flex flex-col gap-3 items-center justify-center text-gray-500"> <div v-else class="py-2 h-[420px] flex flex-col gap-3 items-center justify-center text-gray-500">
<InboxIcon class="w-16 h-16 mx-auto" /> <InboxIcon class="w-16 h-16 mx-auto" />
<p> <p>
There are no records in table {{ $t('msg.thereAreNoRecordsInTable') }}
{{ relatedTableMeta?.title }} {{ relatedTableMeta?.title }}
</p> </p>
</div> </div>
@ -279,8 +279,8 @@ onKeyStroke('Escape', () => {
<div class="flex flex-row justify-between bg-white relative pt-1"> <div class="flex flex-row justify-between bg-white relative pt-1">
<div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50"> <div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">
{{ relation === 'bt' ? (row.row[relatedTableMeta?.title] ? '1' : 0) : childrenListCount ?? 'No' }} records {{ relation === 'bt' ? (row.row[relatedTableMeta?.title] ? '1' : 0) : childrenListCount ?? 'No' }}
{{ childrenListCount !== 0 ? 'are' : '' }} linked {{ $t('objects.records') }} {{ childrenListCount !== 0 ? 'are' : '' }} {{ $t('general.linked') }}
</div> </div>
<div class="flex absolute items-center py-2 justify-center w-full"> <div class="flex absolute items-center py-2 justify-center w-full">
<a-pagination <a-pagination
@ -295,7 +295,7 @@ onKeyStroke('Escape', () => {
show-less-items show-less-items
/> />
</div> </div>
<NcButton class="nc-close-btn ml-auto" type="ghost" @click="vModel = false"> Finish </NcButton> <NcButton class="nc-close-btn ml-auto" type="ghost" @click="vModel = false"> {{ $t('general.finish') }} </NcButton>
</div> </div>
<Suspense> <Suspense>
<LazySmartsheetExpandedForm <LazySmartsheetExpandedForm

6
packages/nc-gui/composables/useColumnCreateStore.ts

@ -103,7 +103,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
title: [ title: [
{ {
required: true, required: true,
message: 'Column name is required', message: t('msg.error.columnNameRequired'),
}, },
// validation for unique column name // validation for unique column name
{ {
@ -118,7 +118,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
(value || '').toLowerCase() === (c.title || '').toLowerCase()), (value || '').toLowerCase() === (c.title || '').toLowerCase()),
) )
) { ) {
return reject(new Error('Duplicate column name')) return reject(new Error(t('msg.error.duplicateColumnName')))
} }
resolve() resolve()
}) })
@ -129,7 +129,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
uidt: [ uidt: [
{ {
required: true, required: true,
message: 'UI Datatype is required', message: t('msg.error.uiDataTypeRequired'),
}, },
], ],
...(additionalValidations?.value || {}), ...(additionalValidations?.value || {}),

224
packages/nc-gui/lang/en.json

@ -46,19 +46,31 @@
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
"ok": "OK", "ok": "OK",
"back": "Back",
"and": "And", "and": "And",
"or": "Or", "or": "Or",
"add": "Add", "add": "Add",
"edit": "Edit", "edit": "Edit",
"link": "Link",
"links": "Links",
"remove": "Remove", "remove": "Remove",
"import": "Import",
"logout": "Log Out",
"empty": "Empty",
"changeIcon": "Change Icon",
"save": "Save", "save": "Save",
"abort": "Abort",
"saving": "Saving", "saving": "Saving",
"cancel": "Cancel", "cancel": "Cancel",
"clear": "Clear", "clear": "Clear",
"submit": "Submit", "submit": "Submit",
"create": "Create", "create": "Create",
"details": "Details",
"skip": "Skip", "skip": "Skip",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicating": "Duplicating",
"activate": "Activate",
"action": "Action",
"insert": "Insert", "insert": "Insert",
"delete": "Delete", "delete": "Delete",
"deleting": "Deleting", "deleting": "Deleting",
@ -68,7 +80,10 @@
"reset": "Reset", "reset": "Reset",
"install": "Install", "install": "Install",
"show": "Show", "show": "Show",
"access": "Access",
"visibility": "Visibility",
"hide": "Hide", "hide": "Hide",
"deprecated": "Deprecated",
"showAll": "Show all", "showAll": "Show all",
"hideAll": "Hide all", "hideAll": "Hide all",
"showMore": "Show more", "showMore": "Show more",
@ -90,6 +105,7 @@
"upload": "Upload", "upload": "Upload",
"download": "Download", "download": "Download",
"default": "Default", "default": "Default",
"base": "Base",
"more": "More", "more": "More",
"less": "Less", "less": "Less",
"event": "Event", "event": "Event",
@ -97,12 +113,14 @@
"after": "After", "after": "After",
"before": "Before", "before": "Before",
"search": "Search", "search": "Search",
"searchIn": "Search In",
"notification": "Notification", "notification": "Notification",
"reference": "Reference", "reference": "Reference",
"function": "Function", "function": "Function",
"confirm": "Confirm", "confirm": "Confirm",
"generate": "Generate", "generate": "Generate",
"copy": "Copy", "copy": "Copy",
"are": "are",
"misc": "Miscellaneous", "misc": "Miscellaneous",
"lock": "Lock", "lock": "Lock",
"unlock": "Unlock", "unlock": "Unlock",
@ -128,7 +146,11 @@
"old": "Old", "old": "Old",
"data": "Data", "data": "Data",
"source": "Source", "source": "Source",
"destination": "Destination" "destination": "Destination",
"active": "Active",
"inactive": "Inactive",
"linked": "linked",
"finish": "Finish"
}, },
"objects": { "objects": {
"workspace": "Workspace", "workspace": "Workspace",
@ -179,7 +201,8 @@
"medium": "Medium", "medium": "Medium",
"tall": "Tall", "tall": "Tall",
"extra": "Extra" "extra": "Extra"
} },
"externalDb": "External Database"
}, },
"datatype": { "datatype": {
"ID": "ID", "ID": "ID",
@ -234,7 +257,33 @@
"isNotNull": "is not null" "isNotNull": "is not null"
}, },
"title": { "title": {
"searchMembers": "Search Members",
"noMembersFound": "No members found",
"dateJoined": "Date Joined",
"tokenName": "Token name",
"creator": "Creator",
"updateSelectedRows": "Update Selected Rows",
"noFiltersAdded": "No filters added",
"editCards": "Edit Cards",
"noFieldsFound": "No fields found",
"displayValue": "Display Value",
"hideAll":"Hide all",
"hideSystemFields": "hideSystemFields",
"removeFile": "Remove File",
"hasMany": "Has Many",
"manyToMany": "Many to Many",
"virtualRelation": "Virtual Relation",
"linkMoreRecords": "Link more records",
"downloadFile": "Download File",
"renameTable": "Rename Table",
"renamingTable": "Renaming Table",
"copyAuthToken": "Copy Auth Token",
"copiedAuthToken": "Copied Auth Token",
"showSidebar": "Show Sidebar",
"hideSidebar": "Hide Sidebar",
"creatingTable": "Creating Table",
"erdView": "ERD View", "erdView": "ERD View",
"newBase": "New Base",
"newProj": "New Project", "newProj": "New Project",
"myProject": "My Projects", "myProject": "My Projects",
"formTitle": "Form Title", "formTitle": "Form Title",
@ -245,6 +294,7 @@
"teamAndAuth": "Team & Auth", "teamAndAuth": "Team & Auth",
"rolesUserMgmt": "Roles & Users Management", "rolesUserMgmt": "Roles & Users Management",
"userMgmt": "Users Management", "userMgmt": "Users Management",
"apiTokens": "API Tokens",
"apiTokenMgmt": "API Tokens Management", "apiTokenMgmt": "API Tokens Management",
"rolesMgmt": "Roles Management", "rolesMgmt": "Roles Management",
"projMeta": "Project Metadata", "projMeta": "Project Metadata",
@ -266,10 +316,17 @@
"importFromAirtable": "Import From Airtable", "importFromAirtable": "Import From Airtable",
"generateToken": "Generate Token", "generateToken": "Generate Token",
"APIsAndSupport": "APIs & Support", "APIsAndSupport": "APIs & Support",
"helpCenter": "Help center", "helpCenter": "Help Center",
"noLabels": "No Labels",
"swaggerDocumentation": "Swagger Documentation", "swaggerDocumentation": "Swagger Documentation",
"quickImportFrom": "Quick Import From", "quickImportFrom": "Quick Import From",
"quickImport": "Quick Import", "quickImport": "Quick Import",
"quickImportAirtable": "Quick Import - Airtable",
"quickImportCSV": "Quick Import - CSV",
"quickImportExcel": "Quick Import - Excel",
"quickImportJSON": "Quick Import - JSON",
"jsonEditor": "JSON Editor",
"comingSoon": "Coming Soon",
"advancedSettings": "Advanced Settings", "advancedSettings": "Advanced Settings",
"codeSnippet": "Code Snippet", "codeSnippet": "Code Snippet",
"keyboardShortcut": "Keyboard Shortcuts", "keyboardShortcut": "Keyboard Shortcuts",
@ -282,17 +339,51 @@
"tokens": "Tokens", "tokens": "Tokens",
"userManagement": "User Management", "userManagement": "User Management",
"licence": "Licence", "licence": "Licence",
"defaultView": "Default View" "allowAllMimeTypes": "Allow All Mime Types",
"defaultView": "Default View",
"relations": "Relations",
"switchLanguage": "Switch Language",
"renameFile": "Rename File"
}, },
"labels": { "labels": {
"singularLabel": "Singular Label",
"pluralLabel": "Plural Label",
"optional": "Optional",
"clickToMake": "Click to make",
"visibleForRole": "visible for role:",
"inUI": "in UI Dashboard",
"projectSettings": "Project Settings",
"clickToHide": "Click to hide",
"forRole": "for role",
"searchUsers": "Search Users",
"superAdmin": "Super Admin",
"allTables": "All Tables",
"members": "Members",
"dataSources": "Data Sources",
"searchProjects": "Search Projects", "searchProjects": "Search Projects",
"createdBy": "Created By", "createdBy": "Created By",
"viewingAttachmentsOf": "Viewing Attachments of",
"readOnly": "Readonly",
"dropHere": "Drop here",
"createdOn": "Created On", "createdOn": "Created On",
"notifyVia": "Notify Via", "notifyVia": "Notify Via",
"projName": "Project name", "projName": "Project name",
"profile": "Profile", "profile": "Profile",
"accountDetails": "Account Details",
"controlAppearance": "Control your Appearance.",
"accountEmailID": "Account Email ID",
"backToWorkspace": "Back to Workspace",
"untitledToken": "Untitled token",
"tableName": "Table name", "tableName": "Table name",
"dashboardName": "Dashboard name", "dashboardName": "Dashboard name",
"noViews": "No views",
"createView": "Create a View",
"creatingView": "Creating View",
"duplicateView": "Duplicate view",
"duplicateGridView": "Duplicate Grid View",
"duplicateGalleryView": "Duplicate Gallery View",
"duplicateFormView": "Duplicate Form View",
"duplicateKanbanView": "Duplicate Kanban View",
"viewName": "View name", "viewName": "View name",
"viewLink": "View Link", "viewLink": "View Link",
"columnName": "Column Name", "columnName": "Column Name",
@ -322,9 +413,17 @@
"where": "Where", "where": "Where",
"cache": "Cache", "cache": "Cache",
"chat": "Chat", "chat": "Chat",
"showOrHide": "Show or Hide",
"airtable": "Airtable",
"csv": "CSV",
"csvFile": "CSV File",
"json": "JSON",
"jsonFile": "JSON File",
"excel": "Excel",
"microsoftExcel": "Microsoft Excel",
"email": "E-mail", "email": "E-mail",
"storage": "Storage", "storage": "Storage",
"uiAcl": "UI-ACL", "uiAcl": "UI ACL",
"models": "Models", "models": "Models",
"syncState": "Sync state", "syncState": "Sync state",
"created": "Created", "created": "Created",
@ -357,9 +456,11 @@
"getAnswered": "Get your questions answered", "getAnswered": "Get your questions answered",
"joinDiscord": "Join Discord", "joinDiscord": "Join Discord",
"joinCommunity": "Join NocoDB Community", "joinCommunity": "Join NocoDB Community",
"joinReddit": "Join /r/NocoDB", "joinReddit": "/r/NocoDB",
"followNocodb": "Follow NocoDB" "followNocodb": "Follow NocoDB",
"communityTranslated": "(Community Translated)"
}, },
"twitter": "Twitter",
"docReference": "Document Reference", "docReference": "Document Reference",
"selectUserRole": "Select User Role", "selectUserRole": "Select User Role",
"childTable": "Child table", "childTable": "Child table",
@ -387,6 +488,8 @@
"noData": "No Data", "noData": "No Data",
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"formatJson": "Format JSON",
"firstRowAsHeaders": "Use First Row as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Download allowed",
"weAreHiring": "We are Hiring!", "weAreHiring": "We are Hiring!",
@ -409,9 +512,39 @@
"addRowForm": "Enter record data through a form", "addRowForm": "Enter record data through a form",
"noAccess": "No access", "noAccess": "No access",
"restApis": "Rest APIs", "restApis": "Rest APIs",
"noViews": "No Views" "apis": "APIs",
"includeData": "Include Data",
"includeView": "Include View",
"includeWebhook": "Include Webhook",
"zoomInToViewColumns": "Zoom in to view columns"
}, },
"activity": { "activity": {
"bulkDownload": "Bulk Download",
"attachFile": "Attach File",
"viewAttachment": "View Attachments",
"attachmentDrop": "Click or drop a file into cell",
"addFiles": "Add File(s)",
"hideInUI": "Hide in UI",
"addBase": "Add Base",
"editBase": "Edit Base",
"okEditBase": "Ok & Edit Base",
"showInUI": "Show in UI",
"outOfSync": "Out of sync",
"newSource": "New Source",
"newWebhook": "New Webhook",
"enablePublicAccess": "Enable Public Access",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Project Access",
"allowDownload": "Allow Download",
"surveyMode": "Survey Mode",
"rtlOrientation": "RTL Orientation",
"useTheme": "Use Theme",
"copyLink": "Copy Link",
"copiedLink": "Link Copied",
"copyUrl": "Copy URL",
"moreColors": "More Colors",
"moveProject": "Move Project", "moveProject": "Move Project",
"createProject": "Create Project", "createProject": "Create Project",
"importProject": "Import Project", "importProject": "Import Project",
@ -451,6 +584,7 @@
"groupBy": "Group By", "groupBy": "Group By",
"addSubGroup": "Add subgroup", "addSubGroup": "Add subgroup",
"shareBase": { "shareBase": {
"label": "Share base",
"disable": "Disable shared base", "disable": "Disable shared base",
"enable": "Anyone with the link", "enable": "Anyone with the link",
"link": "Shared base link" "link": "Shared base link"
@ -489,6 +623,9 @@
"insertRow": "Insert new row", "insertRow": "Insert new row",
"duplicateRow": "Duplicate row", "duplicateRow": "Duplicate row",
"deleteRow": "Delete row", "deleteRow": "Delete row",
"deleteRows": "Delete rows",
"predictColumns": "Predict Columns",
"predictFormulas": "Predict Formulas",
"deleteSelectedRow": "Delete selected rows", "deleteSelectedRow": "Delete selected rows",
"importExcel": "Import Excel", "importExcel": "Import Excel",
"importCSV": "Import CSV", "importCSV": "Import CSV",
@ -516,7 +653,6 @@
"createKanban": "Create Kanban View", "createKanban": "Create Kanban View",
"createForm": "Create Form View", "createForm": "Create Form View",
"showSystemFields": "Show system fields", "showSystemFields": "Show system fields",
"copyUrl": "Copy URL",
"openTab": "Open new tab", "openTab": "Open new tab",
"iFrame": "Copy embeddable HTML code", "iFrame": "Copy embeddable HTML code",
"addWebhook": "Add New Webhook", "addWebhook": "Add New Webhook",
@ -588,7 +724,7 @@
"generateNewApiToken": "Generate new API token", "generateNewApiToken": "Generate new API token",
"addRole": "Add new role", "addRole": "Add new role",
"reloadList": "Reload list", "reloadList": "Reload list",
"metaSync": "Sync metadata", "metaSync": "Sync Metadata",
"sqlMigration": "Reload migrations", "sqlMigration": "Reload migrations",
"updateRestart": "Update & Restart", "updateRestart": "Update & Restart",
"cancelReturn": "Cancel and Return", "cancelReturn": "Cancel and Return",
@ -601,6 +737,10 @@
}, },
"placeholder": { "placeholder": {
"projName": "Enter Project Name", "projName": "Enter Project Name",
"selectGroupField": "Select a Grouping Field",
"selectGroupFieldNotFound" : "No Single Select Field can be found. Please create one first.",
"selectGeoField": "Select a GeoData Field",
"selectGeoFieldNotFound": "No GeoData Field can be found. Please create one first.",
"password": { "password": {
"enter": "Enter the password", "enter": "Enter the password",
"current": "Current password", "current": "Current password",
@ -620,16 +760,57 @@
"selectField": "Select field" "selectField": "Select field"
}, },
"msg": { "msg": {
"hm": {
"title": "Has Many Relation",
"icon": "HasManyIcon",
"tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with multiple records from table "
},
"mm": {
"title": "Many to Many Relation",
"icon": "ManytoManyIcon",
"tooltip_desc": "Multiple records from table ",
"tooltip_desc2": " can be linked with multiple records from table "
},
"bt": {
"title": "Belongs to Relation",
"icon": "BelongsToIcon",
"tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a record from table "
},
"oo": {
"title": "One to One Relation",
"icon": "OnetoOneIcon",
"tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table "
},
"noRecordsAreLinkedFromTable": "No records are linked from table",
"noRecordsLinked": "No records linked",
"recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
"selectFieldToSort": "Select Field to Sort",
"thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following",
"idColumnRequired": "ID column is required, you can rename this later if required.",
"length59Required": "The length exceeds the max 59 characters",
"warning": { "warning": {
"dbValid": "Please make sure database you are trying to connect is valid! This operation can cause schema loss!!",
"barcode": { "barcode": {
"renderError": "Barcode error - please check compatibility between input and barcode type" "renderError": "Barcode error - please check compatibility between input and barcode type"
}, },
"nonEditableFields": { "nonEditableFields": {
"computedFieldUnableToClear": "Warning: Computed field - unable to clear text", "computedFieldUnableToClear": "Warning: Computed field - unable to clear text",
"qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed." "qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed."
} },
"duplicateProject": "Are you sure you want to duplicate the project?",
"duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {
"orgCreator": "Creator can create new projects and access any invited project.", "orgCreator": "Creator can create new projects and access any invited project.",
@ -773,9 +954,16 @@
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack.", "deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack.",
"computedFieldEditWarning": "Computed field: contents are read-only. Use column edit menu to reconfigure", "computedFieldEditWarning": "Computed field: contents are read-only. Use column edit menu to reconfigure",
"computedFieldDeleteWarning": "Computed field: contents are read-only. Unable to clear content.", "computedFieldDeleteWarning": "Computed field: contents are read-only. Unable to clear content.",
"noMoreRecords": "No more records" "noMoreRecords": "No more records",
"tokenNameNotEmpty": "Token name should not be empty",
"tokenNameMaxLength": "Token name should not be more than 255 characters"
}, },
"error": { "error": {
"nameRequired": "Name Required",
"nameMinLength": "Name must be at least 2 characters long",
"nameMaxLength": "Name must be at most 60 characters long",
"viewNameRequired": "View name is required",
"viewNameUnique": "View name should be unique",
"searchProject": "Your search for {search} found no results", "searchProject": "Your search for {search} found no results",
"invalidChar": "Invalid character in folder path.", "invalidChar": "Invalid character in folder path.",
"invalidDbCredentials": "Invalid database credentials.", "invalidDbCredentials": "Invalid database credentials.",
@ -783,9 +971,10 @@
"userDoesntHaveSufficientPermission": "User does not exist or have sufficient permission to create schema.", "userDoesntHaveSufficientPermission": "User does not exist or have sufficient permission to create schema.",
"dbConnectionStatus": "Invalid database parameters", "dbConnectionStatus": "Invalid database parameters",
"dbConnectionFailed": "Connection Failure:", "dbConnectionFailed": "Connection Failure:",
"nullFilterExists": "Null filter exists. Please remove them",
"signUpRules": { "signUpRules": {
"emailReqd": "E-mail is required", "emailReqd": "Email is required",
"emailInvalid": "E-mail must be valid", "emailInvalid": "Email must be valid",
"passwdRequired": "Password is required", "passwdRequired": "Password is required",
"passwdLength": "You password must be atleast 8 characters", "passwdLength": "You password must be atleast 8 characters",
"passwdMismatch": "Passwords do not match", "passwdMismatch": "Passwords do not match",
@ -794,7 +983,9 @@
"atLeastOneUppercase": "One Uppercase letter", "atLeastOneUppercase": "One Uppercase letter",
"atLeastOneNumber": "One Number", "atLeastOneNumber": "One Number",
"atLeastOneSpecialChar": "One special character", "atLeastOneSpecialChar": "One special character",
"allowedSpecialCharList": "Allowed special character list" "allowedSpecialCharList": "Allowed special character list",
"invalidEmails": "Invalid emails",
"invalidEmail": "Invalid Email"
}, },
"invalidURL": "Invalid URL", "invalidURL": "Invalid URL",
"invalidEmail": "Invalid Email", "invalidEmail": "Invalid Email",
@ -825,6 +1016,8 @@
"nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _", "nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _",
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate column name",
"uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",
"projectNameCannotStartWithSpace": "Project name cannot start with space", "projectNameCannotStartWithSpace": "Project name cannot start with space",
@ -857,6 +1050,7 @@
"futureRelease": "Coming soon!" "futureRelease": "Coming soon!"
}, },
"success": { "success": {
"licenseKeyUpdated": "License Key Updated",
"columnDuplicated": "Column duplicated successfully", "columnDuplicated": "Column duplicated successfully",
"rowDuplicatedWithoutSavedYet": "Row duplicated (not saved)", "rowDuplicatedWithoutSavedYet": "Row duplicated (not saved)",
"updatedUIACL": "Updated UI ACL for tables successfully", "updatedUIACL": "Updated UI ACL for tables successfully",

4
packages/nc-gui/pages/account/index.vue

@ -50,7 +50,7 @@ const logout = async () => {
> >
<div class="flex flex-row gap-x-2 items-center h-8.5"> <div class="flex flex-row gap-x-2 items-center h-8.5">
<GeneralIcon icon="arrowLeft" class="-mt-0.1" /> <GeneralIcon icon="arrowLeft" class="-mt-0.1" />
<div class="flex text-xs text-gray-800">Back to Workspace</div> <div class="flex text-xs text-gray-800">{{ $t('labels.backToWorkspace') }}</div>
</div> </div>
</div> </div>
@ -151,7 +151,7 @@ const logout = async () => {
<LazyGeneralReleaseInfo /> <LazyGeneralReleaseInfo />
<a-tooltip v-if="!appInfo.ee" placement="bottom" :mouse-enter-delay="1"> <a-tooltip v-if="!appInfo.ee" placement="bottom" :mouse-enter-delay="1">
<template #title> Switch language</template> <template #title>{{ $t('title.switchLanguage') }}</template>
<div class="flex pr-4 items-center"> <div class="flex pr-4 items-center">
<LazyGeneralLanguage class="cursor-pointer text-2xl hover:text-gray-800" /> <LazyGeneralLanguage class="cursor-pointer text-2xl hover:text-gray-800" />

Loading…
Cancel
Save