-
Disk usage
+
{{ $t('DiskUsage') }}
{{ $filters.filesize(project?.disk_usage, 'MB') }}
@@ -117,6 +117,7 @@ import {
import { computed, watch, defineProps } from 'vue'
import { useRouter, useRoute } from 'vue-router'
+import returnTranslation from '@/../../lang/translate'
import { AdminRoutes } from '@/modules'
import AdminLayout from '@/modules/admin/components/AdminLayout.vue'
@@ -128,6 +129,7 @@ interface TabItem {
const route = useRoute()
const router = useRouter()
const projectStore = useProjectStore()
+const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key)
defineProps<{
projectName: string
@@ -138,15 +140,15 @@ const tabs = computed(() => {
const tabs: TabItem[] = [
{
route: AdminRoutes.ProjectTree,
- header: 'Files'
+ header: t('Files')
},
{
route: AdminRoutes.ProjectHistory,
- header: 'History'
+ header: t('History')
},
{
route: AdminRoutes.ProjectSettings,
- header: 'Settings'
+ header: t('Settings')
}
]
return tabs
diff --git a/web-app/packages/admin-lib/src/modules/admin/views/SettingsView.vue b/web-app/packages/admin-lib/src/modules/admin/views/SettingsView.vue
index 8204b41a..950e677b 100644
--- a/web-app/packages/admin-lib/src/modules/admin/views/SettingsView.vue
+++ b/web-app/packages/admin-lib/src/modules/admin/views/SettingsView.vue
@@ -11,14 +11,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
{{ item.description
}}
- Click
+ {{ $t('Click') }}
here{{ $t('Here') }}
- for more information.
@@ -41,15 +41,18 @@ import {
} from '@mergin/lib'
import { ref } from 'vue'
+import returnTranslation from '@/../../lang/translate'
import SettingsViewTemplate from '@/modules/admin/views/SettingsViewTemplate.vue'
const instanceStore = useInstanceStore()
+const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key)
const settingsItems = ref([
{
- title: 'Collect statistics',
- description:
- 'Help us improve Mergin Maps by sharing usage information. Mergin Maps collects anonymous usage information to make the service better overtime.',
+ title: t('CollectStatistics'),
+ description: t(
+ 'HelpUsImproveMerginMapsBySharingUsageInformationMerginMapsCollectsAnonymousUsageInformationToMakeTheServiceBetterOvertime'
+ ),
key: 'usageInformation'
}
])
diff --git a/web-app/packages/admin-lib/src/modules/admin/views/SettingsViewTemplate.vue b/web-app/packages/admin-lib/src/modules/admin/views/SettingsViewTemplate.vue
index 3956ffbc..c4333bf6 100644
--- a/web-app/packages/admin-lib/src/modules/admin/views/SettingsViewTemplate.vue
+++ b/web-app/packages/admin-lib/src/modules/admin/views/SettingsViewTemplate.vue
@@ -8,14 +8,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
- Settings
+ {{ $t('Settings') }}
- Advanced
-
+ {{ $t('Advanced') }}
+
@@ -43,26 +45,34 @@ import {
AppSettingsItemConfig,
useDialogStore
} from '@mergin/lib'
+import { computed } from 'vue'
import ReportDownloadDialog from '../components/ReportDownloadDialog.vue'
import { useAdminStore } from '../store'
+import returnTranslation from '@/../../lang/translate'
import AdminLayout from '@/modules/admin/components/AdminLayout.vue'
-withDefaults(defineProps<{ settingsItems?: AppSettingsItemConfig[] }>(), {
- settingsItems: () => [
- {
- title: 'Check for updates',
- description: 'Let Mergin Maps automatically check for new updates',
- key: 'checkForUpdates'
- },
- {
- title: 'Server usage report',
- description: 'Download usage statistics for your server deployment.',
- key: 'downloadReport'
- }
- ]
-})
+const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key)
+
+const props = defineProps<{ settingsItems?: AppSettingsItemConfig[] }>()
+
+const defaultSettingsItems = computed(() => [
+ {
+ title: t('CheckForUpdates'),
+ description: t('LetMerginMapsAutomaticallyCheckForNewUpdates'),
+ key: 'checkForUpdates'
+ },
+ {
+ title: t('ServerUsageReport'),
+ description: t('DownloadUsageStatisticsForYourServerDeployment'),
+ key: 'downloadReport'
+ }
+])
+
+const resolvedSettingsItems = computed(
+ () => props.settingsItems ?? defaultSettingsItems.value
+)
const adminStore = useAdminStore()
const dialogStore = useDialogStore()
@@ -76,7 +86,7 @@ function downloadReport() {
dialogStore.show({
component: ReportDownloadDialog,
params: {
- dialog: { header: 'Download report' }
+ dialog: { header: t('DownloadReport') }
}
})
}
diff --git a/web-app/packages/admin-lib/src/modules/layout/components/SidebarFooter.vue b/web-app/packages/admin-lib/src/modules/layout/components/SidebarFooter.vue
index 75667f01..6e00faca 100644
--- a/web-app/packages/admin-lib/src/modules/layout/components/SidebarFooter.vue
+++ b/web-app/packages/admin-lib/src/modules/layout/components/SidebarFooter.vue
@@ -4,7 +4,7 @@
href="/"
target="__blank"
class="flex justify-content-between align-items-center title-t5 no-underline cursor-pointer"
- >Dashboard {{ $t('Dashboard') }}
diff --git a/web-app/packages/app/package.json b/web-app/packages/app/package.json
index 43de8339..aa17c980 100644
--- a/web-app/packages/app/package.json
+++ b/web-app/packages/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/app/src/main.ts b/web-app/packages/app/src/main.ts
index 7522f914..900bfd32 100644
--- a/web-app/packages/app/src/main.ts
+++ b/web-app/packages/app/src/main.ts
@@ -2,30 +2,38 @@
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
-import {
- DialogModule,
- FormModule,
- UserModule,
- NotificationModule,
- moduleUtils,
- getHttpService,
- ProjectModule,
- LayoutModule,
- InstanceModule,
- 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'
import '@tabler/icons-webfont/tabler-icons.min.css'
import '@mergin/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 [
+ {
+ DialogModule,
+ FormModule,
+ UserModule,
+ NotificationModule,
+ moduleUtils,
+ getHttpService,
+ ProjectModule,
+ LayoutModule,
+ InstanceModule,
+ useInstanceStore,
+ initCsrfToken
+ },
+ { createMerginApp },
+ { createPiniaInstance, getPiniaInstance }
+ ] = await Promise.all([
+ import('@mergin/lib'),
+ import('./app'),
+ import('./store')
+ ])
+
createPiniaInstance()
const pinia = getPiniaInstance()
const httpService = getHttpService()
@@ -49,7 +57,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/app/src/modules/project/views/ProjectView.vue b/web-app/packages/app/src/modules/project/views/ProjectView.vue
index 4a856a86..78fa8310 100644
--- a/web-app/packages/app/src/modules/project/views/ProjectView.vue
+++ b/web-app/packages/app/src/modules/project/views/ProjectView.vue
@@ -28,6 +28,8 @@ import {
} from '@mergin/lib'
import { computed, defineComponent } from 'vue'
+import returnTranslation from '@/../../lang/translate'
+
export default defineComponent({
name: 'ProjectView',
components: {
@@ -45,6 +47,7 @@ export default defineComponent({
const canCreateProject = computed(() => userStore.isGlobalWorkspaceAdmin)
const isProjectOwner = computed(() => projectStore.isProjectOwner)
+ const t = (key: string) => returnTranslation(import.meta.env.VITE_LANG, key)
function openCloneDialog() {
const dialogProps = {
@@ -54,14 +57,14 @@ export default defineComponent({
const dialog = {
maxWidth: 580,
persistent: true,
- header: 'Clone project'
+ header: t('CloneProject')
}
const listeners = {
error: (error, data) => {
formStore.handleError({
componentId: data.merginComponentUuid,
error,
- generalMessage: 'Failed to clone project'
+ generalMessage: t('FailedToCloneProject')
})
}
}
@@ -78,7 +81,7 @@ export default defineComponent({
function openShareDialog() {
const dialog = {
maxWidth: 600,
- header: 'Share project'
+ header: t('ShareProject')
}
dialogStore.show({
component: ProjectShareDialog,
diff --git a/web-app/packages/lang/.eslintrc.cjs b/web-app/packages/lang/.eslintrc.cjs
new file mode 100644
index 00000000..946d963d
--- /dev/null
+++ b/web-app/packages/lang/.eslintrc.cjs
@@ -0,0 +1,19 @@
+module.exports = {
+ root: true,
+ ignorePatterns: ['*.d.ts'],
+ extends: ['../../.eslintrc.cjs'],
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module'
+ },
+ plugins: ['@typescript-eslint'],
+ overrides: [
+ {
+ files: ['translations/*.ts'],
+ rules: {
+ 'prettier/prettier': 'off'
+ }
+ }
+ ]
+}
diff --git a/web-app/packages/lang/i18n.d.ts b/web-app/packages/lang/i18n.d.ts
new file mode 100644
index 00000000..fb127e68
--- /dev/null
+++ b/web-app/packages/lang/i18n.d.ts
@@ -0,0 +1,12 @@
+export type LocaleMessages = Record
;
+export type LocaleMessagesMap = Record;
+export interface MerginI18nOptions {
+ locale?: string;
+ messages?: LocaleMessagesMap;
+}
+export declare const DEFAULT_LOCALE = "en";
+export declare const normalizeLocale: (locale?: string) => string;
+export declare const getLocalTranslation: (key: string) => string;
+export declare const getRuntimeTranslation: (key: string, locale?: string) => string;
+export declare const createMerginI18n: ({ locale, messages }?: MerginI18nOptions) => import("vue-i18n").I18n, Record, Record, unknown, boolean>;
+export declare const getMerginI18n: () => import("vue-i18n").I18n, Record, Record, unknown, boolean>;
diff --git a/web-app/packages/lang/i18n.ts b/web-app/packages/lang/i18n.ts
new file mode 100644
index 00000000..95f0f789
--- /dev/null
+++ b/web-app/packages/lang/i18n.ts
@@ -0,0 +1,102 @@
+import { createI18n } from 'vue-i18n'
+
+import EN from './translations/EN.js'
+
+export type LocaleMessages = Record
+export type LocaleMessagesMap = Record
+
+export interface MerginI18nOptions {
+ locale?: string
+ messages?: LocaleMessagesMap
+}
+
+interface RuntimeI18nState {
+ locale: string
+ fallbackLocale: string
+ messages: LocaleMessagesMap
+}
+
+export const DEFAULT_LOCALE = 'en'
+const RUNTIME_I18N_STATE_KEY = '__MERGIN_RUNTIME_I18N__'
+
+let merginI18n: ReturnType | undefined
+
+export const normalizeLocale = (locale?: string) =>
+ locale?.trim().replace(/_/g, '-').toLowerCase() || undefined
+
+const localMessages: LocaleMessagesMap = {
+ [DEFAULT_LOCALE]: EN as LocaleMessages
+}
+
+export const getLocalTranslation = (key: string) =>
+ localMessages[DEFAULT_LOCALE]?.[key]
+
+const getRuntimeI18nState = () =>
+ (
+ globalThis as typeof globalThis &
+ Record
+ )[RUNTIME_I18N_STATE_KEY]
+
+const setRuntimeI18nState = (state: RuntimeI18nState) => {
+ ;(
+ globalThis as typeof globalThis &
+ Record
+ )[RUNTIME_I18N_STATE_KEY] = state
+}
+
+export const getRuntimeTranslation = (key: string, locale?: string) => {
+ const state = getRuntimeI18nState()
+
+ if (!state) {
+ return undefined
+ }
+
+ const normalizedLocale = normalizeLocale(locale) ?? state.locale
+
+ return (
+ state.messages[normalizedLocale]?.[key] ??
+ state.messages[state.locale]?.[key] ??
+ state.messages[state.fallbackLocale]?.[key]
+ )
+}
+
+export const createMerginI18n = ({
+ locale = DEFAULT_LOCALE,
+ messages = {}
+}: MerginI18nOptions = {}) => {
+ const resolvedLocale = normalizeLocale(locale) ?? DEFAULT_LOCALE
+ const resolvedMessages: LocaleMessagesMap = {
+ [DEFAULT_LOCALE]: localMessages[DEFAULT_LOCALE]
+ }
+
+ Object.entries(messages).forEach(([messageLocale, localeMessages]) => {
+ const normalizedLocale = normalizeLocale(messageLocale) ?? messageLocale
+ resolvedMessages[normalizedLocale] = {
+ ...(resolvedMessages[normalizedLocale] ?? {}),
+ ...localeMessages
+ }
+ })
+
+ const activeLocale = resolvedMessages[resolvedLocale]
+ ? resolvedLocale
+ : DEFAULT_LOCALE
+
+ setRuntimeI18nState({
+ locale: activeLocale,
+ fallbackLocale: DEFAULT_LOCALE,
+ messages: resolvedMessages
+ })
+
+ merginI18n = createI18n({
+ legacy: false,
+ locale: activeLocale,
+ fallbackLocale: DEFAULT_LOCALE,
+ messages: resolvedMessages,
+ missingWarn: false,
+ fallbackWarn: false
+ })
+
+ return merginI18n
+}
+
+export const getMerginI18n = () => merginI18n
diff --git a/web-app/packages/lang/runtime.ts b/web-app/packages/lang/runtime.ts
new file mode 100644
index 00000000..6f5f9384
--- /dev/null
+++ b/web-app/packages/lang/runtime.ts
@@ -0,0 +1,121 @@
+import {
+ createMerginI18n,
+ DEFAULT_LOCALE,
+ normalizeLocale,
+ type LocaleMessages
+} from './i18n'
+
+interface WeblateConfig {
+ baseUrl: string
+ project: string
+ component: string
+}
+
+interface ConfiguredLocale {
+ normalizedLocale: string
+}
+
+const DEFAULT_WEBLATE_TIMEOUT_MS = 1000
+
+const isMessagesMap = (value: unknown): value is LocaleMessages =>
+ typeof value === 'object' &&
+ value !== null &&
+ !Array.isArray(value) &&
+ Object.values(value).every((message) => typeof message === 'string')
+
+const getEnvString = (value: unknown) =>
+ typeof value === 'string' ? value.trim() : ''
+
+const getConfiguredLocale = (): ConfiguredLocale => {
+ const rawLocale = getEnvString(import.meta.env.VITE_LANG)
+
+ return {
+ normalizedLocale: normalizeLocale(rawLocale) ?? DEFAULT_LOCALE
+ }
+}
+
+const getWeblateTimeoutMs = () => {
+ const timeoutMs = Number(
+ getEnvString(import.meta.env.VITE_WEBLATE_TIMEOUT_MS)
+ )
+
+ return Number.isFinite(timeoutMs) && timeoutMs > 0
+ ? timeoutMs
+ : DEFAULT_WEBLATE_TIMEOUT_MS
+}
+
+const getWeblateBaseUrl = (configuredBaseUrl: string) => {
+ const baseUrl = configuredBaseUrl.replace(/\/+$/, '')
+
+ if (!baseUrl || baseUrl.startsWith('/') || /^https?:\/\//i.test(baseUrl)) {
+ return baseUrl
+ }
+
+ return `https://${baseUrl}`
+}
+
+const getWeblateConfig = (): WeblateConfig | undefined => {
+ const configuredBaseUrl = getEnvString(import.meta.env.VITE_WEBLATE_URL)
+ const baseUrl = getWeblateBaseUrl(configuredBaseUrl)
+ const project = getEnvString(import.meta.env.VITE_WEBLATE_PROJECT)
+ const component = getEnvString(import.meta.env.VITE_WEBLATE_COMPONENT)
+
+ if (!baseUrl || !project || !component) {
+ return undefined
+ }
+
+ return { baseUrl, project, component }
+}
+
+const buildWeblateFileUrl = (
+ { baseUrl, project, component }: WeblateConfig,
+ locale: string
+) =>
+ `${baseUrl}/api/translations/${encodeURIComponent(
+ project
+ )}/${encodeURIComponent(component)}/${encodeURIComponent(locale)}/file/`
+
+const fetchMessages = async (
+ locale: string,
+ config: WeblateConfig | undefined
+) => {
+ if (!config) {
+ return undefined
+ }
+
+ const controller = new AbortController()
+ const timeoutId = setTimeout(() => controller.abort(), getWeblateTimeoutMs())
+
+ try {
+ const response = await fetch(buildWeblateFileUrl(config, locale), {
+ signal: controller.signal
+ })
+
+ if (!response.ok) {
+ return undefined
+ }
+
+ const messages = await response.json()
+
+ if (!isMessagesMap(messages)) {
+ return undefined
+ }
+
+ return messages
+ } catch {
+ return undefined
+ } finally {
+ clearTimeout(timeoutId)
+ }
+}
+
+export const initializeRuntimeI18n = async () => {
+ const config = getWeblateConfig()
+ const { normalizedLocale } = getConfiguredLocale()
+ const requestedMessages = await fetchMessages(normalizedLocale, config)
+
+ return createMerginI18n({
+ locale: normalizedLocale,
+ messages: requestedMessages ? { [normalizedLocale]: requestedMessages } : {}
+ })
+}
diff --git a/web-app/packages/lang/translate.d.ts b/web-app/packages/lang/translate.d.ts
new file mode 100644
index 00000000..30603c6e
--- /dev/null
+++ b/web-app/packages/lang/translate.d.ts
@@ -0,0 +1,2 @@
+declare const returnTranslation: (_lang: string, key: string) => string;
+export default returnTranslation;
diff --git a/web-app/packages/lang/translate.ts b/web-app/packages/lang/translate.ts
new file mode 100644
index 00000000..92b0856c
--- /dev/null
+++ b/web-app/packages/lang/translate.ts
@@ -0,0 +1,27 @@
+import {
+ getLocalTranslation,
+ getMerginI18n,
+ getRuntimeTranslation
+} from './i18n'
+
+const returnTranslation = (_lang: string, key: string) => {
+ const i18n = getMerginI18n()
+
+ if (i18n) {
+ const translation = i18n.global.t(key)
+
+ if (typeof translation === 'string' && translation !== key) {
+ return translation
+ }
+ }
+
+ const runtimeTranslation = getRuntimeTranslation(key, _lang)
+
+ if (runtimeTranslation) {
+ return runtimeTranslation
+ }
+
+ return getLocalTranslation(key) ?? key
+}
+
+export default returnTranslation
diff --git a/web-app/packages/lang/translations/EN.d.ts b/web-app/packages/lang/translations/EN.d.ts
new file mode 100644
index 00000000..9aaf00f0
--- /dev/null
+++ b/web-app/packages/lang/translations/EN.d.ts
@@ -0,0 +1,396 @@
+declare const translations: {
+ ProfileHasBeenChanged: string;
+ FailedToChangeProfile: string;
+ FailedToFetchUsersProfile: string;
+ UnableToCloseAccount: string;
+ FailedToLogin: string;
+ FailedTologin: string;
+ EmailWithPasswordResetLinkWasSentToYourEmailAddress: string;
+ FailedToSendConfirmationEmail: string;
+ FailedToChangePassword: string;
+ EmailWasSentToAddress: string;
+ FailedToSendConfirmationEmailPleaseCheckYourAddressInUserProfileSettings: string;
+ PasswordHasBeenChanged: string;
+ FailedToLoadWorkspace: string;
+ FailedToLoadWorkspaces: string;
+ LoadingOfStoredUserWorkspaceIdHasFailed: string;
+ EmailConfirmation: string;
+ YourEmailAddressHasBeenVerified: string;
+ InvalidToken: string;
+ Continue: string;
+ AccountDetails: string;
+ EditAccount: string;
+ ChangePassword: string;
+ PleaseVerifyYourEmail: string;
+ WeSentYouAVerificationEmailToTheAccountYouProvidedDuringSignup: string;
+ SendConfirmationEmail: string;
+ FullName: string;
+ Registered: string;
+ Advanced: string;
+ ReceiveNotifications: string;
+ WeWillSendYouInformationAboutWorkspaceActivity: string;
+ CloseAccount: string;
+ YourAccountWillBeClosedInCaseYouAreAnOwnerOfAWorkspaceYouMightNeedToTransferTheOwnershipFirstOrCloseTheWorkspace: string;
+ ResetPassword: string;
+ SignIn: string;
+ Email: string;
+ BackToLogin: string;
+ UsernameOrEmail: string;
+ PleaseEnterUsernameOrEmail: string;
+ Password: string;
+ PleaseEnterYourPassword: string;
+ ForgotPassword: string;
+ NewPassword: string;
+ PasswordMustBeAtLeastCharactersLongAndIncludeAtLeastThreeOfTheFollowingLowercaseLettersUppercaseLettersNumbersOrSpecialCharacters: string;
+ ConfirmPassword: string;
+ PleaseEnterYourNewPassword: string;
+ Change: string;
+ YourPasswordWasChangedYouCanNow: string;
+ YouRequestedAccessToTheseProjects: string;
+ FirstName: string;
+ LastName: string;
+ Cancel: string;
+ SaveChanges: string;
+ PleaseNote: string;
+ DoYouReallyWishToCloseYourAccount: string;
+ ThisActionWillDeleteYourMerginMapsAccountIfYouAreAWorkspaceOwnerYouNeedToTransferTheOwnershipToSomebodyElseOrCloseTheWorkspace: string;
+ No: string;
+ Yes: string;
+ Username: string;
+ InOrderToDeleteYourAccountEnterYourUsernameInTheFieldAboveAndClickYes: string;
+ OldPassword: string;
+ MustBeAtLeastCharacters: string;
+ NoChangesDetectedFileAlreadyExists: string;
+ FailedToLoadProjects: string;
+ FailedToRemoveProject: string;
+ FailedToFetchProjectAccessRequests: string;
+ FailedToCancelAccessRequest: string;
+ FailedToLoadProjectData: string;
+ FailedToUnsubscribeFromProject: string;
+ FailedToLoadProjectAccessRequestsData: string;
+ FailedToFetchProjectVersions: string;
+ UnableToShareProjectWithTheFollowingUsers: string;
+ FollowingUsersHaveBeenAddedToTheProject: string;
+ UploadFinished: string;
+ FailedToGetProjectAccess: string;
+ FailedToGetProjectCollaborators: string;
+ FailedToUpdateProjectAccessForUser: string;
+ FailedToUpdateProjectAccess: string;
+ FailedToUpdateProjectCollaborator: string;
+ FailedToUpdatePublicFlag: string;
+ FailedToDisplayChangesetOfFile: string;
+ FailedToPushChanges: string;
+ Download: string;
+ Clone: string;
+ LeaveProject: string;
+ ThisIsAPrivateProject: string;
+ YouDontHavePermissionsToAccessThisProject: string;
+ RequestAccess: string;
+ ProjectNotFound: string;
+ PleaseCheckFfAddressIsWrittenCorrectly: string;
+ YouAlreadyRequestedAccess: string;
+ Files: string;
+ History: string;
+ Collaborators: string;
+ Settings: string;
+ AccessHasBeenRequested: string;
+ FailedToRequest: string;
+ AreYouSureToLeaveTheProject: string;
+ YouWillNotHaveAccessToItAnymore: string;
+ NewestVersions: string;
+ OldestVersions: string;
+ CreateProject: string;
+ SearchProjectsByName: string;
+ NamespaceNotFound: string;
+ PleaseCheckIfAddressIsWrittenCorrectly: string;
+ PublicProjects: string;
+ MyProjects: string;
+ SortByNameAZ: string;
+ SortByNameZA: string;
+ SortByLastUpdated: string;
+ SortByLastModified: string;
+ SortByFilesSize: string;
+ SortByFileSize: string;
+ NewProject: string;
+ ThisIsPublicProject: string;
+ ThisIsPrivateProject: string;
+ HideThisProjectFromEveryone: string;
+ MakeThisProjectVisibleToAnyone: string;
+ MakePrivate: string;
+ MakePublic: string;
+ DeleteProject: string;
+ AllDataWillBeLost: string;
+ AreYouSureToDeleteProject: string;
+ AllFilesWillBeLostTypeInProjectNameToConfirm: string;
+ Delete: string;
+ ProjectName: string;
+ ConfirmDeleteProject: string;
+ DoYouReallyWantToMakeThisProject: string;
+ Private: string;
+ Public: string;
+ Public2: string;
+ OnceYouMakeYourProjectPrivateItCanNotBeAccessedByTheCommunity: string;
+ OnceYouMakeYourProjectPublicItCanBeAccessedByTheCommunity: string;
+ PrivateProject: string;
+ PublicProject: string;
+ Requests: string;
+ SearchMembers: string;
+ Share: string;
+ NoChangesetForCurrentLayer: string;
+ ChangesCannotBeCalculated: string;
+ ForDetailsPleaseCheckThe: string;
+ Documentation: string;
+ Documentation2: string;
+ SearchFiles: string;
+ UploadFiles: string;
+ Recommended: string;
+ MerginMapsPluginForQGIS: string;
+ ThisIsTheEasiestAndRecommendedWay: string;
+ LearnHowToUseIt: string;
+ Version: string;
+ Author: string;
+ ProjectSize: string;
+ Created: string;
+ UserAgent: string;
+ FailedToFetchProjectVersion: string;
+ UploadingDataToProjects: string;
+ DataSync: string;
+ UpdateChanges: string;
+ OperationCanceledByTheUser: string;
+ ProjectUpdated: string;
+ AreYouReallySureYouWantToContinue: string;
+ ChangesFromOtherUsersMayGetLostWhenUploadingDataFromBrowserItIsHighlyRecommendedToUseMerginMapsQGISPluginInstead: string;
+ Update: string;
+ ConfirmContinueUpload: string;
+ Versions: string;
+ NoVersionsFound: string;
+ FilesAdded: string;
+ FilesEdited: string;
+ FilesRemoved: string;
+ Size: string;
+ ShowAdvanced: string;
+ DetailsNotAvailable: string;
+ WeCouldntFindAnyProjectsMatchingYourSearchCriteria: string;
+ YouDontHaveAnyProjectsYet: string;
+ LetsStartByCreatingAFirstOne: string;
+ CreateNewProject: string;
+ FailedToFetchListOfProjects: string;
+ FailedToFindAQGISProjectFile: string;
+ ConflictingFileInProject: string;
+ Updated: string;
+ ProjectPermission: string;
+ LearnMoreAboutPermissionSystem: string;
+ ShareWith: string;
+ SearchUsersByUsernameOrEmail: string;
+ NoMatchesFoundTryUsingTheirEmailsInstead: string;
+ NotTheRightPersonTryTypingTheirRmailInstead: string;
+ NoMembersFound: string;
+ Members: string;
+ Me: string;
+ EmailAddress: string;
+ ProjectPermissions: string;
+ Remove: string;
+ AGoodCandidateForAProjectNameIsNameOfTheLocationOrPurposeOfTheFieldSurvey: string;
+ AccessRequests: string;
+ User: string;
+ RequestedAnAccessToYourProject: string;
+ Expired: string;
+ ExpiringIn: string;
+ Disallow: string;
+ Accept: string;
+ NoAccessRequestsFound: string;
+ FailedToAcceptAccessRequest: string;
+ NoFilesFound: string;
+ Name: string;
+ Modified: string;
+ File: string;
+ New: string;
+ DeleteFile: string;
+ NewFile: string;
+ DeletedFile: string;
+ ModifiedFile: string;
+ FirstPageLinkPrevPageLinkPageLinksNextPageLinkLastPageLink: string;
+ Layer: string;
+ Inserts: string;
+ Updates: string;
+ Deletes: string;
+ DragAndDropFiles: string;
+ YouCanDropFilesFromYourComputerToStartUploading: string;
+ YouCannotUpdateFilesDuringUpload: string;
+ DropOnlyFilesOrFolders: string;
+ BrowseCommunityProjects: string;
+ CommunityProjects: string;
+ ExploreVariousTemplateProjectsWithinOurCommunity: string;
+ CloneProject: string;
+ YouRequestedAnAccessToProject: string;
+ InWorkspace: string;
+ AdminPanel: string;
+ Dashboard: string;
+ Projects: string;
+ YourProfile: string;
+ SignOut: string;
+ CommunityChat: string;
+ FailedToInitApplication: string;
+ FailedToFetchPingData: string;
+ FailedToFetchConfigData: string;
+ TheServiceIsCurrentlyInReadOnlyModeForMaintenanceUploadAndUpdateFunctionsAreNotAvailableAtThisTimePleaseTryAgainLater: string;
+ Action: string;
+ Ok: string;
+ RecentActiveProjects: string;
+ DownloadMerginMapsToYourPhone: string;
+ CaptureGeoInfoEasilyThroughYourMobileTabletWithOurMobileApp: string;
+ CloseSection: string;
+ Reader: string;
+ Editor: string;
+ Writer: string;
+ Owner: string;
+ CanViewProjectFiles: string;
+ CanCollectFeaturesInProject: string;
+ CanEditProjectFiles: string;
+ CanShareAndRemoveProject: string;
+ Year: string;
+ Years: string;
+ Month: string;
+ Months: string;
+ Week: string;
+ Weeks: string;
+ Day: string;
+ Days: string;
+ Hour: string;
+ Hours: string;
+ Minute: string;
+ Minutes: string;
+ Ago: string;
+ TipFromMerginMaps: string;
+ YourStorageIsAlmostFull: string;
+ SoonYouWillNotBeAbleToSyncYourProjects: string;
+ PasswordMustBeAtLeastCharactersLong: string;
+ PasswordMustContainAtLeastCharacterCategoriesAmongTheFollowing: string;
+ LowercaseCharactersAZ: string;
+ UppercaseCharactersAZ: string;
+ Digits09: string;
+ SpecialCharacters: string;
+ NoDataAvailable: string;
+ StoreAndTrackChangesToYourGeoDataMerginMapsIsARepositoryOfGeoDataForCollaborativeWork: string;
+ FailedToCloneProject: string;
+ ShareProject: string;
+ FailedToCreateProject: string;
+ FailedToSaveProjectSettings: string;
+ FailedToFetchUserProfile: string;
+ UnableToPermanentlyRemoveAccount: string;
+ FailedToFetchProjects: string;
+ FailedToRestoreProject: string;
+ ProjectRemovedSuccessfully: string;
+ UnableToRemoveProject: string;
+ CheckForUpdates: string;
+ LetMerginMapsAutomaticallyCheckForNewUpdates: string;
+ ServerUsageReport: string;
+ DownloadUsageStatisticsForYourServerDeployment: string;
+ DownloadReport: string;
+ Click: string;
+ Here: string;
+ ForMoreInformation: string;
+ CollectStatistics: string;
+ HelpUsImproveMerginMapsBySharingUsageInformationMerginMapsCollectsAnonymousUsageInformationToMakeTheServiceBetterOvertime: string;
+ ProjectDetails: string;
+ OpenInDashboard: string;
+ DiskUsage: string;
+ ProjectVersion: string;
+ TheProjectWillBeVisibleToEveryoneIfItIsMarkedAsPublic: string;
+ DeletingThisProjectWillRemoveItAndAllItsDataThisActionCannotBeUndone: string;
+ AreYouSureYouWantToPermanentlyDeleteThisProject: string;
+ DeletingThisProjectWillRemoveItAndAllItsDataThisActionCannotBeUndoneTypeInProjectNameToConfirm: string;
+ DeletePermanently: string;
+ Overview: string;
+ Contributors: string;
+ UsedStorage: string;
+ RegisteredAccounts: string;
+ ManageUsers: string;
+ ManageProjects: string;
+ Workspaces: string;
+ ManageWorkspaces: string;
+ EmailVerificationStatus: string;
+ GrantAdminAccess: string;
+ RevokeAdminAccess: string;
+ DeactivateAccount: string;
+ ActivateAccount: string;
+ DeleteAccount: string;
+ UserHasEnabledReceivingNotifications: string;
+ UserHasDisabledNotifications: string;
+ AccessToAdminPanel: string;
+ UserHasAccessToTheAdminPanel: string;
+ UserDoesNotHaveAccessToTheAdminPanel: string;
+ AccountActivation: string;
+ TheUsersAccountIsCurrentlyActiveDeactivationWillLeadToATemporaryBanFromMerginMapsUsage: string;
+ TheUsersAccountIsCurrentlyInactiveActivatingItWillAllowAccessToMerginMaps: string;
+ DeletingThisUserWillRemoveThemAndAllTheirDataThisActionCannotBeUndone: string;
+ Deactivate: string;
+ DoYouReallyWantDeactivateThisAccount: string;
+ DeactivatingThisAccountWillLeadToATemporaryBanFromMerginMapsUsage: string;
+ DoYouReallyWantActivateThisAccount: string;
+ Activate: string;
+ UserActivation: string;
+ AreYouSureYouWantToPermanentlyDeleteThisAccount: string;
+ DeletingThisUserWillRemoveThemAndAllTheirDataThisActionCannotBeUndoneTypeInUsernameToConfirm: string;
+ DeleteUser: string;
+ AreYouSureToGrantAccessToAdminPanelToThisUser: string;
+ ThisPersonWillHaveFullManagementAccessToAllDataOnTheServerTheyWillSeeAllUsersAndProjectsAndCanUpdateOrRemoveThem: string;
+ GrantAccess: string;
+ AreYouSureYouWantToRevokeAccessToAdminPanelToThisUser: string;
+ ThisPersonWillNoLongerHaveAccessToTheAdminPanel: string;
+ RevokeAccess: string;
+ AdminAccess: string;
+ ServerIsNotProperlyConfigured: string;
+ YourServerIsNotConfiguredProperlyForUseInTheProductionEnvironmentReadMoreInThe: string;
+ HowToProperlySetUpTheDeployment: string;
+ Dismiss: string;
+ Period: string;
+ CustomRange: string;
+ SelectDateRange: string;
+ Last3months: string;
+ Last6months: string;
+ Last12months: string;
+ Create: string;
+ UserCreated: string;
+ FailedToCreateUser: string;
+ UpdateAvailable: string;
+ ANewVersionOfMerginMapsIsAvailableForUsersLetsExploreItsNewFeatures: string;
+ SearchByWorkspaceNameOrProjectName: string;
+ SearchProjects: string;
+ ScheduledForRemovalAt: string;
+ Restore: string;
+ Workspace: string;
+ LastUpdate: string;
+ ScheduledRemovalAt: string;
+ RemovedBy: string;
+ AreYouSureToRestoreProject: string;
+ RestoreProject: string;
+ SearchAccounts: string;
+ Active: string;
+ CreateUser: string;
+ Accounts: string;
+ TypeYourEmail: string;
+ AddUser: string;
+ Added: string;
+ Removed: string;
+ AddedL: string;
+ UpdatedL: string;
+ RemovedL: string;
+ EnterPassword: string;
+ year: string;
+ years: string;
+ month: string;
+ months: string;
+ week: string;
+ weeks: string;
+ day: string;
+ days: string;
+ hour: string;
+ hours: string;
+ minute: string;
+ minutes: string;
+ ago: string;
+ expired: string;
+ InvalidEmailAddress: string;
+};
+export default translations;
diff --git a/web-app/packages/lang/translations/EN.ts b/web-app/packages/lang/translations/EN.ts
new file mode 100644
index 00000000..ad511f15
--- /dev/null
+++ b/web-app/packages/lang/translations/EN.ts
@@ -0,0 +1,412 @@
+const translations = {
+ ProfileHasBeenChanged: 'Profile has been changed',
+ FailedToChangeProfile: 'Failed to change profile',
+ FailedToFetchUsersProfile: "Failed to fetch user's profile",
+ UnableToCloseAccount: 'Unable to close account',
+ FailedToLogin: 'Failed to login',
+ FailedTologin: 'Failed to login',
+ EmailWithPasswordResetLinkWasSentToYourEmailAddress: 'Email with password reset link was sent to your email address',
+ FailedToSendConfirmationEmail: 'Failed to send confirmation email',
+ FailedToChangePassword: 'Failed to change password',
+ EmailWasSentToAddress: 'Email was sent to address',
+ FailedToSendConfirmationEmailPleaseCheckYourAddressInUserProfileSettings: 'Failed to send confirmation email, please check your address in user profile settings',
+ PasswordHasBeenChanged: 'Password has been changed',
+ FailedToLoadWorkspace: 'Failed to load workspace',
+ FailedToLoadWorkspaces: 'Failed to load workspaces',
+ LoadingOfStoredUserWorkspaceIdHasFailed: 'Loading of stored user workspace id has failed.',
+ EmailConfirmation: 'Email confirmation',
+ YourEmailAddressHasBeenVerified: 'Your email address has been verified.',
+ InvalidToken: 'Invalid token',
+ Continue: 'Continue',
+ AccountDetails: 'Account details',
+ LastSignedIn: 'Last signed in',
+ EditAccount: 'Edit account',
+ ChangePassword: 'Change password',
+ PleaseVerifyYourEmail: 'Please verify your email',
+ WeSentYouAVerificationEmailToTheAccountYouProvidedDuringSignup: 'We sent you a verification email to the account you provided during signup.',
+ SendConfirmationEmail: 'Send confirmation email',
+ FullName: 'Full name',
+ Registered: 'Registered',
+ Advanced: 'Advanced',
+ ReceiveNotifications: 'Receive notifications',
+ WeWillSendYouInformationAboutWorkspaceActivity: 'We will send you information about workspace activity',
+ CloseAccount: 'Close account',
+ YourAccountWillBeClosedInCaseYouAreAnOwnerOfAWorkspaceYouMightNeedToTransferTheOwnershipFirstOrCloseTheWorkspace: 'Your account will be closed. In case you are an owner of a workspace, you might need to transfer the ownership first or close the workspace.',
+ ResetPassword: 'Reset password',
+ SignIn: 'Sign in',
+ Email: 'Email',
+ BackToLogin: 'Back to login',
+ UsernameOrEmail: 'Username or email',
+ PleaseEnterUsernameOrEmail: 'Please enter username or email',
+ Password: 'Password',
+ PleaseEnterYourPassword: 'Please enter your password',
+ ForgotPassword: 'Forgot password?',
+ NewPassword: 'New Password',
+ PasswordMustBeAtLeastCharactersLongAndIncludeAtLeastThreeOfTheFollowingLowercaseLettersUppercaseLettersNumbersOrSpecialCharacters: 'Password must be at least 8 characters long and include at least three of the following: lowercase letters, uppercase letters, numbers or special characters.',
+ ConfirmPassword: 'Confirm password',
+ PleaseEnterYourNewPassword: 'Please enter your new password',
+ Change: 'Change',
+ YourPasswordWasChangedYouCanNow: 'Your password was changed. You can now',
+ YouRequestedAccessToTheseProjects: 'You requested access to these projects',
+ FirstName: 'First name',
+ LastName: 'Last name',
+ Cancel: 'Cancel',
+ SaveChanges: 'Save changes',
+ PleaseNote: 'Please note',
+ DoYouReallyWishToCloseYourAccount: 'Do you really wish to close your account?',
+ ThisActionWillDeleteYourMerginMapsAccountIfYouAreAWorkspaceOwnerYouNeedToTransferTheOwnershipToSomebodyElseOrCloseTheWorkspace: 'This action will delete your Mergin Maps account. If you are a workspace owner, you need to transfer the ownership to somebody else or close the workspace.',
+ No: 'No',
+ Yes: 'Yes',
+ Username: 'Username',
+ InOrderToDeleteYourAccountEnterYourUsernameInTheFieldAboveAndClickYes: 'In order to delete your account, enter your username in the field above and click Yes.',
+ OldPassword: 'Old password',
+ MustBeAtLeastCharacters: 'Must be at least 8 characters',
+ NoChangesDetectedFileAlreadyExists: 'No changes detected. File already exists?',
+ FailedToLoadProjects: 'Failed to load projects',
+ FailedToRemoveProject: 'Failed to remove project',
+ FailedToFetchProjectAccessRequests: 'Failed to fetch project access requests',
+ FailedToCancelAccessRequest: 'Failed to cancel access request',
+ FailedToLoadProjectData: 'Failed to load project data',
+ FailedToUnsubscribeFromProject: 'Failed to unsubscribe from project',
+ FailedToLoadProjectAccessRequestsData: 'Failed to load project access requests data',
+ FailedToFetchProjectVersions: 'Failed to fetch project versions',
+ UnableToShareProjectWithTheFollowingUsers: 'Unable to share project with the following users',
+ FollowingUsersHaveBeenAddedToTheProject: 'Following users have been added to the project',
+ UploadFinished: 'Upload finished',
+ FailedToGetProjectAccess: 'Failed to get project access',
+ FailedToGetProjectCollaborators: 'Failed to get project collaborators',
+ FailedToUpdateProjectAccessForUser: 'Failed to update project access for user',
+ FailedToUpdateProjectAccess: 'Failed to update project access',
+ FailedToUpdateProjectCollaborator: 'Failed to update project collaborator',
+ FailedToUpdatePublicFlag: 'Failed to update public flag',
+ FailedToDisplayChangesetOfFile: 'Failed to display changeset of file',
+ FailedToPushChanges: 'Failed to push changes',
+ Download: 'Download',
+ Clone: 'Clone',
+ LeaveProject: 'Leave project',
+ ThisIsAPrivateProject: 'This is a private project',
+ YouDontHavePermissionsToAccessThisProject: "You don't have permissions to access this project.",
+ RequestAccess: 'Request access',
+ ProjectNotFound: 'Project not found',
+ PleaseCheckFfAddressIsWrittenCorrectly: 'Please check if address is written correctly',
+ YouAlreadyRequestedAccess: 'You already requested access',
+ Files: 'Files',
+ History: 'History',
+ Collaborators: 'Collaborators',
+ Settings: 'Settings',
+ AccessHasBeenRequested: 'Access has been requested',
+ FailedToRequest: 'Failed to request',
+ AreYouSureToLeaveTheProject: 'Are you sure to leave the project',
+ YouWillNotHaveAccessToItAnymore: 'You will not have access to it anymore.',
+ NewestVersions: 'Newest versions',
+ OldestVersions: 'Oldest versions',
+ CreateProject: 'Create project',
+ SearchProjectsByName: 'Search projects by name',
+ NamespaceNotFound: 'Namespace not found',
+ PleaseCheckIfAddressIsWrittenCorrectly: 'Please check if address is written correctly',
+ PublicProjects: 'Public projects',
+ MyProjects: 'My projects',
+ SortByNameAZ: 'Sort by name A-Z',
+ SortByNameZA: 'Sort by name Z-A',
+ SortByLastUpdated: 'Sort by last updated',
+ SortByLastModified: 'Sort by last modified',
+ SortByFilesSize: 'Sort by files size',
+ SortByFileSize: 'Sort by file size',
+ NewProject: 'New project',
+ ThisIsPublicProject: 'This is public project',
+ ThisIsPrivateProject: 'This is private project',
+ HideThisProjectFromEveryone: 'Hide this project from everyone',
+ MakeThisProjectVisibleToAnyone: 'Make this project visible to anyone.',
+ MakePrivate: 'Make private',
+ MakePublic: 'Make public',
+ DeleteProject: 'Delete project',
+ AllDataWillBeLost: 'All data will be lost',
+ AreYouSureToDeleteProject: 'Are you sure to delete project?',
+ AllFilesWillBeLostTypeInProjectNameToConfirm: 'All files will be lost. Type in project name to confirm:',
+ Delete: 'Delete',
+ ProjectName: 'Project name',
+ ConfirmDeleteProject: 'Confirm delete project',
+ DoYouReallyWantToMakeThisProject: 'Do you really want to make this project',
+ Private: 'private',
+ Public: 'public',
+ Public2: 'public',
+ OnceYouMakeYourProjectPrivateItCanNotBeAccessedByTheCommunity: 'Once you make your project private it can not be accessed by the community.',
+ OnceYouMakeYourProjectPublicItCanBeAccessedByTheCommunity: 'Once you make your project public it can be accessed by the community.',
+ PrivateProject: 'Private project',
+ PublicProject: 'Public project',
+ Requests: 'Requests',
+ SearchMembers: 'Search members',
+ Share: 'Share',
+ NoChangesetForCurrentLayer: 'No changeset for current layer',
+ ChangesCannotBeCalculated: 'Changes cannot be calculated',
+ ForDetailsPleaseCheckThe: 'For details please check the',
+ Documentation: 'documentation',
+ Documentation2: 'documentation',
+ SearchFiles: 'Search files',
+ UploadFiles: 'Upload files',
+ Recommended: 'Recommended',
+ MerginMapsPluginForQGIS: 'Mergin Maps plugin for QGIS',
+ ThisIsTheEasiestAndRecommendedWay: 'This is the easiest and recommended way.',
+ LearnHowToUseIt: 'Learn how to use it.',
+ Version: 'Version',
+ Author: 'Author',
+ ProjectSize: 'Project size',
+ Created: 'Created',
+ UserAgent: 'User agent',
+ FailedToFetchProjectVersion: 'Failed to fetch project version',
+ UploadingDataToProjects: 'Uploading data to projects',
+ DataSync: 'Data Sync',
+ UpdateChanges: 'Update Changeso',
+ OperationCanceledByTheUser: 'Operation canceled by the user.',
+ ProjectUpdated: 'Project updated',
+ AreYouReallySureYouWantToContinue: 'Are you really sure you want to continue?',
+ ChangesFromOtherUsersMayGetLostWhenUploadingDataFromBrowserItIsHighlyRecommendedToUseMerginMapsQGISPluginInstead: 'Changes from other users may get lost when uploading data from browser. It is highly recommended to use Mergin Maps QGIS plugin instead.',
+ Update: 'Update',
+ ConfirmContinueUpload: 'Confirm continue upload',
+ CoverForConfirmDialog: 'Cover for confirm dialog',
+ Versions: 'Versions',
+ NoVersionsFound: 'No versions found.',
+ FilesAdded: 'Files added',
+ FilesEdited: 'Files edited',
+ FilesRemoved: 'Files removed',
+ Size: 'Size',
+ ShowAdvanced: 'Show advanced',
+ DetailsNotAvailable: 'Details not available:',
+ FileTooLargeToPreviewDownloadToViewInstead: 'File too large to preview — download to view instead.',
+ ReadOurBlogForMoreDetails: 'Read our blog for more details',
+ TheProjectIsTooLargeToDownloadPleaseUseDirectDownloadWithOurPythonAPIClientOrQGISPluginInstead: 'The project is too large to download. Please use direct download with our Python API Client or QGIS plugin instead.',
+ TypeInProjectNameToConfirmDeletion: 'Type in project name to confirm deletion',
+ TypeInValueToConfirm: 'Type in value to confirm',
+ WeCouldntFindAnyProjectsMatchingYourSearchCriteria: "We couldn't find any projects matching your search criteria.",
+ YouDontHaveAnyProjectsYet: "You don't have any projects yet.",
+ LetsStartByCreatingAFirstOne: 'Let’s start by creating a first one!',
+ CreateNewProject: 'Create new project',
+ FailedToFetchListOfProjects: 'Failed to fetch list of projects',
+ FailedToFindAQGISProjectFile: 'Failed to find a QGIS project file',
+ ConflictingFileInProject: 'Conflicting file in project',
+ Updated: 'Updated',
+ ProjectPermission: 'Project permission',
+ LearnMoreAboutPermissionSystem: 'Learn more about permission system',
+ ShareWith: 'Share with',
+ SearchUsersByUsernameOrEmail: 'Search users by username or email',
+ NoMatchesFoundTryUsingTheirEmailsInstead: 'No matches found - Try using their emails instead',
+ NotTheRightPersonTryTypingTheirRmailInstead: 'Not the right person? Try typing their email instead',
+ NoMembersFound: 'No members found.',
+ Members: 'Members',
+ Me: '(me)',
+ EmailAddress: 'Email address',
+ ProjectPermissions: 'Project permissions',
+ Remove: 'Remove',
+ AGoodCandidateForAProjectNameIsNameOfTheLocationOrPurposeOfTheFieldSurvey: 'A good candidate for a project name is name of the location or purpose of the field survey.',
+ AccessRequests: 'Access requests',
+ User: 'User',
+ RequestedAnAccessToYourProject: 'requested an access to your project',
+ Expired: 'Expired',
+ ExpiringIn: 'Expiring in',
+ Disallow: 'Disallow',
+ Accept: 'Accept',
+ NoAccessRequestsFound: 'No access requests found',
+ FailedToAcceptAccessRequest: 'Failed to accept access request',
+ NoFilesFound: 'No files found.',
+ Name: 'Name',
+ Modified: 'Modified',
+ File: 'File',
+ New: 'new:',
+ DeleteFile: 'Delete file',
+ NewFile: 'New file',
+ DeletedFile: 'Deleted file',
+ ModifiedFile: 'Modified file',
+ FirstPageLinkPrevPageLinkPageLinksNextPageLinkLastPageLink: 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink',
+ Layer: 'Layer',
+ Inserts: 'Inserts',
+ Updates: 'Updates',
+ Deletes: 'Deletes',
+ DragAndDropFiles: 'Drag and drop files',
+ YouCanDropFilesFromYourComputerToStartUploading: 'You can drop files from your computer to start uploading',
+ YouCannotUpdateFilesDuringUpload: 'You cannot update files during upload',
+ DropOnlyFilesOrFolders: 'Drop only files or folders',
+ BrowseCommunityProjects: 'Browse community projects',
+ CommunityProjects: 'Community projects',
+ ExploreVariousTemplateProjectsWithinOurCommunity: 'Explore various template projects within our community.',
+ CloneProject: 'Clone project',
+ YouRequestedAnAccessToProject: 'You requested an access to project',
+ InWorkspace: 'in workspace',
+ AdminPanel: 'Admin Panel',
+ MerginMapsAdminPanel: 'Mergin Maps Admin Panel',
+ Dashboard: 'Dashboard',
+ Projects: 'Projects',
+ YourProfile: 'Your profile',
+ SignOut: 'Sign out',
+ CommunityChat: 'Community chat',
+ FailedToInitApplication: 'Failed to init application.',
+ FailedToFetchPingData: 'Failed to fetch ping data.',
+ FailedToFetchConfigData: 'Failed to fetch config data.',
+ TheServiceIsCurrentlyInReadOnlyModeForMaintenanceUploadAndUpdateFunctionsAreNotAvailableAtThisTimePleaseTryAgainLater: 'The service is currently in read-only mode for maintenance. Upload and update functions are not available at this time. Please try again later.',
+ Action: 'Action',
+ Ok: 'Ok',
+ BackToDashboard: 'Back to Dashboard',
+ Bulb: 'Bulb',
+ Exclamation: 'Exclamation',
+ Member: 'Member',
+ NoWorkspace: 'no workspace',
+ OoopsItSeemsTheGPSHasLostItsWay: 'Ooops, it seems the GPS has lost its way.',
+ ThisPageDoesNotExistCheckYourUrlForMistakesPlease: 'This page does not exist, check your url for mistakes, please.',
+ RecentActiveProjects: 'Recent active projects',
+ DownloadMerginMapsToYourPhone: 'Download Mergin Maps to your phone',
+ CaptureGeoInfoEasilyThroughYourMobileTabletWithOurMobileApp: 'Capture geo-info easily through your mobile/tablet with our mobile app.',
+ CloseSection: 'Close section',
+ Reader: 'Reader',
+ Editor: 'Editor',
+ Writer: 'Writer',
+ Owner: 'Owner',
+ CanViewProjectFiles: 'Can view project files',
+ CanCollectFeaturesInProject: 'Can collect features in project',
+ CanEditProjectFiles: 'Can edit project files',
+ CanShareAndRemoveProject: 'Can share and remove project',
+ Year: 'year',
+ Years: 'years',
+ Month: 'month',
+ Months: 'months',
+ Week: 'week',
+ Weeks: 'weeks',
+ Day: 'day',
+ Days: 'days',
+ Hour: 'hour',
+ Hours: 'hours',
+ Minute: 'minute',
+ Minutes: 'minutes',
+ Ago: 'ago',
+ TipFromMerginMaps: 'Tip from Mergin Maps',
+ YourStorageIsAlmostFull: 'Your storage is almost full',
+ SoonYouWillNotBeAbleToSyncYourProjects: 'Soon you will not be able to sync your projects.',
+ PasswordMustBeAtLeastCharactersLong: 'Password must be at least 8 characters long.',
+ PasswordMustContainAtLeastCharacterCategoriesAmongTheFollowing: 'Password must contain at least 3 character categories among the following:',
+ LowercaseCharactersAZ: 'Lowercase characters (a-z)',
+ UppercaseCharactersAZ: 'Uppercase characters (A-Z)',
+ Digits09: 'Digits (0-9)',
+ SpecialCharacters: 'Special characters',
+ NoDataAvailable: 'No data available',
+ StoreAndTrackChangesToYourGeoDataMerginMapsIsARepositoryOfGeoDataForCollaborativeWork: 'Store and track changes to your geo-data. Mergin Maps is a repository of geo-data for collaborative work.',
+ FailedToCloneProject: 'Failed to clone project',
+ ShareProject: 'Share project',
+ FailedToCreateProject: 'Failed to create project.',
+ FailedToSaveProjectSettings: 'Failed to save project settings',
+ FailedToFetchUserProfile: 'Failed to fetch user profile',
+ UnableToPermanentlyRemoveAccount: 'Unable to permanently remove account',
+ FailedToFetchProjects: 'Failed to fetch projects',
+ FailedToRestoreProject: 'Failed to restore project',
+ ProjectRemovedSuccessfully: 'Project removed successfully',
+ UnableToRemoveProject: 'Unable to remove project',
+ CheckForUpdates: 'Check for updates',
+ LetMerginMapsAutomaticallyCheckForNewUpdates: 'Let Mergin Maps automatically check for new updates',
+ ServerUsageReport: 'Server usage report',
+ DownloadUsageStatisticsForYourServerDeployment: 'Download usage statistics for your server deployment.',
+ DownloadReport: 'Download report',
+ Click: 'Click',
+ Here: 'here',
+ ForMoreInformation: 'for more information.',
+ CollectStatistics: 'Collect statistics',
+ HelpUsImproveMerginMapsBySharingUsageInformationMerginMapsCollectsAnonymousUsageInformationToMakeTheServiceBetterOvertime: 'Help us improve Mergin Maps by sharing usage information. Mergin Maps collects anonymous usage information to make the service better overtime.',
+ ProjectDetails: 'Project details',
+ OpenInDashboard: 'Open in dashboard',
+ DiskUsage: 'Disk usage',
+ ProjectVersion: 'Project version',
+ TheProjectWillBeVisibleToEveryoneIfItIsMarkedAsPublic: 'The project will be visible to everyone if it is marked as public.',
+ DeletingThisProjectWillRemoveItAndAllItsDataThisActionCannotBeUndone: 'Deleting this project will remove it and all its data. This action cannot be undone.',
+ AreYouSureYouWantToPermanentlyDeleteThisProject: 'Are you sure you want to permanently delete this project?',
+ DeletingThisProjectWillRemoveItAndAllItsDataThisActionCannotBeUndoneTypeInProjectNameToConfirm: 'Deleting this project will remove it and all its data. This action cannot be undone. Type in project name to confirm:',
+ DeletePermanently: 'Delete permanently',
+ Overview: 'Overview',
+ Contributors: 'Contributors',
+ UsedStorage: 'Used storage',
+ RegisteredAccounts: 'Registered accounts',
+ ManageUsers: 'Manage users',
+ ManageProjects: 'Manage projects',
+ Workspaces: 'Workspaces',
+ ManageWorkspaces: 'Manage workspaces',
+ EmailVerificationStatus: 'Email verification status',
+ GrantAdminAccess: 'Grant admin access',
+ RevokeAdminAccess: 'Revoke admin access',
+ DeactivateAccount: 'Deactivate account',
+ ActivateAccount: 'Activate account',
+ DeleteAccount: 'Delete account',
+ UserHasEnabledReceivingNotifications: 'User has enabled receiving notifications',
+ UserHasDisabledNotifications: 'User has disabled notifications.',
+ AccessToAdminPanel: 'Access to admin panel',
+ UserHasAccessToTheAdminPanel: 'User has access to the admin panel.',
+ UserDoesNotHaveAccessToTheAdminPanel: 'User does not have access to the admin panel.',
+ AccountActivation: 'Account activation',
+ TheUsersAccountIsCurrentlyActiveDeactivationWillLeadToATemporaryBanFromMerginMapsUsage: "The user's account is currently active. Deactivation will lead to a temporary ban from Mergin Maps usage.",
+ TheUsersAccountIsCurrentlyInactiveActivatingItWillAllowAccessToMerginMaps: "The user's account is currently inactive. Activating it will allow access to Mergin Maps.",
+ DeletingThisUserWillRemoveThemAndAllTheirDataThisActionCannotBeUndone: 'Deleting this user will remove them and all their data. This action cannot be undone.',
+ Deactivate: 'Deactivate',
+ DoYouReallyWantDeactivateThisAccount: 'Do you really want deactivate this account?',
+ DeactivatingThisAccountWillLeadToATemporaryBanFromMerginMapsUsage: 'Deactivating this account will lead to a temporary ban from Mergin Maps usage.',
+ DoYouReallyWantActivateThisAccount: 'Do you really want activate this account?',
+ Activate: 'Activate',
+ UserActivation: 'User activation',
+ AreYouSureYouWantToPermanentlyDeleteThisAccount: 'Are you sure you want to permanently delete this account?',
+ DeletingThisUserWillRemoveThemAndAllTheirDataThisActionCannotBeUndoneTypeInUsernameToConfirm: 'Deleting this user will remove them and all their data. This action cannot be undone. Type in username to confirm:',
+ DeleteUser: 'Delete user',
+ AreYouSureToGrantAccessToAdminPanelToThisUser: 'Are you sure to grant access to admin panel to this user?',
+ ThisPersonWillHaveFullManagementAccessToAllDataOnTheServerTheyWillSeeAllUsersAndProjectsAndCanUpdateOrRemoveThem: 'This person will have full management access to all data on the server. They will see all users and projects and can update or remove them.',
+ GrantAccess: 'Grant access',
+ AreYouSureYouWantToRevokeAccessToAdminPanelToThisUser: 'Are you sure you want to revoke access to admin panel to this user?',
+ ThisPersonWillNoLongerHaveAccessToTheAdminPanel: 'This person will no longer have access to the admin panel.',
+ RevokeAccess: 'Revoke access',
+ AdminAccess: 'Admin access',
+ ServerIsNotProperlyConfigured: 'Server is not properly configured',
+ YourServerIsNotConfiguredProperlyForUseInTheProductionEnvironmentReadMoreInThe: 'Your server is not configured properly for use in the production environment. Read more in the',
+ HowToProperlySetUpTheDeployment: 'how to properly set up the deployment.',
+ Dismiss: 'Dismiss',
+ Period: 'Period',
+ CustomRange: 'Custom range',
+ SelectDateRange: 'Select date range',
+ Last3months: 'Last 3 months',
+ Last6months: 'Last 6 months',
+ Last12months: 'Last 12 months',
+ Create: 'Create',
+ UserCreated: 'User created',
+ FailedToCreateUser: 'Failed to create user',
+ UpdateAvailable: 'Update available 🎉!',
+ ANewVersionOfMerginMapsIsAvailableForUsersLetsExploreItsNewFeatures: "A new version of Mergin Maps is available for users. Let's explore its new features.",
+ SearchByWorkspaceNameOrProjectName: 'Search by workspace name or project name',
+ SearchProjects: 'Search projects',
+ ScheduledForRemovalAt: 'Scheduled for removal at',
+ Restore: 'Restore',
+ Workspace: 'Workspace',
+ LastUpdate: 'Last Update',
+ ScheduledRemovalAt: 'Scheduled removal at',
+ RemovedBy: 'Removed by',
+ AreYouSureToRestoreProject: 'Are you sure to restore project',
+ RestoreProject: 'Restore project',
+ SearchAccounts: 'Search accounts',
+ Active: 'Active',
+ CreateUser: 'Create user',
+ Accounts: 'Accounts',
+ TypeYourEmail: 'Type your email',
+ AddUser: 'Add user',
+ Added: 'Added',
+ Removed: 'Removed',
+ AddedL: 'added',
+ UpdatedL: 'updated',
+ RemovedL: 'removed',
+ EnterPassword: 'Enter password',
+ year: 'year',
+ years: 'years',
+ month: 'month',
+ months: 'months',
+ week: 'week',
+ weeks: 'weeks',
+ day: 'day',
+ days: 'days',
+ hour: 'hour',
+ hours: 'hours',
+ minute: 'minute',
+ minutes: 'minutes',
+ ago: 'ago',
+ expired: 'expired',
+ InvalidEmailAddress: 'Invalid email address.',
+}
+
+export default translations
diff --git a/web-app/packages/lib/package.json b/web-app/packages/lib/package.json
index 101c4cc2..bae1b101 100644
--- a/web-app/packages/lib/package.json
+++ b/web-app/packages/lib/package.json
@@ -48,6 +48,7 @@
"primeflex": "^3.3.1",
"primevue": "3.43.0",
"vue": "3.5.12",
+ "vue-i18n": "11",
"vue-router": "4.2.5"
},
"peerDependencies": {
diff --git a/web-app/packages/lib/src/common/components/AppPasswordTooltip.vue b/web-app/packages/lib/src/common/components/AppPasswordTooltip.vue
index eac0d184..4fb9e257 100644
--- a/web-app/packages/lib/src/common/components/AppPasswordTooltip.vue
+++ b/web-app/packages/lib/src/common/components/AppPasswordTooltip.vue
@@ -18,17 +18,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
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') }}