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

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

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

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

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

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

@ -31,7 +31,7 @@ loadSettings()
<template>
<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">
<a-form-item>
<a-checkbox

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

@ -29,7 +29,7 @@ const showNewTokenModal = ref(false)
const currentLimit = ref(10)
const defaultTokenName = 'Untitled token'
const defaultTokenName = t('labels.untitledToken')
const selectedTokenData = ref<ApiTokenType>({
description: defaultTokenName,
@ -147,9 +147,9 @@ const selectInputOnMount: VNodeRef = (el) =>
const errorMessage = computed(() => {
const tokenLength = selectedTokenData.value.description?.length
if (!tokenLength) {
return 'Token name should not be empty'
return t('msg.info.tokenNameNotEmpty')
} 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="max-w-[810px] mx-auto p-4" data-testid="nc-token-list">
<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
:disabled="showNewTokenModal"
class="!rounded-md"
@ -180,14 +180,14 @@ const handleCancel = () => {
</span>
</NcButton>
</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>
<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 text-start">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 pl-19 text-gray-500 font-medium text-3.5 w-2/9 text-start">Actions</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">{{ $t('title.creator') }}</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">{{ $t('labels.actions') }}</span>
</div>
<main>
<div v-if="showNewTokenModal">
@ -223,7 +223,7 @@ const handleCancel = () => {
<NcDivider />
</div>
<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 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">
<div class="flex justify-end items-center gap-3 pr-5">
<NcTooltip placement="top">
<template #title>show or hide</template>
<template #title>{{ $t('labels.showOrHide') }}</template>
<component
:is="iconMap.eye"
class="nc-toggle-token-visibility hover::cursor-pointer"
@ -255,11 +255,11 @@ const handleCancel = () => {
/>
</NcTooltip>
<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)" />
</NcTooltip>
<NcTooltip placement="top" class="mb-0.5">
<template #title>delete</template>
<template #title>{{ $t('general.delete') }}</template>
<component
:is="iconMap.delete"
data-testid="nc-token-row-action-icon"
@ -283,7 +283,11 @@ const handleCancel = () => {
</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>
<span>
<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>
<div data-testid="nc-super-user-list">
<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">
<a-input-search
v-model:value="searchText"
size="middle"
class="max-w-[300px]"
placeholder="Search Users"
:placeholder="$t('labels.searchUsers')"
@blur="loadUsers"
@keydown.enter="loadUsers"
>
@ -157,7 +157,7 @@ const copyPasswordResetUrl = async (user: User) => {
>
<div class="flex items-center gap-1">
<component :is="iconMap.plus" />
Invite new user
{{ $t('activity.inviteUser') }}
</div>
</a-button>
</div>
@ -186,7 +186,7 @@ const copyPasswordResetUrl = async (user: User) => {
<a-table-column key="roles" :title="$t('objects.role')" data-index="roles">
<template #default="{ record }">
<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
v-else
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-row items-center pl-1.5 pb-1 h-[1.1rem]">
<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>
<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"
>
<MaterialSymbolsAttachFile class="transform group-hover:(text-accent scale-120)" />
Attach File
{{ $t('activity.attachFile') }}
</div>
<div class="flex items-center gap-2">
<div v-if="readOnly" class="text-gray-400">[Readonly]</div>
Viewing Attachments of
<div v-if="readOnly" class="text-gray-400">[{{ $t('labels.readOnly') }}]</div>
{{ $t('labels.viewingAttachmentsOf') }}
<div class="font-semibold underline">{{ column?.title }}</div>
</div>
<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>
</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"
>
<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>
</template>
@ -138,7 +140,7 @@ const handleFileDelete = (i: number) => {
/>
<a-tooltip v-if="!readOnly">
<template #title> Remove File </template>
<template #title> {{ $t('title.removeFile') }} </template>
<component
:is="iconMap.closeCircle"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic && !isLocked)"
@ -148,7 +150,7 @@ const handleFileDelete = (i: number) => {
</a-tooltip>
<a-tooltip placement="bottom">
<template #title> Download File </template>
<template #title> {{ $t('title.downloadFile') }} </template>
<div class="nc-attachment-download group-hover:(opacity-100)">
<component :is="iconMap.download" @click.stop="downloadFile(item)" />
@ -156,7 +158,7 @@ const handleFileDelete = (i: number) => {
</a-tooltip>
<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]">
<component :is="iconMap.edit" @click.stop="renameFile(item, i)" />

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

@ -48,7 +48,7 @@ onMounted(() => {
<template>
<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="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-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"
>
<MaterialSymbolsFileCopyOutline class="text-accent" />
Drop here
{{ $t('labels.dropHere') }}
</general-overlay>
</template>
@ -182,7 +182,7 @@ const rowHeight = inject(RowHeightInj, ref())
<component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />
<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">
<MaterialSymbolsAttachFile
@ -192,7 +192,7 @@ const rowHeight = inject(RowHeightInj, ref())
v-if="!visibleItems.length"
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>
</NcTooltip>
@ -258,7 +258,7 @@ const rowHeight = inject(RowHeightInj, ref())
<component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />
<NcTooltip v-else placement="bottom">
<template #title> View attachments</template>
<template #title> {{ $t('activity.viewAttachment') }}</template>
<component
: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
>
<template #title>
{{
isLeftSidebarOpen
? `${$t('general.hide')} ${$t('objects.sidebar').toLowerCase()}`
: `${$t('general.show')} ${$t('objects.sidebar').toLowerCase()}`
}}
{{ isLeftSidebarOpen ? `${$t('title.hideSidebar')}` : `${$t('title.showSidebar')}` }}
</template>
<NcButton
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">
<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" />
<span class="menu-btn"> Log Out </span>
<span class="menu-btn"> {{ $t('general.logout') }}</span>
</NcMenuItem>
<template v-if="!isMobileMode">
<NcDivider />
<a href="https://docs.nocodb.com" target="_blank" class="!underline-transparent">
<NcMenuItem>
<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>
</a>
</template>
@ -99,19 +99,19 @@ onMounted(() => {
<a href="https://discord.gg/5RgZmkW" target="_blank" class="!underline-transparent">
<NcMenuItem class="social-icon-wrapper">
<GeneralIcon class="social-icon" icon="discord" />
<span class="menu-btn"> Join our Discord </span>
<span class="menu-btn"> {{ $t('labels.community.joinDiscord') }} </span>
</NcMenuItem>
</a>
<a href="https://www.reddit.com/r/NocoDB" target="_blank" class="!underline-transparent">
<NcMenuItem class="social-icon-wrapper">
<GeneralIcon class="social-icon" icon="reddit" />
<span class="menu-btn"> /r/NocoDB </span>
<span class="menu-btn"> {{ $t('labels.community.joinReddit') }} </span>
</NcMenuItem>
</a>
<a href="https://twitter.com/nocodb" target="_blank" class="!underline-transparent">
<NcMenuItem class="social-icon-wrapper group">
<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>
</a>
<template v-if="!appInfo.ee">
@ -120,7 +120,7 @@ onMounted(() => {
<NcMenuItem>
<GeneralIcon icon="translate" class="group-hover:text-black nc-language ml-0.25 menu-icon" />
{{ $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" />
<MaterialSymbolsChevronRightRounded class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" />
@ -139,11 +139,11 @@ onMounted(() => {
<NcMenuItem @click="onCopy">
<GeneralIcon v-if="isAuthTokenCopied" icon="check" class="group-hover:text-black menu-icon" />
<GeneralIcon v-else icon="copy" class="menu-icon" />
<template v-if="isAuthTokenCopied"> Copied Auth Token </template>
<template v-else> Copy Auth Token </template>
<template v-if="isAuthTokenCopied"> {{ $t('title.copiedAuthToken') }} </template>
<template v-else> {{ $t('title.copyAuthToken') }} </template>
</NcMenuItem>
<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>
</template>
</NcMenu>

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

@ -73,7 +73,7 @@ function openQuickImportDialog(type: string) {
@click="openAirtableImportDialog(base.id)"
>
<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
@ -82,7 +82,7 @@ function openQuickImportDialog(type: string) {
@click="openQuickImportDialog('csv')"
>
<GeneralIcon icon="csv" class="w-4 group-hover:text-black" />
CSV file
{{ $t('labels.csvFile') }}
</NcMenuItem>
<NcMenuItem
@ -91,7 +91,7 @@ function openQuickImportDialog(type: string) {
@click="openQuickImportDialog('json')"
>
<GeneralIcon icon="code" class="w-4 group-hover:text-black" />
JSON file
{{ $t('labels.jsonFile') }}
</NcMenuItem>
<NcMenuItem
@ -100,7 +100,7 @@ function openQuickImportDialog(type: string) {
@click="openQuickImportDialog('excel')"
>
<GeneralIcon icon="excel" class="max-w-4 group-hover:text-black" />
Microsoft Excel
{{ $t('labels.microsoftExcel') }}
</NcMenuItem>
</NcSubMenu>
</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 -->
<NcMenuItem key="erd" data-testid="nc-sidebar-project-relations" @click="openProjectErdView(project)">
<GeneralIcon icon="erd" />
Relations
{{ $t('title.relations') }}
</NcMenuItem>
<!-- Swagger: Rest APIs -->
@ -610,7 +610,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
@contextmenu="setMenuContext('base', base)"
>
<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
v-else
@ -626,7 +626,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
{{ base.alias || '' }}
</div>
<a-tooltip class="xs:(hidden)">
<template #title>External DB</template>
<template #title>{{ $t('objects.externalDb') }}</template>
<div>
<GeneralIcon icon="info" class="text-gray-400 -mt-0.5 hover:text-gray-700 mr-1" />
</div>
@ -659,7 +659,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
<!-- ERD View -->
<NcMenuItem key="erd" @click="openErdView(base)">
<GeneralIcon icon="erd" />
Relations
{{ $t('title.relations') }}
</NcMenuItem>
<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,
}"
>
Empty
{{ $t('general.empty') }}
</div>
<div
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>
<NcTooltip class="flex" placement="topLeft" hide-on-click :disabled="!canUserEditEmote">
<template #title>
{{ 'Change icon' }}
{{ $t('general.changeIcon') }}
</template>
<MdiTable

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

@ -36,6 +36,7 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const { isMobileMode } = useGlobal()
const { $e } = useNuxtApp()
const { t } = useI18n()
const isDefaultBase = computed(() => {
const base = project.value?.bases?.find((b) => b.id === table.value.base_id)
@ -86,11 +87,11 @@ function markItem(id: string) {
/** validate view title */
function validate(view: ViewType) {
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)) {
return 'View name should be unique'
return t('msg.error.viewNameDuplicate')
}
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}`">
<NcMenuItem @click.stop="onDblClick">
<GeneralIcon icon="edit" />
<div class="-ml-0.25">Rename</div>
<div class="-ml-0.25">{{ $t('general.rename') }}</div>
</NcMenuItem>
<NcMenuItem @click.stop="onDuplicate">
<GeneralIcon icon="duplicate" class="nc-view-copy-icon" />
Duplicate
{{ $t('general.duplicate') }}
</NcMenuItem>
<NcDivider />
@ -291,7 +291,7 @@ function onRef(el: HTMLElement) {
<template v-if="!vModel.is_default">
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click.stop="onDelete">
<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>
</template>
</NcMenu>

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

@ -55,6 +55,8 @@ const { loadTables } = projectStore
const { tables } = storeToRefs(projectStore)
const { t } = useI18n()
const { activeTable: _activeTable } = storeToRefs(useTablesStore())
const { refreshCommandPalette } = useCommandPalette()
@ -126,7 +128,7 @@ const duplicateTable = async (table: TableType) => {
openTable(newTable!)
} else if (status === JobStatus.FAILED) {
message.error('Failed to duplicate table')
message.error(t('msg.error.failedToDuplicateTable'))
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)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit">
<IcRoundEdit class="pr-0.5" :height="12" />
Edit
{{ $t('general.edit') }}
</div>
</a-button>
<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">
<component :is="iconMap.closeCircle" />
<div class="flex ml-0.5">Reset</div>
<div class="flex ml-0.5">{{ $t('general.reset') }}</div>
</div>
</a-button>
<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">
<component :is="iconMap.plus" />
Install
{{ $t('general.install') }}
</div>
</a-button>
</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 { t } = useI18n()
const { loadProject } = useProjects()
const projectStore = useProject()
@ -135,7 +137,7 @@ const moveBase = async (e: any) => {
id: base.id,
project_id: base.project_id,
})
message.info('Bases are migrated. Please try again.')
message.info(t('info.basesMigrated'))
} else {
await $api.base.update(base.project_id as string, base.id as string, {
id: base.id,
@ -302,7 +304,7 @@ const isEditBaseModalOpen = computed({
>
<div class="flex flex-row items-center w-full gap-x-1">
<component :is="iconMap.plus" />
<div class="flex">New Source</div>
<div class="flex">{{ $t('activity.newSource') }}</div>
</div>
</NcButton>
</div>
@ -314,10 +316,10 @@ const isEditBaseModalOpen = computed({
>
<div class="ds-table-head">
<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-name">Name</div>
<div class="ds-table-col ds-table-type">Type</div>
<div class="ds-table-col ds-table-actions pl-2">Actions</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">{{ $t('general.name') }}</div>
<div class="ds-table-col ds-table-type">{{ $t('general.type') }}</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>
</div>
@ -329,8 +331,8 @@ const isEditBaseModalOpen = computed({
<div class="flex items-center gap-1 cursor-pointer">
<a-tooltip>
<template #title>
<template v-if="sources[0].enabled">Hide in UI</template>
<template v-else>Show in UI</template>
<template v-if="sources[0].enabled">{{ $t('activity.hideInUI') }}</template>
<template v-else>{{ $t('activity.showInUI') }}</template>
</template>
<a-switch
: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="flex items-center gap-1">
<!-- <GeneralBaseLogo :base-type="sources[0].type" /> -->
Default
{{ $t('general.default') }}
</div>
</div>
@ -361,11 +363,11 @@ const isEditBaseModalOpen = computed({
>
<div class="flex items-center gap-2 text-gray-600">
<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" />
</a-tooltip>
<GeneralIcon v-else icon="sync" class="group-hover:text-accent" />
Sync Metadata
{{ $t('activity.metaSync') }}
</div>
</a-button>
<a-button
@ -375,7 +377,7 @@ const isEditBaseModalOpen = computed({
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="erd" class="group-hover:text-accent" />
Relations
{{ $t('title.relations') }}
</div>
</a-button>
<a-button
@ -385,7 +387,7 @@ const isEditBaseModalOpen = computed({
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="acl" class="group-hover:text-accent" />
UI ACL
{{ $t('labels.uiAcl') }}
</div>
</a-button>
<a-button
@ -395,7 +397,7 @@ const isEditBaseModalOpen = computed({
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="book" class="group-hover:text-accent" />
Audit
{{ $t('title.audit') }}
</div>
</a-button>
</div>
@ -418,8 +420,8 @@ const isEditBaseModalOpen = computed({
<div class="flex items-center gap-1 cursor-pointer">
<a-tooltip>
<template #title>
<template v-if="base.enabled">Hide in UI</template>
<template v-else>Show in UI</template>
<template v-if="base.enabled">{{ $t('activity.hideInUI') }}</template>
<template v-else>{{ $t('activity.showInUI') }}</template>
</template>
<a-switch :checked="base.enabled ? true : false" @change="toggleBase(base, $event)" />
</a-tooltip>
@ -429,7 +431,7 @@ const isEditBaseModalOpen = computed({
<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-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>
@ -450,7 +452,7 @@ const isEditBaseModalOpen = computed({
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="erd" class="group-hover:text-accent" />
Relations
{{ $t('title.relations') }}
</div>
</a-button>
<a-button
@ -460,7 +462,7 @@ const isEditBaseModalOpen = computed({
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="acl" class="group-hover:text-accent" />
UI ACL
{{ $t('labels.uiAcl') }}
</div>
</a-button>
<a-button
@ -471,11 +473,11 @@ const isEditBaseModalOpen = computed({
>
<div class="flex items-center gap-2 text-gray-600">
<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" />
</a-tooltip>
<GeneralIcon v-else icon="sync" class="group-hover:text-accent" />
Sync Metadata
{{ $t('tooltip.metaSync') }}
</div>
</a-button>
</div>
@ -541,7 +543,7 @@ const isEditBaseModalOpen = computed({
<LazyDashboardSettingsBaseAudit :base-id="activeBaseId" @close="isBaseAuditModalOpen = false" />
</div>
</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>
<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" />

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

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

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

@ -43,6 +43,8 @@ provide(ProjectIdInj, projectId)
const { $e } = useNuxtApp()
const { t } = useI18n()
const dataSourcesReload = ref(false)
const dataSourcesAwakened = ref(false)
@ -107,12 +109,12 @@ const tabsInfo: TabGroup = {
// },
projectSettings: {
// Project Settings
title: 'Project Settings',
title: t('labels.projectSettings'),
icon: iconMap.settings,
subTabs: {
misc: {
// Misc
title: 'Misc',
title: t('general.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 = [
{
title: tableHeaderRenderer('Table name'),
title: tableHeaderRenderer(t('labels.tableName')),
name: 'table_name',
},
{
title: tableHeaderRenderer('View name'),
title: tableHeaderRenderer(t('labels.viewName')),
name: 'view_name',
},
{
title: tableHeaderRenderer('Editor'),
title: tableHeaderRenderer(t('objects.roleType.editor')),
name: 'editor',
width: 120,
},
{
title: tableHeaderRenderer('Commenter'),
title: tableHeaderRenderer(t('objects.roleType.commenter')),
name: 'commenter',
width: 120,
},
{
title: tableHeaderRenderer('Viewer'),
title: tableHeaderRenderer(t('objects.roleType.viewer')),
name: 'viewer',
width: 120,
},
@ -125,7 +125,7 @@ const columns = [
<div class="flex flex-col w-[900px]">
<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">
<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>
<component :is="iconMap.search" />
</template>
@ -134,14 +134,14 @@ const columns = [
<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">
<component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
Reload
{{ $t('general.reload') }}
</div>
</a-button>
<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">
<component :is="iconMap.save" />
<div class="flex">Save</div>
<div class="flex">{{ $t('general.save') }}</div>
</div>
</NcButton>
</div>
@ -196,9 +196,13 @@ const columns = [
<a-tooltip>
<template #title>
<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>
<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="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="submit" type="primary" @click="createBase">Ok & Add Base</NcButton>
<NcButton key="submit" type="primary" @click="createBase"> {{ $t('activity.addBase') }}</NcButton>
</div>
</div>
</GeneralModal>
<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">
New Base
{{ $t('title.newBase') }}
<DashboardSettingsDataSourcesInfo />
<span class="flex-grow"></span>
</h1>
@ -635,7 +635,7 @@ watch(
<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">
<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>
</GeneralModal>

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

@ -338,7 +338,7 @@ onMounted(async () => {
<template>
<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
ref="form"
@ -562,7 +562,7 @@ onMounted(async () => {
</a-form-item>
<div class="w-full flex items-center mt-2 text-[#e65100]">
<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>
</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="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="submit" type="primary" @click="editBase">Ok & Edit Base</NcButton>
<NcButton key="submit" type="primary" @click="editBase">{{ $t('activity.okEditBase') }}</NcButton>
</div>
</div>
</GeneralModal>

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

@ -270,7 +270,7 @@ onMounted(async () => {
>
<div class="px-5">
<!-- 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 class="mb-4">
@ -353,7 +353,7 @@ onMounted(async () => {
<!-- Import Formula Columns -->
<a-tooltip placement="top">
<template #title>
<span>Coming Soon!</span>
<span>{{ $t('title.comingSoon') }}</span>
</template>
<a-checkbox v-model:checked="syncSource.details.options.syncFormula" disabled>
{{ $t('labels.importFormulaColumns') }}
@ -420,7 +420,9 @@ onMounted(async () => {
<a-button v-if="showGoToDashboardButton" class="mt-4" size="large" @click="dialogShow = false">
{{ $t('labels.goToDashboard') }}
</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>

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

@ -81,16 +81,16 @@ const isEaster = ref(false)
{{ $t('general.duplicate') }} {{ $t('objects.project') }}
</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>
<a-divider class="!m-0 !p-0 !my-2" />
<div class="text-xs p-2">
<a-checkbox v-model:checked="options.includeData">Include data</a-checkbox>
<a-checkbox v-model:checked="options.includeViews">Include views</a-checkbox>
<a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">Include webhooks</a-checkbox>
<a-checkbox v-model:checked="options.includeData">{{ $t('labels.includeData') }}</a-checkbox>
<a-checkbox v-model:checked="options.includeViews">{{ $t('labels.includeView') }}</a-checkbox>
<a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">{{ $t('labels.includeWebhook') }}</a-checkbox>
</div>
</div>
<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(() => {
if (IsImportTypeExcel.value) {
return {
header: `${t('title.quickImport')} - EXCEL`,
header: `${t('title.quickImportExcel')}`,
uploadHint: t('msg.info.excelSupport'),
urlInputLabel: t('msg.info.excelURL'),
loadUrlDirective: ['c:quick-import:excel:load-url'],
@ -127,7 +127,7 @@ const importMeta = computed(() => {
}
} else if (isImportTypeCsv.value) {
return {
header: `${t('title.quickImport')} - CSV`,
header: `${t('title.quickImportCSV')}`,
uploadHint: '',
urlInputLabel: t('msg.info.csvURL'),
loadUrlDirective: ['c:quick-import:csv:load-url'],
@ -135,7 +135,7 @@ const importMeta = computed(() => {
}
} else if (isImportTypeJson.value) {
return {
header: `${t('title.quickImport')} - JSON`,
header: `${t('title.quickImportJSON')}`,
uploadHint: '',
acceptTypes: '.json',
}
@ -598,7 +598,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
<template #tab>
<span class="flex items-center gap-2">
<component :is="iconMap.json" />
JSON Editor
{{ $t('title.jsonEditor') }}
</span>
</template>
@ -611,7 +611,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
<template #tab>
<span class="flex items-center gap-2">
<component :is="iconMap.link" />
URL
{{ $t('datatype.URL') }}
</span>
</template>
@ -645,7 +645,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
<a-form-item v-if="isImportTypeCsv || IsImportTypeExcel" class="!my-2">
<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-form-item>
@ -665,7 +665,9 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
</div>
</a-spin>
<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>
@ -676,7 +678,7 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
:disabled="disableFormatJsonButton"
@click="formatJson"
>
Format JSON
{{ $t('labels.formatJson') }}
</a-button>
<a-button

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

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

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

@ -70,16 +70,16 @@ const isEaster = ref(false)
{{ $t('general.duplicate') }} {{ $t('objects.table') }}
</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>
<a-divider class="!m-0 !p-0 !my-2" />
<div class="text-xs p-2">
<a-checkbox v-model:checked="options.includeData">Include data</a-checkbox>
<a-checkbox v-model:checked="options.includeViews">Include views</a-checkbox>
<a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">Include webhooks</a-checkbox>
<a-checkbox v-model:checked="options.includeData">{{ $t('labels.includeData') }}a</a-checkbox>
<a-checkbox v-model:checked="options.includeViews">{{ $t('labels.includeView') }}</a-checkbox>
<a-checkbox v-show="isEaster" v-model:checked="options.includeHooks">{{ $t('labels.includeWebhook') }}</a-checkbox>
</div>
</div>
<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"
@click="() => renameTable()"
>
Rename Table
<template #loading> Renaming Table </template>
{{ $t('title.renameTable') }}
<template #loading> {{ $t('title.renamingTable') }}</template>
</NcButton>
</div>
</div>

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

@ -238,8 +238,21 @@ onMounted(async () => {
<template #header>
<div class="flex flex-row items-center gap-x-1.5">
<GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" />
{{ $t(`general.${selectedViewId ? 'duplicate' : 'create'}`) }} <span class="capitalize">{{ typeAlias }}</span>
{{ $t('objects.view') }}
<template v-if="form.type === ViewTypes.GRID">
{{ $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>
</template>
<div class="mt-2">
@ -266,8 +279,8 @@ onMounted(async () => {
:disabled="groupingFieldColumnId || isMetaLoading"
:loading="isMetaLoading"
:options="viewSelectFieldOptions"
placeholder="Select a Grouping Field"
not-found-content="No Single Select Field can be found. Please create one first."
:placeholder="$t('placeholder.selectGroupField')"
:not-found-content="$t('placeholder.selectGroupFieldNotFound')"
/>
</a-form-item>
<a-form-item
@ -282,8 +295,8 @@ onMounted(async () => {
:options="viewSelectFieldOptions"
:disabled="groupingFieldColumnId || isMetaLoading"
:loading="isMetaLoading"
placeholder="Select a GeoData Field"
not-found-content="No GeoData Field can be found. Please create one first."
:placeholder="$t('placeholder.selectGeoField')"
:not-found-content="$t('placeholder.selectGeoFieldNotFound')"
/>
</a-form-item>
</a-form>
@ -294,8 +307,8 @@ onMounted(async () => {
</NcButton>
<NcButton type="primary" :loading="isViewCreating" @click="onSubmit">
Create View
<template #loading> Creating View </template>
{{ $t('labels.createView') }}
<template #loading> {{ $t('labels.creatingView') }}</template>
</NcButton>
</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 { t } = useI18n()
const useForm = Form.useForm
const validators = computed(() => {
return {
@ -11,12 +13,16 @@ const validators = computed(() => {
{
validator: (rule: any, value: string, callback: (errMsg?: string) => void) => {
if (!value || value.length === 0) {
callback('Email is required')
callback(t('msg.error.signUpRules.emailReqd'))
return
}
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !validateEmail(e))
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 {
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 w-full p-3 border-1 border-gray-100 rounded-md">
<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" />
</div>
<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" />
<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
:loading="isRoleToggleLoading"
: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 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" :style="{ fontWeight: 500 }">Enable public viewing</div>
<div class="flex" :style="{ fontWeight: 500 }">{{ $t('activity.enabledPublicViewing') }}</div>
<a-switch
data-testid="share-view-toggle"
:checked="isPublicShared"
@ -284,7 +284,7 @@ const isPublicShareDisabled = computed(() => {
</div>
<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 text-black">Restrict access with password</div>
<div class="flex text-black">{{ $t('activity.restrictAccessWithPassword') }}</div>
<a-switch
data-testid="share-password-toggle"
:checked="passwordProtected"
@ -317,7 +317,7 @@ const isPublicShareDisabled = computed(() => {
"
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
v-model:checked="allowCSVDownload"
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">
<!-- 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">
<!-- todo i18n -->
</a-switch>
</div>
<div v-if="activeView?.type === ViewTypes.FORM" class="flex flex-row justify-between">
<!-- 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">
<!-- todo i18n -->
</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">
<!-- todo: i18n -->
<div class="flex flex-row justify-between">
<div class="text-black">Use Theme</div>
<div class="text-black">{{ $t('activity.useTheme') }}</div>
<a-switch
data-testid="share-theme-toggle"
:checked="viewTheme"

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

@ -133,16 +133,16 @@ watch(showShareModal, (val) => {
</div>
<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 text-base font-medium">Share</div>
<div class="flex text-base font-medium">{{ $t('activity.share') }}</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">
<component
:is="viewIcons[view?.type]?.icon"
class="nc-view-icon group-hover"
:style="{ color: viewIcons[view?.type]?.color }"
/>
<div>Share View</div>
<div>{{ $t('activity.shareView') }}</div>
<div
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' }"
@ -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">
<GeneralProjectIcon :type="project.type" class="nc-view-icon group-hover" />
<div>Share Base</div>
<div>{{ $t('activity.shareBase.label') }}</div>
<div
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' }"
@ -167,13 +167,15 @@ watch(showShareModal, (val) => {
<LazyDlgShareAndCollaborateShareBase />
</div>
<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
data-testid="docs-share-manage-access"
type="secondary"
:loading="isOpeningManageAccess"
@click="openManageAccess"
>Manage project access</NcButton
>{{ $t('activity.manageProjectAccess') }}</NcButton
>
<!-- <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)"
@click="zoomIn"
>
<!-- todo: i18n -->
Zoom in to view columns
{{ $t('labels.zoomInToViewColumns') }}
</Panel>
</Transition>

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

@ -63,7 +63,7 @@ watch(picked, (n, _o) => {
@click="isPickerOn = !isPickerOn"
>
<GeneralTooltip>
<template #title>More colors</template>
<template #title>{{ $t('activity.moreColors') }}</template>
<GeneralIcon class="mt-1.5" :icon="isPickerOn ? 'minus' : 'plus'" />
</GeneralTooltip>
</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" />
<MdiContentCopy v-else class="h-3.5" />
<div class="flex text-xs" :style="{ fontWeight: 500 }">
<template v-if="isCopied.link"> Link Copied </template>
<template v-else> Copy Link </template>
<template v-if="isCopied.link"> {{ $t('activity.copiedLink') }} </template>
<template v-else> {{ $t('activity.copyUrl') }} </template>
</div>
</div>
</div>

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

@ -47,7 +47,7 @@ onKeyStroke('Enter', () => {
</div>
<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>
<slot name="entity-preview"></slot>

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

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

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

@ -160,7 +160,7 @@ onMounted(async () => {
</div>
<template v-else>
<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>
<PhMagnifyingGlassBold class="!h-3.5 text-gray-500" />
</template>
@ -174,13 +174,13 @@ onMounted(async () => {
v-else-if="!collaborators?.length"
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 v-else class="nc-collaborators-list nc-scrollbar-md">
<div class="nc-collaborators-list-header">
<div class="flex w-3/5">Users</div>
<div class="flex w-2/5">Date Joined</div>
<div class="flex w-1/5">Access</div>
<div class="flex w-3/5">{{ $t('objects.users') }}</div>
<div class="flex w-2/5">{{ $t('title.dateJoined') }}</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>

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

@ -69,25 +69,25 @@ const onClick = (type: 'airtable' | 'csv' | 'excel' | 'json') => {
<template>
<GeneralModal v-model:visible="visible" width="35rem">
<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="nc-project-view-import-sub-btn" @click="onClick('airtable')">
<GeneralIcon icon="airtable" />
<div class="label">Airtable</div>
<div class="label">{{ $t('labels.airtable') }}</div>
</div>
<div class="nc-project-view-import-sub-btn" @click="onClick('csv')">
<GeneralIcon icon="csv" />
<div class="label">CSV</div>
<div class="label">{{ $t('labels.csv') }}</div>
</div>
</div>
<div class="row">
<div class="nc-project-view-import-sub-btn" @click="onClick('excel')">
<GeneralIcon icon="excelColored" />
<div class="label">Excel</div>
<div class="label">{{ $t('labels.excel') }}</div>
</div>
<div class="nc-project-view-import-sub-btn" @click="onClick('json')">
<GeneralIcon icon="code" />
<div class="label">JSON</div>
<div class="label">{{ $t('labels.json') }}</div>
</div>
</div>
</div>

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

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

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

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

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

@ -16,7 +16,7 @@ const { allowCSVDownload } = useSharedView()
<template>
<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">
<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>
<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"
>
<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">
<component :is="opt.icon" class="text-gray-700 mx-1" />
{{ 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>
</a-select-option>
</a-select>
@ -297,7 +297,7 @@ if (props.fromTableExplorer) {
class="ml-1 mb-1"
>
<span class="text-[10px] text-gray-600">
{{ `Accept only valid ${formState.uidt}` }}
{{ `${$t('msg.acceptOnlyValid')} ${formState.uidt}` }}
</span>
</a-checkbox>
<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 { t } = useI18n()
const vModel = useVModel(props, 'value', emit)
const { validateInfos, setAdditionalValidations } = useColumnCreateStoreOrThrow()
@ -17,7 +19,7 @@ setAdditionalValidations({
validator: (_, value: string) => {
return new Promise((resolve, reject) => {
if (value?.length > 59) {
return reject(new Error('The length exceeds the max 59 characters'))
return reject(t('msg.length59Required'))
}
resolve(true)
})
@ -29,7 +31,7 @@ setAdditionalValidations({
validator: (_, value: string) => {
return new Promise((resolve, reject) => {
if (value?.length > 59) {
return reject(new Error('The length exceeds the max 59 characters'))
return reject(t('msg.length59Required'))
}
resolve(true)
})
@ -49,14 +51,14 @@ vModel.value.meta = {
<template>
<a-row class="my-2" gutter="8">
<a-col :span="12">
<a-form-item v-bind="validateInfos['meta.singular']" label="Singular Label">
<a-input v-model:value="vModel.meta.singular" placeholder="Link" class="!w-full nc-link-singular" />
<a-form-item v-bind="validateInfos['meta.singular']" :label="$t('labels.singularLabel')">
<a-input v-model:value="vModel.meta.singular" :placeholder="$t('general.link')" class="!w-full nc-link-singular" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item v-bind="validateInfos['meta.plural']" label="Plural Label">
<a-input v-model:value="vModel.meta.plural" placeholder="Links" class="!w-full nc-link-plural" />
<a-form-item v-bind="validateInfos['meta.plural']" :label="$t('labels.pluralLabel')">
<a-input v-model:value="vModel.meta.plural" :placeholder="$t('general.links')" class="!w-full nc-link-plural" />
</a-form-item>
</a-col>
</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">
<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 value="hm">Has Many</a-radio>
<a-radio value="mm">Many To Many</a-radio>
<a-radio value="hm">{{ $t('title.hasMany') }}</a-radio>
<a-radio value="mm">{{ $t('title.manyToMany') }}</a-radio>
</a-radio-group>
</a-form-item>
@ -132,7 +132,9 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
<div class="flex flex-row">
<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>
</div>
</template>

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

@ -156,11 +156,11 @@ watch(
<GeneralIcon icon="arrowLeft" />
</NcButton>
<div class="flex flex-row ml-2">
<NuxtLink class="link" :to="webhookMainUrl">Webhooks</NuxtLink>
<NuxtLink class="link" :to="webhookMainUrl">{{ $t('objects.webhooks') }}</NuxtLink>
</div>
<template v-if="selectedHook || isDraftMode">
<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>
<div
v-if="selectedHook"
@ -170,7 +170,7 @@ watch(
'bg-gray-100 text-gray-500': !selectedHook.active,
}"
>
{{ selectedHook.active ? 'Active' : 'Inactive' }}
{{ selectedHook.active ? $t('general.active') : $t('general.inactive') }}
</div>
</div>
<NcButton
@ -182,7 +182,7 @@ watch(
@click="createWebhook()"
>
<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" />
</div>
</NcButton>
@ -190,24 +190,24 @@ watch(
<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">
<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">Create web-hooks to power you automations,</div>
<div class="flex">Get notified as soon as there are changes in your data</div>
<div class="flex">{{ $t('msg.createWebhookMsg2') }}</div>
<div class="flex">{{ $t('msg.createWebhookMsg3') }}</div>
</div>
<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">
<span class="ml-1">New Webhook</span>
<span class="ml-1">{{ $t('activity.newWebhook') }}</span>
<GeneralIcon icon="plus" />
</div>
</NcButton>
</div>
<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="nc-view-sidebar-webhook-item-toggle header">Activate</div>
<div class="nc-view-sidebar-webhook-item-title header">Title</div>
<div class="nc-view-sidebar-webhook-item-event header">Event</div>
<div class="nc-view-sidebar-webhook-item-action header">Action</div>
<div class="nc-view-sidebar-webhook-item-toggle header">{{ $t('general.activate') }}</div>
<div class="nc-view-sidebar-webhook-item-title header">{{ $t('general.title') }}</div>
<div class="nc-view-sidebar-webhook-item-event header">{{ $t('general.event') }}</div>
<div class="nc-view-sidebar-webhook-item-action header">{{ $t('general.action') }}</div>
</div>
<div v-for="hook in hooks" :key="hook.id" class="nc-view-sidebar-webhook-item">
<div
@ -255,13 +255,13 @@ watch(
:centered="false"
@click="copyWebhook(hook)"
>
<template #loading> Duplicating </template>
<div class="flex items-center gap-x-1"><GeneralIcon icon="copy" /> Duplicate</div>
<template #loading> {{ $t('general.duplicating') }} </template>
<div class="flex items-center gap-x-1"><GeneralIcon icon="copy" /> {{ $t('general.duplicate') }}</div>
</NcButton>
<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">
<GeneralIcon icon="delete" />
Delete
{{ $t('general.delete') }}
</div>
</NcButton>
</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 gap-2">
<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 class="flex mt-1">
<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>
<div class="flex flex-row items-center py-3">
<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" />
</div>
</template>
@ -1276,14 +1276,14 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<div class="flex flex-row items-center py-3" @click="predictNextColumn">
<MdiReload v-if="predictingNextColumn" class="animate-infinite animate-spin" />
<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>
</NcMenuItem>
<a-sub-menu v-if="predictedNextFormulas" key="predict-formula">
<template #title>
<div class="flex flex-row items-center py-3">
<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" />
</div>
</template>
@ -1306,7 +1306,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<div class="flex flex-row items-center py-3" @click="predictNextFormulas">
<MdiReload v-if="predictingNextFormulas" class="animate-infinite animate-spin" />
<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>
</NcMenuItem>
</NcMenu>
@ -1513,8 +1513,8 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
@click="emits('bulkUpdateDlg')"
>
<component :is="iconMap.edit" />
<!-- TODO i18n -->
Update Selected Rows
{{ $t('title.updateSelectedRows') }}
</NcMenuItem>
<NcMenuItem
@ -1575,7 +1575,8 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
@click="clearSelectedRangeOfCells()"
>
<GeneralIcon icon="closeBox" class="text-gray-500" />
Clear
{{ $t('general.clear') }}
</NcMenuItem>
<NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected))" />
<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">
<GeneralIcon icon="delete" class="text-gray-500 text-error" />
<!-- Delete Rows -->
Delete rows
{{ $t('activity.deleteRows') }}
</NcMenuItem>
</div>
</NcMenu>

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

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

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

@ -89,10 +89,10 @@ const onArrowUp = () => {
@keydown.enter.prevent="onClick(options[activeFieldIndex])"
>
<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 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
v-for="(option, index) in options"
:key="index"

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

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

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

@ -135,7 +135,7 @@ watch(columns, () => {
v-model:value="search.query"
size="small"
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"
data-testid="search-data-input"
@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" />
<div
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)]"
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 />
<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
class="tab"
@ -34,7 +34,7 @@ const { onViewsTabChange } = useViewsStore()
fontWeight: 500,
}"
/>
<div class="tab-title nc-tab">Details</div>
<div class="tab-title nc-tab">{{ $t('general.details') }}</div>
</div>
</div>
</template>

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

@ -29,6 +29,8 @@ const childListDlg = ref(false)
const { isUIAllowed } = useRoles()
const { t } = useI18n()
const { state, isNew } = useSmartsheetRowStoreOrThrow()
const { relatedTableMeta, loadRelatedTableMeta, relatedTableDisplayValueProp } = useProvideLTARStore(
@ -47,18 +49,18 @@ loadRelatedTableMeta()
const textVal = computed(() => {
if (isForm?.value) {
return state.value?.[colTitle.value]?.length
? `${+state.value?.[colTitle.value]?.length} records Linked`
: 'No records linked'
? `${+state.value?.[colTitle.value]?.length} ${t('msg.recordsLinked')}`
: t('msg.noRecordsLinked')
}
const parsedValue = +value?.value || 0
if (!parsedValue) {
return 'No records linked'
return t('msg.noRecordsLinked')
} else if (parsedValue === 1) {
return `1 ${column.value?.meta?.singular || 'Link'}`
return `1 ${column.value?.meta?.singular || t('general.link')}`
} 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
}>()
const { t } = useI18n()
const relationMeta = computed(() => {
if (relation === 'hm') {
return {
title: 'Has Many Relation',
title: t('msg.hm.title'),
icon: HasManyIcon,
tooltip_desc: 'A single record from table ',
tooltip_desc2: ' can be linked with a multiple records from table ',
tooltip_desc: t('msg.hm.tooltip_desc'),
tooltip_desc2: t('msg.hm.tooltip_desc2'),
}
} else if (relation === 'mm') {
return {
title: 'Many to Many Relation',
title: t('msg.mm.title'),
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') {
return {
title: 'Belongs to Relation',
title: t('msg.bt.title'),
icon: BelongsToIcon,
tooltip_desc: 'A single record from table ',
tooltip_desc2: ' can be linked with a record from table ',
tooltip_desc: t('msg.bt.tooltip_desc'),
tooltip_desc2: t('msg.bt.tooltip_desc2'),
}
} else {
return {
title: 'One to One Relation',
title: t('msg.oo.title'),
icon: OnetoOneIcon,
tooltip_desc: 'A single record from table ',
tooltip_desc2: ' can be linked with a single record from table ',
tooltip_desc: t('msg.oo.tooltip_desc'),
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" />
<p>
No records are linked from table
{{ $t('msg.noRecordsAreLinkedFromTable') }}
{{ relatedTableMeta?.title }}
</p>
<NcButton
@ -250,7 +250,7 @@ onKeyStroke('Escape', () => {
data-testid="nc-child-list-button-link-to"
@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>
</div>
@ -258,10 +258,13 @@ onKeyStroke('Escape', () => {
<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">
{{ childrenListCount || 0 }} records {{ childrenListCount !== 0 ? 'are' : '' }} linked
{{ childrenListCount || 0 }} {{ $t('objects.records') }} {{ childrenListCount !== 0 ? $t('general.are') : '' }}
{{ $t('general.linked') }}
</div>
<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 class="flex absolute items-center py-2 justify-center w-full">
<a-pagination
@ -277,13 +280,13 @@ onKeyStroke('Escape', () => {
/>
</div>
<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
v-if="!readonly && childrenListCount > 0"
data-testid="nc-child-list-button-link-to"
@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>
</div>
</div>

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

@ -176,7 +176,7 @@ onKeyStroke('Escape', () => {
<a-input
ref="filterQueryRef"
v-model:value="childrenExcludedListPagination.query"
:placeholder="`Search in ${relatedTableMeta?.title}`"
:placeholder="`${$t('general.searchIn')} ${relatedTableMeta?.title}`"
class="w-full !rounded-md nc-excluded-search"
size="small"
:bordered="false"
@ -193,7 +193,7 @@ onKeyStroke('Escape', () => {
<!-- Add new record -->
<NcButton
v-if="!isPublic"
type="ghost"
type="secondary"
size="xl"
class="!text-brand-500"
@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>
</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">
<InboxIcon class="w-16 h-16 mx-auto" />
<p>
There are no records in table
{{ $t('msg.thereAreNoRecordsInTable') }}
{{ relatedTableMeta?.title }}
</p>
</div>
@ -279,8 +279,8 @@ onKeyStroke('Escape', () => {
<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">
{{ relation === 'bt' ? (row.row[relatedTableMeta?.title] ? '1' : 0) : childrenListCount ?? 'No' }} records
{{ childrenListCount !== 0 ? 'are' : '' }} linked
{{ relation === 'bt' ? (row.row[relatedTableMeta?.title] ? '1' : 0) : childrenListCount ?? 'No' }}
{{ $t('objects.records') }} {{ childrenListCount !== 0 ? 'are' : '' }} {{ $t('general.linked') }}
</div>
<div class="flex absolute items-center py-2 justify-center w-full">
<a-pagination
@ -295,7 +295,7 @@ onKeyStroke('Escape', () => {
show-less-items
/>
</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>
<Suspense>
<LazySmartsheetExpandedForm

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

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

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

@ -46,19 +46,31 @@
"yes": "Yes",
"no": "No",
"ok": "OK",
"back": "Back",
"and": "And",
"or": "Or",
"add": "Add",
"edit": "Edit",
"link": "Link",
"links": "Links",
"remove": "Remove",
"import": "Import",
"logout": "Log Out",
"empty": "Empty",
"changeIcon": "Change Icon",
"save": "Save",
"abort": "Abort",
"saving": "Saving",
"cancel": "Cancel",
"clear": "Clear",
"submit": "Submit",
"create": "Create",
"details": "Details",
"skip": "Skip",
"duplicate": "Duplicate",
"duplicating": "Duplicating",
"activate": "Activate",
"action": "Action",
"insert": "Insert",
"delete": "Delete",
"deleting": "Deleting",
@ -68,7 +80,10 @@
"reset": "Reset",
"install": "Install",
"show": "Show",
"access": "Access",
"visibility": "Visibility",
"hide": "Hide",
"deprecated": "Deprecated",
"showAll": "Show all",
"hideAll": "Hide all",
"showMore": "Show more",
@ -90,6 +105,7 @@
"upload": "Upload",
"download": "Download",
"default": "Default",
"base": "Base",
"more": "More",
"less": "Less",
"event": "Event",
@ -97,12 +113,14 @@
"after": "After",
"before": "Before",
"search": "Search",
"searchIn": "Search In",
"notification": "Notification",
"reference": "Reference",
"function": "Function",
"confirm": "Confirm",
"generate": "Generate",
"copy": "Copy",
"are": "are",
"misc": "Miscellaneous",
"lock": "Lock",
"unlock": "Unlock",
@ -128,7 +146,11 @@
"old": "Old",
"data": "Data",
"source": "Source",
"destination": "Destination"
"destination": "Destination",
"active": "Active",
"inactive": "Inactive",
"linked": "linked",
"finish": "Finish"
},
"objects": {
"workspace": "Workspace",
@ -179,7 +201,8 @@
"medium": "Medium",
"tall": "Tall",
"extra": "Extra"
}
},
"externalDb": "External Database"
},
"datatype": {
"ID": "ID",
@ -234,7 +257,33 @@
"isNotNull": "is not null"
},
"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",
"newBase": "New Base",
"newProj": "New Project",
"myProject": "My Projects",
"formTitle": "Form Title",
@ -245,6 +294,7 @@
"teamAndAuth": "Team & Auth",
"rolesUserMgmt": "Roles & Users Management",
"userMgmt": "Users Management",
"apiTokens": "API Tokens",
"apiTokenMgmt": "API Tokens Management",
"rolesMgmt": "Roles Management",
"projMeta": "Project Metadata",
@ -266,10 +316,17 @@
"importFromAirtable": "Import From Airtable",
"generateToken": "Generate Token",
"APIsAndSupport": "APIs & Support",
"helpCenter": "Help center",
"helpCenter": "Help Center",
"noLabels": "No Labels",
"swaggerDocumentation": "Swagger Documentation",
"quickImportFrom": "Quick Import From",
"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",
"codeSnippet": "Code Snippet",
"keyboardShortcut": "Keyboard Shortcuts",
@ -282,17 +339,51 @@
"tokens": "Tokens",
"userManagement": "User Management",
"licence": "Licence",
"defaultView": "Default View"
"allowAllMimeTypes": "Allow All Mime Types",
"defaultView": "Default View",
"relations": "Relations",
"switchLanguage": "Switch Language",
"renameFile": "Rename File"
},
"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",
"createdBy": "Created By",
"viewingAttachmentsOf": "Viewing Attachments of",
"readOnly": "Readonly",
"dropHere": "Drop here",
"createdOn": "Created On",
"notifyVia": "Notify Via",
"projName": "Project name",
"profile": "Profile",
"accountDetails": "Account Details",
"controlAppearance": "Control your Appearance.",
"accountEmailID": "Account Email ID",
"backToWorkspace": "Back to Workspace",
"untitledToken": "Untitled token",
"tableName": "Table 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",
"viewLink": "View Link",
"columnName": "Column Name",
@ -322,9 +413,17 @@
"where": "Where",
"cache": "Cache",
"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",
"storage": "Storage",
"uiAcl": "UI-ACL",
"uiAcl": "UI ACL",
"models": "Models",
"syncState": "Sync state",
"created": "Created",
@ -357,9 +456,11 @@
"getAnswered": "Get your questions answered",
"joinDiscord": "Join Discord",
"joinCommunity": "Join NocoDB Community",
"joinReddit": "Join /r/NocoDB",
"followNocodb": "Follow NocoDB"
"joinReddit": "/r/NocoDB",
"followNocodb": "Follow NocoDB",
"communityTranslated": "(Community Translated)"
},
"twitter": "Twitter",
"docReference": "Document Reference",
"selectUserRole": "Select User Role",
"childTable": "Child table",
@ -387,6 +488,8 @@
"noData": "No Data",
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"formatJson": "Format JSON",
"firstRowAsHeaders": "Use First Row as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
"weAreHiring": "We are Hiring!",
@ -409,9 +512,39 @@
"addRowForm": "Enter record data through a form",
"noAccess": "No access",
"restApis": "Rest APIs",
"noViews": "No Views"
"apis": "APIs",
"includeData": "Include Data",
"includeView": "Include View",
"includeWebhook": "Include Webhook",
"zoomInToViewColumns": "Zoom in to view columns"
},
"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",
"createProject": "Create Project",
"importProject": "Import Project",
@ -451,6 +584,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"disable": "Disable shared base",
"enable": "Anyone with the link",
"link": "Shared base link"
@ -489,6 +623,9 @@
"insertRow": "Insert new row",
"duplicateRow": "Duplicate row",
"deleteRow": "Delete row",
"deleteRows": "Delete rows",
"predictColumns": "Predict Columns",
"predictFormulas": "Predict Formulas",
"deleteSelectedRow": "Delete selected rows",
"importExcel": "Import Excel",
"importCSV": "Import CSV",
@ -516,7 +653,6 @@
"createKanban": "Create Kanban View",
"createForm": "Create Form View",
"showSystemFields": "Show system fields",
"copyUrl": "Copy URL",
"openTab": "Open new tab",
"iFrame": "Copy embeddable HTML code",
"addWebhook": "Add New Webhook",
@ -588,7 +724,7 @@
"generateNewApiToken": "Generate new API token",
"addRole": "Add new role",
"reloadList": "Reload list",
"metaSync": "Sync metadata",
"metaSync": "Sync Metadata",
"sqlMigration": "Reload migrations",
"updateRestart": "Update & Restart",
"cancelReturn": "Cancel and Return",
@ -601,6 +737,10 @@
},
"placeholder": {
"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": {
"enter": "Enter the password",
"current": "Current password",
@ -620,16 +760,57 @@
"selectField": "Select field"
},
"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": {
"dbValid": "Please make sure database you are trying to connect is valid! This operation can cause schema loss!!",
"barcode": {
"renderError": "Barcode error - please check compatibility between input and barcode type"
},
"nonEditableFields": {
"computedFieldUnableToClear": "Warning: Computed field - unable to clear text",
"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": {
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {
"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.",
"computedFieldEditWarning": "Computed field: contents are read-only. Use column edit menu to reconfigure",
"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": {
"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",
"invalidChar": "Invalid character in folder path.",
"invalidDbCredentials": "Invalid database credentials.",
@ -783,9 +971,10 @@
"userDoesntHaveSufficientPermission": "User does not exist or have sufficient permission to create schema.",
"dbConnectionStatus": "Invalid database parameters",
"dbConnectionFailed": "Connection Failure:",
"nullFilterExists": "Null filter exists. Please remove them",
"signUpRules": {
"emailReqd": "E-mail is required",
"emailInvalid": "E-mail must be valid",
"emailReqd": "Email is required",
"emailInvalid": "Email must be valid",
"passwdRequired": "Password is required",
"passwdLength": "You password must be atleast 8 characters",
"passwdMismatch": "Passwords do not match",
@ -794,7 +983,9 @@
"atLeastOneUppercase": "One Uppercase letter",
"atLeastOneNumber": "One Number",
"atLeastOneSpecialChar": "One special character",
"allowedSpecialCharList": "Allowed special character list"
"allowedSpecialCharList": "Allowed special character list",
"invalidEmails": "Invalid emails",
"invalidEmail": "Invalid Email"
},
"invalidURL": "Invalid URL",
"invalidEmail": "Invalid Email",
@ -825,6 +1016,8 @@
"nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _",
"followingCharactersAreNotAllowed": "Following characters are not allowed",
"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",
"projectNameExceeds50Characters": "Project name exceeds 50 characters",
"projectNameCannotStartWithSpace": "Project name cannot start with space",
@ -857,6 +1050,7 @@
"futureRelease": "Coming soon!"
},
"success": {
"licenseKeyUpdated": "License Key Updated",
"columnDuplicated": "Column duplicated successfully",
"rowDuplicatedWithoutSavedYet": "Row duplicated (not saved)",
"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">
<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>
@ -151,7 +151,7 @@ const logout = async () => {
<LazyGeneralReleaseInfo />
<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">
<LazyGeneralLanguage class="cursor-pointer text-2xl hover:text-gray-800" />

Loading…
Cancel
Save