From 5c06c0d8133e5ee22bbc989590fd4fb4e6527f9f Mon Sep 17 00:00:00 2001 From: Mateusz Misztoft Date: Wed, 24 Jun 2026 00:08:22 +0200 Subject: [PATCH] Add frontend Weblate translations Add runtime Weblate translation loading and replace hardcoded frontend strings with translation keys across the dashboard, admin panel, project views, dialogs, and notifications. --- web-app/package.json | 2 +- web-app/packages/admin-app/package.json | 1 + web-app/packages/admin-app/src/App.vue | 28 +- web-app/packages/admin-app/src/main.ts | 47 +- .../src/modules/layout/components/Sidebar.vue | 19 +- web-app/packages/admin-lib/package.json | 3 +- .../admin/components/AccountsTable.vue | 25 +- .../admin/components/AdminProjectsTable.vue | 60 +-- .../admin/components/CheckForUpdates.vue | 13 +- .../admin/components/CreateUserForm.vue | 18 +- .../admin/components/ReportDownloadDialog.vue | 20 +- .../admin/components/ServerNotConfigured.vue | 17 +- .../admin-lib/src/modules/admin/routes.ts | 24 +- .../admin-lib/src/modules/admin/store.ts | 26 +- .../modules/admin/views/AccountDetailView.vue | 107 +++-- .../src/modules/admin/views/OverviewView.vue | 20 +- .../modules/admin/views/ProjectFilesView.vue | 13 +- .../admin/views/ProjectSettingsView.vue | 31 +- .../admin/views/ProjectVersionView.vue | 16 +- .../admin/views/ProjectVersionsView.vue | 7 +- .../src/modules/admin/views/ProjectView.vue | 20 +- .../src/modules/admin/views/SettingsView.vue | 15 +- .../admin/views/SettingsViewTemplate.vue | 48 +- .../layout/components/SidebarFooter.vue | 2 +- web-app/packages/app/package.json | 1 + web-app/packages/app/src/main.ts | 44 +- .../src/modules/project/views/ProjectView.vue | 9 +- web-app/packages/lang/.eslintrc.cjs | 19 + web-app/packages/lang/i18n.d.ts | 12 + web-app/packages/lang/i18n.ts | 102 +++++ web-app/packages/lang/runtime.ts | 121 +++++ web-app/packages/lang/translate.d.ts | 2 + web-app/packages/lang/translate.ts | 27 ++ web-app/packages/lang/translations/EN.d.ts | 396 +++++++++++++++++ web-app/packages/lang/translations/EN.ts | 412 ++++++++++++++++++ web-app/packages/lib/package.json | 1 + .../common/components/AppPasswordTooltip.vue | 23 +- .../components/FullStorageWarningTemplate.vue | 15 +- .../lib/src/common/components/TipMessage.vue | 14 +- web-app/packages/lib/src/common/date_utils.ts | 17 +- web-app/packages/lib/src/common/http.ts | 7 +- .../lib/src/common/permission_utils.ts | 67 +-- .../components/DashboardAccessRequestsRow.vue | 4 +- .../dashboard/components/DashboardFooter.vue | 11 +- .../components/DashboardProjectsRow.vue | 5 +- .../lib/src/modules/dashboard/routes.ts | 5 +- .../dialog/components/ConfirmDialog.vue | 21 +- .../packages/lib/src/modules/dialog/store.ts | 8 +- .../components/InstanceMaintenanceMessage.vue | 9 +- .../lib/src/modules/instance/store.ts | 9 +- .../layout/components/AppHeaderTemplate.vue | 16 +- .../layout/components/SideBarTemplate.vue | 8 +- .../src/modules/layout/views/NotFoundView.vue | 12 +- .../components/AccessRequestTableTemplate.vue | 29 +- .../components/CloneDialogTemplate.vue | 10 +- .../project/components/CommunityBanner.vue | 12 +- .../project/components/DownloadFileLarge.vue | 18 +- .../modules/project/components/DropArea.vue | 12 +- .../components/FileChangesetSummaryTable.vue | 15 +- .../project/components/FileDetailSidebar.vue | 22 +- .../modules/project/components/FilesTable.vue | 14 +- .../components/ProjectAccessRequests.vue | 24 +- .../project/components/ProjectForm.vue | 19 +- .../components/ProjectMembersTable.vue | 12 +- .../project/components/ProjectShareDialog.vue | 14 +- .../components/ProjectShareDialogTemplate.vue | 8 +- .../components/ProjectVersionChanges.vue | 12 +- .../components/ProjectVersionsTable.vue | 20 +- .../project/components/ProjectsTable.vue | 18 +- .../components/ProjectsTableDataLoader.vue | 23 +- .../project/components/UploadDialog.vue | 35 +- .../project/components/UploadProgress.vue | 6 +- .../components/VersionDetailSidebar.vue | 16 +- .../packages/lib/src/modules/project/store.ts | 33 +- .../modules/project/views/FileBrowserView.vue | 24 +- .../project/views/FileVersionDetailView.vue | 14 +- .../ProjectCollaboratorsViewTemplate.vue | 12 +- .../views/ProjectSettingsViewTemplate.vue | 54 ++- .../views/ProjectVersionsViewTemplate.vue | 8 +- .../project/views/ProjectViewTemplate.vue | 50 ++- .../views/ProjectsListViewTemplate.vue | 54 ++- .../user/components/ChangePasswordForm.vue | 29 +- .../user/components/DeleteAccountConfirm.vue | 18 +- .../user/components/EditProfileForm.vue | 16 +- .../components/ProfileAccessRequestsRow.vue | 8 +- .../packages/lib/src/modules/user/routes.ts | 11 +- .../packages/lib/src/modules/user/store.ts | 35 +- .../modules/user/views/ChangePasswordView.vue | 30 +- .../modules/user/views/LoginViewTemplate.vue | 31 +- .../user/views/ProfileViewTemplate.vue | 65 +-- .../modules/user/views/VerifyEmailView.vue | 17 +- web-app/yarn.lock | 40 ++ 92 files changed, 2204 insertions(+), 663 deletions(-) create mode 100644 web-app/packages/lang/.eslintrc.cjs create mode 100644 web-app/packages/lang/i18n.d.ts create mode 100644 web-app/packages/lang/i18n.ts create mode 100644 web-app/packages/lang/runtime.ts create mode 100644 web-app/packages/lang/translate.d.ts create mode 100644 web-app/packages/lang/translate.ts create mode 100644 web-app/packages/lang/translations/EN.d.ts create mode 100644 web-app/packages/lang/translations/EN.ts diff --git a/web-app/package.json b/web-app/package.json index 241a0eed..e41aee2c 100755 --- a/web-app/package.json +++ b/web-app/package.json @@ -36,7 +36,7 @@ "watch:lib:types": "yarn workspace @mergin/lib build:types:watch", "watch:admin-lib": "yarn workspace @mergin/admin-lib build:lib:watch", "watch:admin-lib:types": "yarn workspace @mergin/admin-lib build:types:watch", - "lint:all": "yarn workspaces run lint", + "lint:all": "yarn workspaces run lint && eslint packages/lang --ext .ts", "lint:no-legacy": "yarn workspace @mergin/lib lint && yarn workspace @mergin/app lint" }, "devDependencies": { diff --git a/web-app/packages/admin-app/package.json b/web-app/packages/admin-app/package.json index 08fea4d7..e7d57b07 100644 --- a/web-app/packages/admin-app/package.json +++ b/web-app/packages/admin-app/package.json @@ -25,6 +25,7 @@ "primeflex": "^3.3.1", "primevue": "3.43.0", "vue": "3.5.12", + "vue-i18n": "11", "vue-meta": "^3.0.0-alpha.10", "vue-router": "4.2.5" } diff --git a/web-app/packages/admin-app/src/App.vue b/web-app/packages/admin-app/src/App.vue index cc7dff16..eaa7a1b6 100644 --- a/web-app/packages/admin-app/src/App.vue +++ b/web-app/packages/admin-app/src/App.vue @@ -63,6 +63,10 @@ import { useToast } from 'primevue/usetoast' import { defineComponent, watchEffect } from 'vue' import { useMeta } from 'vue-meta' +import returnTranslation from '@/../../lang/translate' + +const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key) + export default defineComponent({ name: 'app', components: { @@ -77,13 +81,15 @@ export default defineComponent({ meta: [ { name: 'description', - content: - 'Store and track changes to your geo-data. Mergin Maps is a repository of geo-data for collaborative work.' + content: t( + 'StoreAndTrackChangesToYourGeoDataMerginMapsIsARepositoryOfGeoDataForCollaborativeWork' + ) }, { property: 'og:title', - content: - 'Store and track changes to your geo-data. Mergin Maps is a repository of geo-data for collaborative work.' + content: t( + 'StoreAndTrackChangesToYourGeoDataMerginMapsIsARepositoryOfGeoDataForCollaborativeWork' + ) }, { property: 'og:site_name', content: 'Mergin Maps' } ] @@ -122,20 +128,22 @@ export default defineComponent({ }, setup() { const { title } = useRouterTitle({ - defaultTitle: 'Mergin Maps Admin Panel' + defaultTitle: t('MerginMapsAdminPanel') }) useMeta({ - title: 'Mergin Maps Admin Panel', + title: t('MerginMapsAdminPanel'), meta: [ { name: 'description', - content: - 'Store and track changes to your geo-data. Mergin Maps is a repository of geo-data for collaborative work.' + content: t( + 'StoreAndTrackChangesToYourGeoDataMerginMapsIsARepositoryOfGeoDataForCollaborativeWork' + ) }, { property: 'og:title', - content: - 'Store and track changes to your geo-data. Mergin Maps is a repository of geo-data for collaborative work.' + content: t( + 'StoreAndTrackChangesToYourGeoDataMerginMapsIsARepositoryOfGeoDataForCollaborativeWork' + ) }, { property: 'og:site_name', content: 'Mergin Maps' } ] diff --git a/web-app/packages/admin-app/src/main.ts b/web-app/packages/admin-app/src/main.ts index f7095c64..6a5993d1 100644 --- a/web-app/packages/admin-app/src/main.ts +++ b/web-app/packages/admin-app/src/main.ts @@ -2,21 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial -import { AdminModule } from '@mergin/admin-lib' -import { - DialogModule, - FormModule, - getHttpService, - InstanceModule, - LayoutModule, - moduleUtils, - NotificationModule, - ProjectModule, - UserModule, - useInstanceStore, - initCsrfToken -} from '@mergin/lib' - import 'primevue/resources/primevue.min.css' import 'primeflex/primeflex.min.css' import '@mergin/lib/dist/sass/themes/mm-theme-light/theme.scss' @@ -24,10 +9,34 @@ import '@tabler/icons-webfont/tabler-icons.min.css' import '@mergin/lib/dist/style.css' import '@mergin/admin-lib/dist/style.css' -import { createMerginApp } from './app' -import { createPiniaInstance, getPiniaInstance } from './store' +import { initializeRuntimeI18n } from '@/../../lang/runtime' async function main() { + const i18n = await initializeRuntimeI18n() + const [ + { AdminModule }, + { + DialogModule, + FormModule, + getHttpService, + InstanceModule, + LayoutModule, + moduleUtils, + NotificationModule, + ProjectModule, + UserModule, + useInstanceStore, + initCsrfToken + }, + { createMerginApp }, + { createPiniaInstance, getPiniaInstance } + ] = await Promise.all([ + import('@mergin/admin-lib'), + import('@mergin/lib'), + import('./app'), + import('./store') + ]) + createPiniaInstance() const pinia = getPiniaInstance() const httpService = getHttpService() @@ -52,7 +61,9 @@ async function main() { const instanceStore = useInstanceStore(pinia) const response = await instanceStore.initApp() initCsrfToken(response) - createMerginApp().mount('#app') + const app = createMerginApp() + app.use(i18n) + app.mount('#app') } main() diff --git a/web-app/packages/admin-app/src/modules/layout/components/Sidebar.vue b/web-app/packages/admin-app/src/modules/layout/components/Sidebar.vue index 44f63887..bc38aa42 100644 --- a/web-app/packages/admin-app/src/modules/layout/components/Sidebar.vue +++ b/web-app/packages/admin-app/src/modules/layout/components/Sidebar.vue @@ -4,35 +4,44 @@ Copyright (C) Lutra Consulting Limited SPDX-License-Identifier: LicenseRef-MerginMaps-Commercial --> + + diff --git a/web-app/packages/admin-lib/src/modules/admin/views/OverviewView.vue b/web-app/packages/admin-lib/src/modules/admin/views/OverviewView.vue index d90d4747..2d0881e1 100644 --- a/web-app/packages/admin-lib/src/modules/admin/views/OverviewView.vue +++ b/web-app/packages/admin-lib/src/modules/admin/views/OverviewView.vue @@ -8,14 +8,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial - +
- +
- +
- +
- +
- +
diff --git a/web-app/packages/admin-lib/src/modules/admin/views/ProjectFilesView.vue b/web-app/packages/admin-lib/src/modules/admin/views/ProjectFilesView.vue index 359eeb15..1b6a5897 100644 --- a/web-app/packages/admin-lib/src/modules/admin/views/ProjectFilesView.vue +++ b/web-app/packages/admin-lib/src/modules/admin/views/ProjectFilesView.vue @@ -9,7 +9,7 @@ (), { const router = useRouter() const projectStore = useProjectStore() const { project } = storeToRefs(projectStore) +const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key) const options = ref({ sortBy: 'name', @@ -71,10 +74,10 @@ const searchFilter = ref('') const filterMenuItems = computed(() => { const items = [ - { label: 'Sort by name A-Z', key: 'name', sortDesc: 1 }, - { label: 'Sort by name Z-A', key: 'name', sortDesc: -1 }, - { label: 'Sort by last modified', key: 'mtime', sortDesc: -1 }, - { label: 'Sort by file size', key: 'size', sortDesc: -1 } + { label: t('SortByNameAZ'), key: 'name', sortDesc: 1 }, + { label: t('SortByNameZA'), key: 'name', sortDesc: -1 }, + { label: t('SortByLastModified'), key: 'mtime', sortDesc: -1 }, + { label: t('SortByFileSize'), key: 'size', sortDesc: -1 } ] return items.map((item) => ({ diff --git a/web-app/packages/admin-lib/src/modules/admin/views/ProjectSettingsView.vue b/web-app/packages/admin-lib/src/modules/admin/views/ProjectSettingsView.vue index 126e7b23..15a2a9ab 100644 --- a/web-app/packages/admin-lib/src/modules/admin/views/ProjectSettingsView.vue +++ b/web-app/packages/admin-lib/src/modules/admin/views/ProjectSettingsView.vue @@ -1,7 +1,7 @@ diff --git a/web-app/packages/lib/src/common/components/FullStorageWarningTemplate.vue b/web-app/packages/lib/src/common/components/FullStorageWarningTemplate.vue index 542fdb2b..66234330 100644 --- a/web-app/packages/lib/src/common/components/FullStorageWarningTemplate.vue +++ b/web-app/packages/lib/src/common/components/FullStorageWarningTemplate.vue @@ -13,9 +13,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

Your storage is almost full ({{ usage }}%).{{ yourStorageIsAlmostFull }} ({{ usage }}%). - Soon you will not be able to sync your projects. + {{ soonYouWillNotBeAbleToSyncYourProjects }}

@@ -25,9 +25,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial diff --git a/web-app/packages/lib/src/modules/instance/store.ts b/web-app/packages/lib/src/modules/instance/store.ts index 49a9aa20..08ccee6b 100644 --- a/web-app/packages/lib/src/modules/instance/store.ts +++ b/web-app/packages/lib/src/modules/instance/store.ts @@ -4,6 +4,7 @@ import { defineStore } from 'pinia' +import returnTranslation from '@/../../lang/translate' import { GlobalRole } from '@/common/permission_utils' import { InstanceApi } from '@/modules/instance/instanceApi' import { @@ -14,6 +15,8 @@ import { import { useNotificationStore } from '@/modules/notification/store' import { useUserStore } from '@/modules/user/store' +const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key) + export interface InstanceState { initData: InitResponse initialized: boolean @@ -72,7 +75,7 @@ export const useInstanceStore = defineStore('instanceModule', { } return response } catch { - notificationStore.error({ text: 'Failed to init application.' }) + notificationStore.error({ text: t('FailedToInitApplication') }) } }, @@ -83,7 +86,7 @@ export const useInstanceStore = defineStore('instanceModule', { this.setPingData(response.data) return response } catch { - await notificationStore.error({ text: 'Failed to fetch ping data.' }) + await notificationStore.error({ text: t('FailedToFetchPingData') }) } }, @@ -94,7 +97,7 @@ export const useInstanceStore = defineStore('instanceModule', { this.setConfigData(response.data) return response } catch { - await notificationStore.error({ text: 'Failed to fetch config data.' }) + await notificationStore.error({ text: t('FailedToFetchConfigData') }) } } } diff --git a/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue b/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue index 1d1501ef..0b8a888b 100644 --- a/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue +++ b/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial v-if="renderNamespace" class="paragraph-p6 opacity-80 font-normal" > - {{ currentWorkspace?.name || 'no workspace' }} + {{ currentWorkspace?.name || t('NoWorkspace') }}
{ this.$router.push({ @@ -187,7 +188,7 @@ export default defineComponent({ ] : []), { - label: 'Sign out', + label: this.t('SignOut'), icon: 'ti ti-logout', command: () => { this.logout() @@ -198,12 +199,12 @@ export default defineComponent({ _helpMenuItems() { return [ { - label: 'Documentation', + label: this.t('Documentation'), url: this.configData?.docs_url, target: '_blank' }, { - label: 'Community chat', + label: this.t('CommunityChat'), url: import.meta.env.VITE_VUE_APP_JOIN_COMMUNITY_LINK, target: '_blank' }, @@ -228,6 +229,9 @@ export default defineComponent({ methods: { ...mapActions(useLayoutStore, ['setDrawer']), ...mapActions(useUserStore, { logoutUser: 'logout' }), + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + }, async logout() { try { diff --git a/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue b/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue index d4ab084f..30c7f461 100644 --- a/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue +++ b/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue @@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial href="/admin" target="__blank" class="flex justify-content-between align-items-center title-t5 no-underline cursor-pointer" - >Admin Panel {{ t('AdminPanel') }} @@ -89,6 +89,7 @@ import { useRoute } from 'vue-router' import { SideBarItemModel } from '../types' +import returnTranslation from '@/../../lang/translate' import defaultLogoUrl from '@/assets/mm-logo.svg' import { DashboardRouteName, useUserStore } from '@/main' import { useInstanceStore } from '@/modules/instance/store' @@ -113,6 +114,7 @@ const logoUrl = computed(() => const route = useRoute() const layoutStore = useLayoutStore() const userStore = useUserStore() +const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key) const props = defineProps<{ sidebarItems?: SideBarItemModel[] }>() @@ -124,7 +126,7 @@ const initialSidebarItems = computed(() => { active: route.matched.some( (item) => item.name === DashboardRouteName.Dashboard ), - title: 'Dashboard', + title: t('Dashboard'), to: '/dashboard', icon: 'ti ti-home' }, @@ -134,7 +136,7 @@ const initialSidebarItems = computed(() => { item.name === ProjectRouteName.Projects || item.name === ProjectRouteName.Project ), - title: 'Projects', + title: t('Projects'), to: '/projects', icon: 'ti ti-article' } diff --git a/web-app/packages/lib/src/modules/layout/views/NotFoundView.vue b/web-app/packages/lib/src/modules/layout/views/NotFoundView.vue index ee6ca251..0157af88 100644 --- a/web-app/packages/lib/src/modules/layout/views/NotFoundView.vue +++ b/web-app/packages/lib/src/modules/layout/views/NotFoundView.vue @@ -15,15 +15,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial class="not-found-view-container flex flex-column align-items-center text-center row-gap-4 p-4 lg:p-0" >
-

Ooops, it seems the GPS has lost its way.

+

{{ t('OoopsItSeemsTheGPSHasLostItsWay') }}

Not found - This page does not exist, check your url for mistakes, please. + {{ t('ThisPageDoesNotExistCheckYourUrlForMistakesPlease') }} Back to Dashboard + >{{ t('BackToDashboard') }}
@@ -33,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial import { mapState } from 'pinia' import { defineComponent } from 'vue' +import returnTranslation from '@/../../lang/translate' import { useUserStore } from '@/modules/user/store' export default defineComponent({ @@ -42,6 +43,11 @@ export default defineComponent({ displayBackButton() { return this.loggedUser } + }, + methods: { + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + } } }) diff --git a/web-app/packages/lib/src/modules/project/components/AccessRequestTableTemplate.vue b/web-app/packages/lib/src/modules/project/components/AccessRequestTableTemplate.vue index 4239ea62..288757a4 100644 --- a/web-app/packages/lib/src/modules/project/components/AccessRequestTableTemplate.vue +++ b/web-app/packages/lib/src/modules/project/components/AccessRequestTableTemplate.vue @@ -18,7 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial @page="onPage" > @@ -99,6 +102,7 @@ import { DataViewPageEvent } from 'primevue/dataview' import { DropdownChangeEvent } from 'primevue/dropdown' import { defineComponent } from 'vue' +import returnTranslation from '@/../../lang/translate' import AppDropdown from '@/common/components/AppDropdown.vue' import { ProjectPermissionName } from '@/common/permission_utils' import { useUserStore } from '@/main' @@ -161,6 +165,9 @@ export default defineComponent({ 'getAccessRequests' ]), ...mapActions(useNotificationStore, ['error']), + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + }, onPage(e: DataViewPageEvent) { this.options.page = e.page + 1 diff --git a/web-app/packages/lib/src/modules/project/components/CloneDialogTemplate.vue b/web-app/packages/lib/src/modules/project/components/CloneDialogTemplate.vue index 5d6fa39a..2a8d81c0 100644 --- a/web-app/packages/lib/src/modules/project/components/CloneDialogTemplate.vue +++ b/web-app/packages/lib/src/modules/project/components/CloneDialogTemplate.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial data-cy="clone-dialog-project-name" class="flex-grow-1" /> - + @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial @click="close" class="flex w-12 mb-2 lg:mb-0 lg:mr-2 lg:w-6 justify-content-center" data-cy="clone-dialog-close-btn" - >Cancel{{ t('Cancel') }} - Clone project + {{ t('CloneProject') }}
@@ -50,6 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial import { mapActions, mapState } from 'pinia' import { defineComponent } from 'vue' +import returnTranslation from '@/../../lang/translate' import { useDialogStore } from '@/modules/dialog/store' import { useFormStore } from '@/modules/form/store' import { useProjectStore } from '@/modules/project/store' @@ -84,6 +85,9 @@ export default defineComponent({ ...mapActions(useDialogStore, ['close']), ...mapActions(useFormStore, ['clearErrors', 'handleError']), ...mapActions(useProjectStore, ['cloneProject']), + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + }, successCloneCallback() { this.close() diff --git a/web-app/packages/lib/src/modules/project/components/CommunityBanner.vue b/web-app/packages/lib/src/modules/project/components/CommunityBanner.vue index df607c88..4e72d339 100644 --- a/web-app/packages/lib/src/modules/project/components/CommunityBanner.vue +++ b/web-app/packages/lib/src/modules/project/components/CommunityBanner.vue @@ -12,14 +12,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial - - + + @@ -29,10 +29,12 @@ import { useRouter } from 'vue-router' import { ProjectRouteName } from '../routes' +import returnTranslation from '@/../../lang/translate' import AppContainer from '@/common/components/AppContainer.vue' import AppSectionBanner from '@/common/components/AppSectionBanner.vue' const router = useRouter() +const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key) function click() { router.push({ name: ProjectRouteName.ProjectsExplore }) diff --git a/web-app/packages/lib/src/modules/project/components/DownloadFileLarge.vue b/web-app/packages/lib/src/modules/project/components/DownloadFileLarge.vue index 4865dc66..6b785091 100644 --- a/web-app/packages/lib/src/modules/project/components/DownloadFileLarge.vue +++ b/web-app/packages/lib/src/modules/project/components/DownloadFileLarge.vue @@ -8,13 +8,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial @@ -24,9 +27,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial diff --git a/web-app/packages/lib/src/modules/project/components/DropArea.vue b/web-app/packages/lib/src/modules/project/components/DropArea.vue index cb675fbd..fe397fbb 100644 --- a/web-app/packages/lib/src/modules/project/components/DropArea.vue +++ b/web-app/packages/lib/src/modules/project/components/DropArea.vue @@ -40,10 +40,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

- Drag and drop files + {{ t('DragAndDropFiles') }}

- You can drop files from your computer to start uploading + {{ t('YouCanDropFilesFromYourComputerToStartUploading') }}

@@ -58,6 +58,7 @@ import Path from 'path' import { mapActions, mapState } from 'pinia' import { defineComponent } from 'vue' +import returnTranslation from '@/../../lang/translate' import { getFiles, checksum } from '@/common/mergin_utils' import { useInstanceStore } from '@/modules/instance/store' import { useNotificationStore } from '@/modules/notification/store' @@ -94,6 +95,9 @@ export default defineComponent({ 'finishFileAnalysis', 'analysingFiles' ]), + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + }, setOver: debounce(function (isOver) { this.dragOver = isOver @@ -110,7 +114,7 @@ export default defineComponent({ } if (this.upload && this.upload.running) { return this.error({ - text: 'You cannot update files during upload' + text: this.t('YouCannotUpdateFilesDuringUpload') }) } // prepare all entries because they will be not accessible after this callback ends (after 'await') @@ -119,7 +123,7 @@ export default defineComponent({ ).map((i) => i.webkitGetAsEntry()) if (entries.some((e) => e === null)) { return this.error({ - text: 'Drop only files or folders' + text: this.t('DropOnlyFilesOrFolders') }) } this.createUpload(entries) diff --git a/web-app/packages/lib/src/modules/project/components/FileChangesetSummaryTable.vue b/web-app/packages/lib/src/modules/project/components/FileChangesetSummaryTable.vue index 3139be19..1205bdee 100644 --- a/web-app/packages/lib/src/modules/project/components/FileChangesetSummaryTable.vue +++ b/web-app/packages/lib/src/modules/project/components/FileChangesetSummaryTable.vue @@ -58,6 +58,8 @@ import { PropType, defineComponent } from 'vue' import { ChangesetSuccessSummaryItem } from '../types' +import returnTranslation from '@/../../lang/translate' + export default defineComponent({ name: 'file-changeset-summary-table', props: { @@ -66,14 +68,14 @@ export default defineComponent({ data() { return { columns: [ - { text: 'Layer', value: 'table' }, + { text: this.t('Layer'), value: 'table' }, { - text: 'Inserts', + text: this.t('Inserts'), icon: 'ti-plus', value: 'insert' }, - { text: 'Updates', icon: 'ti-pencil', value: 'update' }, - { text: 'Deletes', icon: 'ti-trash', value: 'delete' } + { text: this.t('Updates'), icon: 'ti-pencil', value: 'update' }, + { text: this.t('Deletes'), icon: 'ti-trash', value: 'delete' } ], itemsPerPage: 10 } @@ -83,6 +85,11 @@ export default defineComponent({ // Displayed changesets data into table data return this.changesets.filter((p) => p.table !== 'gpkg_contents') } + }, + methods: { + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + } } }) diff --git a/web-app/packages/lib/src/modules/project/components/FileDetailSidebar.vue b/web-app/packages/lib/src/modules/project/components/FileDetailSidebar.vue index 4dcec5ee..20024b61 100644 --- a/web-app/packages/lib/src/modules/project/components/FileDetailSidebar.vue +++ b/web-app/packages/lib/src/modules/project/components/FileDetailSidebar.vue @@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
-
File
+
{{ t('File') }}

{{ fileName }} @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

-
Modified
+
{{ t('Modified') }}
{{ $filters.timediff(file.mtime) @@ -45,11 +45,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
-
Size
+
{{ t('Size') }}
{{ $filters.filesize(file.size) }} (new: + >({{ t('New') }} {{ $filters.filesize(upload.files[file.path].size) }})
@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial v-if="isOverLimit" class="flex justify-content-center m-4 opacity-60" > - File too large to preview — download to view instead. + {{ t('FileTooLargeToPreviewDownloadToViewInstead') }}

- Delete file + {{ t('DeleteFile') }} @@ -102,6 +102,7 @@ import { defineComponent } from 'vue' import FileIcon from './FileIcon.vue' +import returnTranslation from '@/../../lang/translate' import AppSidebarRight from '@/common/components/AppSidebarRight.vue' import { ProjectApi } from '@/modules/project/projectApi' import { useProjectStore } from '@/modules/project/store' @@ -164,9 +165,9 @@ export default defineComponent({ }, stateText() { const stateText: Record = { - added: 'New file', - removed: 'Deleted file', - updated: 'Modified file' + added: this.t('NewFile'), + removed: this.t('DeletedFile'), + updated: this.t('ModifiedFile') } return this.state && stateText[this.state] }, @@ -212,6 +213,9 @@ export default defineComponent({ }, methods: { ...mapActions(useProjectStore, ['deleteFiles']), + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + }, txtPreview() { if (this.isOverLimit) { this.content = null diff --git a/web-app/packages/lib/src/modules/project/components/FilesTable.vue b/web-app/packages/lib/src/modules/project/components/FilesTable.vue index 1409ccd7..999e8318 100644 --- a/web-app/packages/lib/src/modules/project/components/FilesTable.vue +++ b/web-app/packages/lib/src/modules/project/components/FilesTable.vue @@ -56,7 +56,7 @@ > {{ col.text }}
-
Files
+
{{ t('Files') }}
@@ -108,6 +108,7 @@ import max from 'lodash/max' import Path from 'path' import { computed } from 'vue' +import returnTranslation from '@/../../lang/translate' import { dirname } from '@/common/path_utils' import { removeAccents } from '@/common/text_utils' import FileIcon from '@/modules/project/components/FileIcon.vue' @@ -150,11 +151,12 @@ const emit = defineEmits<{ const projectStore = useProjectStore() const ITEMS_PER_PAGE = 100 +const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key) const columns: Column[] = [ - { text: 'Name', key: 'name', cols: 8 }, - { text: 'Modified', key: 'mtime' }, - { text: 'Size', key: 'size' } + { text: t('Name'), key: 'name', cols: 8 }, + { text: t('Modified'), key: 'mtime' }, + { text: t('Size'), key: 'size' } ] const breadcrumps = computed(() => { @@ -172,7 +174,7 @@ const breadcrumps = computed(() => { return [ { icon: 'ti ti-folder', - label: 'Files', + label: t('Files'), path: folderLink(''), active: parts.length === 0 }, diff --git a/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue b/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue index 2771b53f..572e6222 100644 --- a/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue +++ b/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue @@ -18,7 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial @page="onPage" > @@ -86,6 +88,7 @@ import { mapActions, mapState } from 'pinia' import { DataViewPageEvent } from 'primevue/dataview' import { defineComponent } from 'vue' +import returnTranslation from '@/../../lang/translate' import AppDropdown from '@/common/components/AppDropdown.vue' import { getErrorMessage } from '@/common/error_utils' import { isAtLeastProjectRole, ProjectRole } from '@/common/permission_utils' @@ -128,6 +131,9 @@ export default defineComponent({ 'getAccessRequests' ]), ...mapActions(useNotificationStore, ['error']), + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + }, onUpdateOptions(options) { this.options = options @@ -163,7 +169,7 @@ export default defineComponent({ await this.updatePaginationOrFetch() } catch (err) { this.error({ - text: getErrorMessage(err, 'Failed to accept access request') + text: getErrorMessage(err, this.t('FailedToAcceptAccessRequest')) }) } }, diff --git a/web-app/packages/lib/src/modules/project/components/ProjectForm.vue b/web-app/packages/lib/src/modules/project/components/ProjectForm.vue index d9f200d9..76706165 100644 --- a/web-app/packages/lib/src/modules/project/components/ProjectForm.vue +++ b/web-app/packages/lib/src/modules/project/components/ProjectForm.vue @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial data-cy="project-form-name" class="flex-grow-1" /> - + {{ errors.detail || ' ' }} @@ -26,10 +26,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial - >
Cancel{{ t('Cancel') }} - Create project + {{ t('CreateProject') }}
@@ -59,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial import { mapActions, mapState } from 'pinia' import { defineComponent } from 'vue' +import returnTranslation from '@/../../lang/translate' import { TipMessage } from '@/common/components' import { useUserStore } from '@/main' import { useDialogStore } from '@/modules/dialog/store' @@ -92,6 +94,9 @@ export default defineComponent({ ...mapActions(useDialogStore, ['close']), ...mapActions(useFormStore, ['clearErrors']), ...mapActions(useProjectStore, ['createProject']), + t(key: string) { + return returnTranslation(import.meta.env.VITE_LANG, key) + }, async create() { // TODO: add types diff --git a/web-app/packages/lib/src/modules/project/components/ProjectMembersTable.vue b/web-app/packages/lib/src/modules/project/components/ProjectMembersTable.vue index ef994b92..d3e2b659 100644 --- a/web-app/packages/lib/src/modules/project/components/ProjectMembersTable.vue +++ b/web-app/packages/lib/src/modules/project/components/ProjectMembersTable.vue @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial :columns="columns" :paginator="searchedItems.length > itemsPerPage" :loading="projectStore.accessLoading" - :empty-message="'No members found.'" + :empty-message="t('NoMembersFound')" :row-cursor-pointer="false" :options="{ itemsPerPage, @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial sortDesc: projectStore.accessSorting?.sortDesc }" > - +