diff --git a/src/backend/configure.ts b/src/backend/configure.ts
index 55d74ca0..503f2c4d 100644
--- a/src/backend/configure.ts
+++ b/src/backend/configure.ts
@@ -164,6 +164,7 @@ export async function configure(command: Configure) {
await codemods.makeUsingStub(stubsRoot, 'tests/unit/stream_service.stub', {});
await codemods.makeUsingStub(stubsRoot, 'tests/unit/user_service.stub', {});
await codemods.makeUsingStub(stubsRoot, 'tests/unit/progress_service.stub', {});
+ await codemods.makeUsingStub(stubsRoot, 'tests/unit/language_service.stub', {});
await codemods.makeUsingStub(stubsRoot, 'tests/unit/model.stub', {});
await codemods.makeUsingStub(stubsRoot, 'tests/helpers/cms_mock.stub', {});
await codemods.makeUsingStub(stubsRoot, 'tests/helpers/story_test_helper.stub', {});
diff --git a/src/backend/index.ts b/src/backend/index.ts
index 270a6026..dd26af89 100644
--- a/src/backend/index.ts
+++ b/src/backend/index.ts
@@ -40,6 +40,7 @@ export * from './services/draft_service.js';
export * from './services/index_service.js';
export * from './services/page_service.js';
export * from './services/progress_service.js';
+export * from './services/language_service.js';
export * from './services/ui_service.js';
export * from './services/user_service.js';
export * from './services/stream_service.js';
diff --git a/src/backend/services/language_service.ts b/src/backend/services/language_service.ts
new file mode 100644
index 00000000..e3a81b33
--- /dev/null
+++ b/src/backend/services/language_service.ts
@@ -0,0 +1,218 @@
+import { inject } from '@adonisjs/core';
+import {
+ type LanguageSpecification,
+ type LanguageTableItem,
+ type LanguagesEditProps,
+ type SettingsPageProps,
+ type SupportCode,
+ type UserInterface,
+} from '../../types.js';
+import User from '../models/user.js';
+import { CmsService } from './cms_service.js';
+import { ProgressService } from './progress_service.js';
+
+export interface SupportRequestLanguageSpec {
+ name: string;
+ nativeName: string;
+ locale: string;
+}
+
+export interface SupportRequestDetails {
+ subject: string;
+ details: string;
+ language?: SupportRequestLanguageSpec;
+}
+
+interface SupportCodeDefinition {
+ code: SupportCode;
+ description: string;
+ subject: string;
+}
+
+const SUPPORT_CODES = {
+ REMOVE_LANGUAGE: {
+ code: 'REMOVE_LANGUAGE',
+ subject: 'Remove language',
+ description: 'Language requested to be removed',
+ },
+ UPDATE_LANGUAGE: {
+ code: 'UPDATE_LANGUAGE',
+ subject: 'App update - new language added.',
+ description: 'Language requested to be added',
+ },
+ UPDATE_CONTENT: {
+ code: 'UPDATE_CONTENT',
+ subject: 'App update - content added.',
+ description: 'Content requested to be updated',
+ },
+ UPDATE_APP: {
+ code: 'UPDATE_APP',
+ subject: 'App update - new language and content.',
+ description: 'App update requested for new language and new content',
+ },
+} as const satisfies Record;
+
+const defaultTranslationProgress = [
+ { name: 'Interface', done: 0, draft: 0, total: 0 },
+ { name: 'Content', done: 0, draft: 0, total: 0 },
+];
+
+@inject()
+export class LanguageService {
+ protected sourceLocale: string;
+
+ constructor(protected cms: CmsService) {
+ this.sourceLocale = cms.sourceLocale;
+ }
+
+ public find(locale: string): LanguageSpecification | undefined {
+ return this.cms.config.languages.find(
+ (lang: LanguageSpecification) => lang.locale === locale,
+ );
+ }
+
+ public languagesEdit(): LanguagesEditProps {
+ return {
+ addedLanguages: this.cms.config.languages,
+ };
+ }
+
+ public async settingsIndex(
+ user: UserInterface,
+ ): Promise> {
+ const sourceLanguageSpec =
+ this.cms.config.languages.find(
+ (lang: LanguageSpecification) => lang.locale === this.sourceLocale,
+ ) ?? this.cms.config.languages[0];
+
+ const progressService = new ProgressService(this.cms);
+ const progressItems = await progressService.progress(user);
+ const translationProgressByLocale = Object.fromEntries(
+ (progressItems ?? []).map((item) => [item.locale, item.progress]),
+ );
+
+ const users = await User.query().where('name', '!=', 'redacted');
+
+ const sourceLanguage = this.toLanguageTableItem(
+ sourceLanguageSpec,
+ translationProgressByLocale,
+ users,
+ );
+
+ const languageItems = this.cms.config.languages
+ .filter(
+ (language: LanguageSpecification) => language.locale !== sourceLanguage.locale,
+ )
+ .map((language: LanguageSpecification) =>
+ this.toLanguageTableItem(language, translationProgressByLocale, users),
+ );
+
+ return { sourceLanguage, languageItems };
+ }
+
+ public async addLanguages(languages: LanguageSpecification[]): Promise {
+ const existingLocales = new Set(
+ this.cms.config.languages.map((lang: LanguageSpecification) => lang.locale),
+ );
+ const toAdd = languages.filter((lang) => !existingLocales.has(lang.locale));
+
+ if (toAdd.length > 0) {
+ await this.save([...this.cms.config.languages, ...toAdd]);
+ }
+ }
+
+ public async updateBibleTranslation(
+ locale: string,
+ bibleVersion: string,
+ bibleLabel: string,
+ ): Promise {
+ if (!this.find(locale)) {
+ throw new Error('Language not found');
+ }
+
+ const languages = this.cms.config.languages.map((lang: LanguageSpecification) =>
+ lang.locale === locale ? { ...lang, bibleVersion, bibleLabel } : lang,
+ );
+
+ await this.save(languages);
+ }
+
+ public async removeLanguage(locale: string): Promise {
+ if (locale === this.sourceLocale) {
+ throw new Error('Cannot remove the source language');
+ }
+
+ if (!this.find(locale)) {
+ throw new Error('Language not found');
+ }
+
+ const languages = this.cms.config.languages.filter(
+ (lang: LanguageSpecification) => lang.locale !== locale,
+ );
+ await this.save(languages);
+ }
+
+ public getSupportRequestDetails(
+ supportCode: SupportCode,
+ removeLanguageCode?: string,
+ ): SupportRequestDetails {
+ const definition = this.supportCodeDefinition(supportCode);
+ if (!definition) {
+ throw new Error('Invalid support code');
+ }
+
+ let language: SupportRequestLanguageSpec | undefined;
+ if (supportCode === 'REMOVE_LANGUAGE' && removeLanguageCode) {
+ const languageSpec = this.find(removeLanguageCode);
+ if (languageSpec) {
+ language = this.parseLanguageForSupport(languageSpec);
+ }
+ }
+
+ return {
+ subject: `Support request: ${definition.subject}`,
+ details: definition.description,
+ language,
+ };
+ }
+
+ private toLanguageTableItem(
+ spec: LanguageSpecification,
+ translationProgressByLocale: Record,
+ users: InstanceType[],
+ ): LanguageTableItem {
+ return {
+ language: spec.language,
+ languageDirection: spec.languageDirection,
+ locale: spec.locale,
+ bibleLabel: spec.bibleLabel,
+ bibleVersion: spec.bibleVersion,
+ translationProgress:
+ translationProgressByLocale[spec.locale] ?? defaultTranslationProgress,
+ teamMembers: users
+ .filter((user) => user.language === spec.locale)
+ .map((user) => user.meta),
+ };
+ }
+
+ private parseLanguageForSupport(
+ spec: LanguageSpecification,
+ ): SupportRequestLanguageSpec {
+ const { language, locale } = spec;
+ const name = language.split('|')[0].trim();
+
+ if (language.includes('|')) {
+ return { name, nativeName: language.split('|')[1].trim(), locale };
+ }
+
+ return { name, nativeName: language, locale };
+ }
+
+ private supportCodeDefinition(code: string): SupportCodeDefinition | undefined {
+ return Object.values(SUPPORT_CODES).find((definition) => definition.code === code);
+ }
+
+ private async save(languages: LanguageSpecification[]) {
+ await this.cms.patchConfig({ languages });
+ }
+}
diff --git a/src/backend/stubs/controllers/settings_controller.stub b/src/backend/stubs/controllers/settings_controller.stub
index e223e0a4..1319a5cf 100644
--- a/src/backend/stubs/controllers/settings_controller.stub
+++ b/src/backend/stubs/controllers/settings_controller.stub
@@ -4,11 +4,8 @@
import type { HttpContext } from '@adonisjs/core/http';
import mail from '@adonisjs/mail/services/main';
import {
- User,
- ProgressService,
+ LanguageService,
type LanguageSpecification,
- type LanguageTableItem,
- type LanguagesEditProps,
type SettingsPageProps,
type UserInterface,
} from '@story-cms/kit';
@@ -16,101 +13,15 @@ import {
import cms from '#services/cms';
import providers from '#config/providers';
-interface SupportRequestLanguageSpec {
- name: string;
- nativeName: string;
- locale: string;
-}
-
-interface SupportCodeDefinition {
- code: string;
- description: string;
- subject: string;
-}
-
-const SUPPORT_CODES = {
- REMOVE_LANGUAGE: {
- code: 'REMOVE_LANGUAGE',
- subject: 'Remove language',
- description: 'Language requested to be removed',
- },
- UPDATE_LANGUAGE: {
- code: 'UPDATE_LANGUAGE',
- subject: 'App update - new language added.',
- description: 'Language requested to be added',
- },
- UPDATE_CONTENT: {
- code: 'UPDATE_CONTENT',
- subject: 'App update - content added.',
- description: 'Content requested to be updated',
- },
- UPDATE_APP: {
- code: 'UPDATE_APP',
- subject: 'App update - new language and content.',
- description: 'App update requested for new language and new content',
- },
-} as const satisfies Record;
-
-type SupportCode = (typeof SUPPORT_CODES)[keyof typeof SUPPORT_CODES]['code'];
-
-function languageDisplayName(language: string): string {
- return language.split('|')[0].trim();
-}
-
-function parseLanguageForSupport(
- spec: LanguageSpecification,
-): SupportRequestLanguageSpec {
- const { language, locale } = spec;
- const name = languageDisplayName(language);
-
- if (language.includes('|')) {
- const nativeName = language.split('|')[1].trim();
- return { name, nativeName, locale };
- }
-
- return { name, nativeName: language, locale };
-}
-
-function supportCodeDefinition(code: string): SupportCodeDefinition | undefined {
- return Object.values(SUPPORT_CODES).find((definition) => definition.code === code);
-}
-
export default class SettingsController {
public async index(ctx: HttpContext) {
cms.localeFromPath(ctx);
- const sourceLanguage =
- cms.config.languages.find(
- (lang: LanguageSpecification) => lang.locale === cms.sourceLocale,
- ) ?? cms.config.languages[0];
-
- const progressService = new ProgressService(cms);
- const progressItems = await progressService.progress(ctx.auth.user! as UserInterface);
- const translationProgressByLocale = Object.fromEntries(
- (progressItems ?? []).map((item) => [item.locale, item.progress]),
+ const languageService = new LanguageService(cms);
+ const { sourceLanguage, languageItems } = await languageService.settingsIndex(
+ ctx.auth.user! as UserInterface,
);
- const users = await User.query().where('name', '!=', 'redacted');
-
- const languageItems: LanguageTableItem[] = cms.config.languages
- .filter(
- (language: LanguageSpecification) => language.locale !== sourceLanguage.locale,
- )
- .map((language: LanguageSpecification) => ({
- language: language.language,
- languageDirection: language.languageDirection,
- locale: language.locale,
- bibleLabel: language.bibleLabel,
- bibleVersion: language.bibleVersion,
- translationProgress: translationProgressByLocale[language.locale] ?? [
- { name: 'Interface', done: 0, draft: 0, total: 0 },
- { name: 'Content', done: 0, draft: 0, total: 0 },
- ],
- teamMembers: users
- .filter((user) => user.language === language.locale)
- .map((user) => user.meta),
- }));
-
const props: SettingsPageProps = {
sourceLanguage,
languageItems,
@@ -125,9 +36,7 @@ export default class SettingsController {
public async editLanguage(ctx: HttpContext) {
cms.localeFromPath(ctx);
- const props: LanguagesEditProps = {
- addedLanguages: cms.config.languages,
- };
+ const props = new LanguageService(cms).languagesEdit();
// @ts-expect-error Inertia page name
return ctx.inertia.render('LanguagesEdit', props);
@@ -137,16 +46,7 @@ export default class SettingsController {
cms.localeFromPath(ctx);
const payload = ctx.request.all() as { languages?: LanguageSpecification[] };
- const existingLocales = new Set(
- cms.config.languages.map((lang: LanguageSpecification) => lang.locale),
- );
- const toAdd = (payload.languages ?? []).filter(
- (lang: LanguageSpecification) => !existingLocales.has(lang.locale),
- );
-
- if (toAdd.length > 0) {
- await cms.patchConfig({ languages: [...cms.config.languages, ...toAdd] });
- }
+ await new LanguageService(cms).addLanguages(payload.languages ?? []);
return ctx.response.redirect().back();
}
@@ -154,52 +54,43 @@ export default class SettingsController {
public async updateBibleTranslation(ctx: HttpContext) {
cms.localeFromPath(ctx);
- const languageLocale = ctx.params.languageLocale;
const { bibleVersion, bibleVersionName } = ctx.request.only([
'bibleVersion',
'bibleVersionName',
]);
- const language = cms.config.languages.find(
- (lang: LanguageSpecification) => lang.locale === languageLocale,
- );
- if (!language) {
- return ctx.response.notFound({ error: 'Language not found' });
+ try {
+ await new LanguageService(cms).updateBibleTranslation(
+ ctx.params.languageLocale,
+ bibleVersion,
+ bibleVersionName,
+ );
+ } catch (error) {
+ if ((error as Error).message === 'Language not found') {
+ return ctx.response.notFound({ error: 'Language not found' });
+ }
+ throw error;
}
- const languages = cms.config.languages.map((lang: LanguageSpecification) =>
- lang.locale === languageLocale
- ? { ...lang, bibleVersion, bibleLabel: bibleVersionName }
- : lang,
- );
-
- await cms.patchConfig({ languages });
-
return ctx.response.redirect().back();
}
public async removeLanguage(ctx: HttpContext) {
cms.localeFromPath(ctx);
- const languageLocale = ctx.params.languageLocale;
-
- if (languageLocale === cms.sourceLocale) {
- return ctx.response.badRequest({ error: 'Cannot remove the source language' });
- }
-
- const language = cms.config.languages.find(
- (lang: LanguageSpecification) => lang.locale === languageLocale,
- );
- if (!language) {
- return ctx.response.notFound({ error: 'Language not found' });
+ try {
+ await new LanguageService(cms).removeLanguage(ctx.params.languageLocale);
+ } catch (error) {
+ const message = (error as Error).message;
+ if (message === 'Cannot remove the source language') {
+ return ctx.response.badRequest({ error: message });
+ }
+ if (message === 'Language not found') {
+ return ctx.response.notFound({ error: message });
+ }
+ throw error;
}
- const languages = cms.config.languages.filter(
- (lang: LanguageSpecification) => lang.locale !== languageLocale,
- );
-
- await cms.patchConfig({ languages });
-
return ctx.response.redirect().back();
}
@@ -209,43 +100,38 @@ export default class SettingsController {
const { supportCode, removeLanguageCode } = ctx.request.only([
'supportCode',
'removeLanguageCode',
- ]) as {
- supportCode?: SupportCode;
- removeLanguageCode?: string;
- };
+ ]);
- const definition = supportCode ? supportCodeDefinition(supportCode) : undefined;
- if (!definition) {
+ if (!supportCode) {
return ctx.response.badRequest({ error: 'Invalid support code' });
}
- let language: SupportRequestLanguageSpec | undefined;
- if (supportCode === 'REMOVE_LANGUAGE' && removeLanguageCode) {
- const languageSpec = cms.config.languages.find(
- (lang: LanguageSpecification) => lang.locale === removeLanguageCode,
- );
- if (languageSpec) {
- language = parseLanguageForSupport(languageSpec);
+ try {
+ const { subject, details, language } = new LanguageService(
+ cms,
+ ).getSupportRequestDetails(supportCode, removeLanguageCode);
+
+ await mail.send((message) => {
+ message
+ .to(cms.config.supportEmail, 'Journey Studio Support')
+ .subject(subject)
+ .htmlView('emails/support_request', {
+ appName: cms.config.name,
+ requester: ctx.auth.user?.name ?? 'Unknown',
+ subject,
+ supportCode,
+ details,
+ language,
+ logo: cms.config.logo,
+ });
+ });
+ } catch (error) {
+ if ((error as Error).message === 'Invalid support code') {
+ return ctx.response.badRequest({ error: 'Invalid support code' });
}
+ throw error;
}
- const subject = {{ '`Support request: ${definition.subject}`' }};
-
- await mail.send((message) => {
- message
- .to(cms.config.supportEmail, 'Journey Studio Support')
- .subject(subject)
- .htmlView('emails/support_request', {
- appName: cms.config.name,
- requester: ctx.auth.user?.name ?? 'Unknown',
- subject,
- supportCode,
- details: definition.description,
- language,
- logo: cms.config.logo,
- });
- });
-
return ctx.response.redirect().back();
}
}
diff --git a/src/backend/stubs/tests/unit/language_service.stub b/src/backend/stubs/tests/unit/language_service.stub
new file mode 100644
index 00000000..32376855
--- /dev/null
+++ b/src/backend/stubs/tests/unit/language_service.stub
@@ -0,0 +1,168 @@
+{{{
+ exports({ to: app.makePath('tests/unit/language_service.spec.ts') })
+}}}
+import testUtils from '@adonisjs/core/services/test_utils';
+import { test } from '@japa/runner';
+import {
+ Config,
+ User,
+ LanguageService,
+ type CmsConfig,
+ type LanguageSpecification,
+} from '@story-cms/kit';
+import cms from '#services/cms';
+import { setupMockCms, testCmsConfig } from '#tests/helpers/cms_mock';
+
+test.group('LanguageService', (group) => {
+ group.each.setup(() => testUtils.db().withGlobalTransaction());
+
+ const createConfigRecord = async () => {
+ return Config.create({
+ key: 'cms',
+ version: 1,
+ data: structuredClone(testCmsConfig) as unknown as Record,
+ });
+ };
+
+ test('addLanguages skips duplicate locales', async ({ assert }) => {
+ await createConfigRecord();
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+ await service.addLanguages([
+ { locale: 'es', language: 'Spanish', languageDirection: 'ltr' },
+ { locale: 'fr', language: 'French', languageDirection: 'ltr' },
+ ]);
+
+ assert.lengthOf(cms.config.languages, 4);
+ assert.isTrue(
+ cms.config.languages.some((lang: LanguageSpecification) => lang.locale === 'fr'),
+ );
+ assert.equal(
+ cms.config.languages.filter((lang: LanguageSpecification) => lang.locale === 'es')
+ .length,
+ 1,
+ );
+ });
+
+ test('updateBibleTranslation updates one language and leaves others untouched', async ({
+ assert,
+ }) => {
+ const config = await createConfigRecord();
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+ await service.updateBibleTranslation('es', 'new-es-version', 'Nueva Biblia');
+
+ await config.refresh();
+ const data = config.data as CmsConfig;
+
+ const spanish = data.languages.find((lang) => lang.locale === 'es');
+ assert.equal(spanish?.bibleVersion, 'new-es-version');
+ assert.equal(spanish?.bibleLabel, 'Nueva Biblia');
+
+ const english = data.languages.find((lang) => lang.locale === 'en');
+ assert.equal(english?.bibleVersion, 'test-bible-version');
+ });
+
+ test('updateBibleTranslation throws for unknown locale', async ({ assert }) => {
+ await createConfigRecord();
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+
+ await assert.rejects(
+ () => service.updateBibleTranslation('xx', 'some-version', 'Some Version'),
+ /Language not found/,
+ );
+ });
+
+ test('removeLanguage rejects source locale', async ({ assert }) => {
+ await createConfigRecord();
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+
+ await assert.rejects(
+ () => service.removeLanguage('en'),
+ /Cannot remove the source language/,
+ );
+ assert.lengthOf(cms.config.languages, 3);
+ });
+
+ test('removeLanguage throws for unknown locale', async ({ assert }) => {
+ await createConfigRecord();
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+
+ await assert.rejects(() => service.removeLanguage('xx'), /Language not found/);
+ assert.lengthOf(cms.config.languages, 3);
+ });
+
+ test('removeLanguage deletes a non-source language', async ({ assert }) => {
+ const config = await createConfigRecord();
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+ await service.removeLanguage('es');
+
+ await config.refresh();
+ const data = config.data as CmsConfig;
+ assert.isUndefined(data.languages.find((lang) => lang.locale === 'es'));
+ });
+
+ test('settingsIndex returns source language and non-source items', async ({
+ assert,
+ }) => {
+ await createConfigRecord();
+ setupMockCms(testCmsConfig);
+
+ await User.create({
+ name: 'Spanish Editor',
+ email: 'es@test.com',
+ password: 'password',
+ language: 'es',
+ });
+
+ const service = new LanguageService(cms);
+ const user = await User.create({
+ name: 'Admin',
+ email: 'admin@test.com',
+ password: 'password',
+ language: '*',
+ });
+
+ const { sourceLanguage, languageItems } = await service.settingsIndex(user);
+
+ assert.equal(sourceLanguage.locale, 'en');
+ assert.lengthOf(languageItems, 2);
+ assert.isTrue(languageItems.every((item) => item.locale !== 'en'));
+
+ const spanish = languageItems.find((item) => item.locale === 'es');
+ assert.isDefined(spanish);
+ assert.lengthOf(spanish!.teamMembers ?? [], 1);
+ assert.equal(spanish!.teamMembers![0].name, 'Spanish Editor');
+ });
+
+ test('languagesEdit returns configured languages', async ({ assert }) => {
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+ const props = service.languagesEdit();
+
+ assert.lengthOf(props.addedLanguages, 3);
+ assert.equal(props.addedLanguages[0].locale, 'en');
+ });
+
+ test('getSupportRequestDetails throws for unknown support code', async ({ assert }) => {
+ setupMockCms(testCmsConfig);
+
+ const service = new LanguageService(cms);
+
+ await assert.rejects(
+ () => service.getSupportRequestDetails('UNKNOWN_CODE' as 'REMOVE_LANGUAGE'),
+ /Invalid support code/,
+ );
+ });
+});
diff --git a/src/frontend/settings/languages/components/language-table-row.vue b/src/frontend/settings/languages/components/language-table-row.vue
new file mode 100644
index 00000000..2b2b8b0d
--- /dev/null
+++ b/src/frontend/settings/languages/components/language-table-row.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+ Source language
+
+
+
+
+
+
+
+
+
+
+
+
No team members yet
+
+ Press the three dots to
+ assign team members.
+
+
+
+
+
+ {{ item.bibleLabel ?? '—' }}
+
+
+
+
+
+
+
+
+ Assign team members
+
+
+ Change Bible translation
+
+
+ {{
+ deletionMode === 'request' ? 'Request language deletion' : 'Remove language'
+ }}
+
+
+
+
+
+
+
diff --git a/src/frontend/settings/languages/components/language-table.story.vue b/src/frontend/settings/languages/components/language-table.story.vue
index 83d576c3..577668eb 100644
--- a/src/frontend/settings/languages/components/language-table.story.vue
+++ b/src/frontend/settings/languages/components/language-table.story.vue
@@ -3,16 +3,18 @@
-
+
import LangTable from './language-table.vue';
-import { languageTableItems, manyLanguageTableItems } from '../../../test/mocks';
+import {
+ languageTableItems,
+ manyLanguageTableItems,
+ sourceLanguage,
+} from '../../../test/mocks';
import type { LanguageTableItem } from '../../../../types';
const onRemove = (item: LanguageTableItem) => {
diff --git a/src/frontend/settings/languages/components/language-table.vue b/src/frontend/settings/languages/components/language-table.vue
index 601699b8..4c0733e6 100644
--- a/src/frontend/settings/languages/components/language-table.vue
+++ b/src/frontend/settings/languages/components/language-table.vue
@@ -6,123 +6,57 @@
-
-
+
+
+
+
+
+
+
+
+
Active translations
Translation progress
Team members
Bible translation
-
+
Actions
-
-
-
-
-
-
-
-
-
-
-
-
-
-
No team members yet
-
- Press the three dots to
- assign team members.
-
-
-
-
- {{ truncate(item.bibleLabel, 30) }}
-
-
-
-
-
- Actions for {{ item.language }}
-
-
-
-
-
- Assign team members
-
-
-
-
- Change Bible translation
-
-
-
-
- {{
- hasContent(item) ? 'Request language deletion' : 'Remove language'
- }}
-
-
-
-
-
-
-
+
+
@@ -146,9 +80,9 @@
/>
@@ -157,21 +91,18 @@
diff --git a/src/frontend/settings/languages/components/source-language.story.vue b/src/frontend/settings/languages/components/source-language.story.vue
deleted file mode 100644
index e90cf2ed..00000000
--- a/src/frontend/settings/languages/components/source-language.story.vue
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/frontend/settings/languages/components/source-language.vue b/src/frontend/settings/languages/components/source-language.vue
deleted file mode 100644
index 7a69858a..00000000
--- a/src/frontend/settings/languages/components/source-language.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
Source Language
-
- You cannot change your source language. However upon request you can change the
- Bible translation of your source language by pressing the three dots.
-
-
-
-
-
-
-
-
- {{ spec.bibleLabel }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/frontend/settings/languages/languages-edit.story.vue b/src/frontend/settings/languages/languages-edit.story.vue
index 67a6a9e8..f465fe0f 100644
--- a/src/frontend/settings/languages/languages-edit.story.vue
+++ b/src/frontend/settings/languages/languages-edit.story.vue
@@ -2,15 +2,11 @@
@@ -19,7 +15,7 @@
diff --git a/src/frontend/shared/helpers.ts b/src/frontend/shared/helpers.ts
index c737599c..a6eca5ca 100644
--- a/src/frontend/shared/helpers.ts
+++ b/src/frontend/shared/helpers.ts
@@ -1,8 +1,11 @@
import type { App, PropType } from 'vue';
-import type { FieldSpec, LanguageSpecification } from '../../types';
+import { router } from '@inertiajs/vue3';
+import type { RequestPayload } from '@inertiajs/core';
+import { ResponseStatus, type FieldSpec, type LanguageSpecification } from '../../types';
import { BibleBooksMap } from './bibleBooks';
import type { Variant, Story } from 'histoire';
import { DateTime } from 'luxon';
+import { useSharedStore } from '../store';
export const commonProps = {
field: {
@@ -305,9 +308,7 @@ export function compareLanguagesByDisplayName(
a: LanguageSortable,
b: LanguageSortable,
): number {
- return languageDisplayName(a.language).localeCompare(
- languageDisplayName(b.language),
- );
+ return languageDisplayName(a.language).localeCompare(languageDisplayName(b.language));
}
export function sortLanguagesByDisplayName(
@@ -349,3 +350,40 @@ export function replaceLocaleInPath(
segments[0] = targetLocale;
return `/${segments.join('/')}`;
}
+
+type PostOptions = {
+ onSuccess?: () => void;
+ onError?: () => void;
+ successMessage?: string;
+ successDetail?: string;
+ failureMessage?: string;
+};
+
+export const postWithPayload = (
+ url: string,
+ payload: Record | object = {},
+ options: PostOptions = {},
+) => {
+ const shared = useSharedStore();
+
+ router.post(url, payload as RequestPayload, {
+ preserveScroll: true,
+ onSuccess: () => {
+ if (options.successMessage) {
+ shared.addMessage(
+ ResponseStatus.Confirmation,
+ options.successMessage,
+ options.successDetail,
+ );
+ }
+ options.onSuccess?.();
+ },
+ onError: (errors) => {
+ shared.setErrors(errors);
+ if (options.failureMessage) {
+ shared.addMessage(ResponseStatus.Failure, options.failureMessage);
+ }
+ options.onError?.();
+ },
+ });
+};
diff --git a/src/frontend/test/mocks.ts b/src/frontend/test/mocks.ts
index eedce499..5b9c200e 100644
--- a/src/frontend/test/mocks.ts
+++ b/src/frontend/test/mocks.ts
@@ -1059,12 +1059,42 @@ export const languageTableItems: LanguageTableItem[] = [
},
];
-export const sourceLanguage: LanguageSpecification = {
+export const sourceLanguage: LanguageTableItem = {
language: 'English | American',
locale: 'en',
languageDirection: 'ltr',
bibleVersion: 'de4e12af7f28f599-01',
bibleLabel: '(KJV) King James Version',
+ translationProgress: [
+ { name: 'Interface', done: 100, draft: 0, total: 100 },
+ { name: 'Content', done: 250, draft: 25, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 1,
+ name: 'John Doe',
+ initials: 'JD',
+ email: 'john@example.com',
+ isManager: true,
+ isAdmin: true,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ {
+ id: 2,
+ name: 'Jane Smith',
+ initials: 'JS',
+ email: 'jane.smith@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
};
export const manyLanguageTableItems: LanguageTableItem[] = [
@@ -1105,6 +1135,681 @@ export const manyLanguageTableItems: LanguageTableItem[] = [
},
],
},
+ {
+ language: 'Afrikaans',
+ languageDirection: 'ltr',
+ locale: 'af',
+ bibleLabel: 'Die Bybel',
+ translationProgress: [
+ { name: 'Interface', done: 42, draft: 8, total: 100 },
+ { name: 'Content', done: 120, draft: 15, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 11,
+ name: 'Pieter van Wyk',
+ initials: 'PW',
+ email: 'pieter.vanwyk@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Amharic - አማርኛ',
+ languageDirection: 'ltr',
+ locale: 'am',
+ bibleLabel: 'Amharic Bible',
+ translationProgress: [
+ { name: 'Interface', done: 15, draft: 5, total: 100 },
+ { name: 'Content', done: 45, draft: 10, total: 200 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Assamese - অসমীয়া',
+ languageDirection: 'ltr',
+ locale: 'as',
+ bibleLabel: 'Assamese Bible',
+ translationProgress: [
+ { name: 'Interface', done: 60, draft: 0, total: 100 },
+ { name: 'Content', done: 180, draft: 20, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 12,
+ name: 'Ananya Das',
+ initials: 'AD',
+ email: 'ananya.das@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'translator',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ {
+ id: 13,
+ name: 'Rohan Baruah',
+ initials: 'RB',
+ email: 'rohan.baruah@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: true,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Azerbaijani - Azərbaycan',
+ languageDirection: 'ltr',
+ locale: 'az',
+ bibleLabel: 'Azərbaycan Müqəddəs Kitabı',
+ translationProgress: [
+ { name: 'Interface', done: 90, draft: 0, total: 100 },
+ { name: 'Content', done: 270, draft: 0, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 14,
+ name: 'Elvin Mammadov',
+ initials: 'EM',
+ email: 'elvin.mammadov@example.com',
+ isManager: true,
+ isAdmin: false,
+ role: 'admin',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Belarusian - Беларуская',
+ languageDirection: 'ltr',
+ locale: 'be',
+ bibleLabel: 'Біблія',
+ translationProgress: [
+ { name: 'Interface', done: 30, draft: 12, total: 100 },
+ { name: 'Content', done: 90, draft: 30, total: 250 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Bulgarian - Български',
+ languageDirection: 'ltr',
+ locale: 'bg',
+ bibleLabel: 'Библия',
+ translationProgress: [
+ { name: 'Interface', done: 75, draft: 5, total: 100 },
+ { name: 'Content', done: 210, draft: 0, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 15,
+ name: 'Ivana Petrova',
+ initials: 'IP',
+ email: 'ivana.petrova@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Tibetan - བོད་ཡིག',
+ languageDirection: 'ltr',
+ locale: 'bo',
+ bibleLabel: 'Tibetan Bible',
+ translationProgress: [
+ { name: 'Interface', done: 5, draft: 2, total: 100 },
+ { name: 'Content', done: 12, draft: 3, total: 150 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Bosnian - Bosanski',
+ languageDirection: 'ltr',
+ locale: 'bs',
+ bibleLabel: 'Biblija',
+ translationProgress: [
+ { name: 'Interface', done: 55, draft: 10, total: 100 },
+ { name: 'Content', done: 165, draft: 15, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 16,
+ name: 'Amira Hodžić',
+ initials: 'AH',
+ email: 'amira.hodzic@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Catalan Valencian - Català',
+ languageDirection: 'ltr',
+ locale: 'ca',
+ bibleLabel: 'Bíblia Catalana',
+ translationProgress: [
+ { name: 'Interface', done: 100, draft: 0, total: 100 },
+ { name: 'Content', done: 300, draft: 0, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 17,
+ name: 'Marc Soler',
+ initials: 'MS',
+ email: 'marc.soler@example.com',
+ isManager: false,
+ isAdmin: true,
+ role: 'admin',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ {
+ id: 18,
+ name: 'Laia Ferrer',
+ initials: 'LF',
+ email: 'laia.ferrer@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Czech - Čeština',
+ languageDirection: 'ltr',
+ locale: 'cs',
+ bibleLabel: 'Bible kralická',
+ translationProgress: [
+ { name: 'Interface', done: 68, draft: 7, total: 100 },
+ { name: 'Content', done: 204, draft: 6, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 19,
+ name: 'Tomáš Novák',
+ initials: 'TN',
+ email: 'tomas.novak@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Welsh - Cymraeg',
+ languageDirection: 'ltr',
+ locale: 'cy',
+ bibleLabel: 'Y Beibl Cymraeg',
+ translationProgress: [
+ { name: 'Interface', done: 22, draft: 8, total: 100 },
+ { name: 'Content', done: 66, draft: 12, total: 200 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Danish - Dansk',
+ languageDirection: 'ltr',
+ locale: 'da',
+ bibleLabel: 'Bibelen på dansk',
+ translationProgress: [
+ { name: 'Interface', done: 95, draft: 0, total: 100 },
+ { name: 'Content', done: 285, draft: 5, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 20,
+ name: 'Freja Andersen',
+ initials: 'FA',
+ email: 'freja.andersen@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'translator',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Modern Greek - Ελληνικά',
+ languageDirection: 'ltr',
+ locale: 'el',
+ bibleLabel: 'Βίβλος',
+ translationProgress: [
+ { name: 'Interface', done: 80, draft: 0, total: 100 },
+ { name: 'Content', done: 240, draft: 0, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 21,
+ name: 'Elena Papadopoulos',
+ initials: 'EP',
+ email: 'elena.papadopoulos@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ {
+ id: 22,
+ name: 'Nikos Georgiou',
+ initials: 'NG',
+ email: 'nikos.georgiou@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Estonian - Eesti',
+ languageDirection: 'ltr',
+ locale: 'et',
+ bibleLabel: 'Piibel',
+ translationProgress: [
+ { name: 'Interface', done: 50, draft: 15, total: 100 },
+ { name: 'Content', done: 150, draft: 25, total: 300 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Basque - Euskara',
+ languageDirection: 'ltr',
+ locale: 'eu',
+ bibleLabel: 'Euskal Biblia',
+ translationProgress: [
+ { name: 'Interface', done: 35, draft: 5, total: 100 },
+ { name: 'Content', done: 105, draft: 10, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 23,
+ name: 'Ane Iturrioz',
+ initials: 'AI',
+ email: 'ane.iturrioz@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Persian - فارسی',
+ languageDirection: 'rtl',
+ locale: 'fa',
+ bibleLabel: 'ترجمه فارسی',
+ translationProgress: [
+ { name: 'Interface', done: 72, draft: 3, total: 100 },
+ { name: 'Content', done: 216, draft: 9, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 24,
+ name: 'Sara Rahmani',
+ initials: 'SR',
+ email: 'sara.rahmani@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Finnish - Suomi',
+ languageDirection: 'ltr',
+ locale: 'fi',
+ bibleLabel: 'Raamattu',
+ translationProgress: [
+ { name: 'Interface', done: 100, draft: 0, total: 100 },
+ { name: 'Content', done: 298, draft: 2, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 25,
+ name: 'Mikko Virtanen',
+ initials: 'MV',
+ email: 'mikko.virtanen@example.com',
+ isManager: true,
+ isAdmin: false,
+ role: 'admin',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Filipino Pilipino - Pilipino',
+ languageDirection: 'ltr',
+ locale: 'fil',
+ bibleLabel: 'Ang Biblia',
+ translationProgress: [
+ { name: 'Interface', done: 88, draft: 2, total: 100 },
+ { name: 'Content', done: 264, draft: 6, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 26,
+ name: 'Maria Santos',
+ initials: 'MS',
+ email: 'maria.santos@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ {
+ id: 27,
+ name: 'Jose Reyes',
+ initials: 'JR',
+ email: 'jose.reyes@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'translator',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Swiss French - Français suisse',
+ languageDirection: 'ltr',
+ locale: 'fr-ch',
+ bibleLabel: 'Bible Segond 21',
+ translationProgress: [
+ { name: 'Interface', done: 62, draft: 8, total: 100 },
+ { name: 'Content', done: 186, draft: 14, total: 300 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Irish - Gaeilge',
+ languageDirection: 'ltr',
+ locale: 'ga',
+ bibleLabel: 'An Bíobla',
+ translationProgress: [
+ { name: 'Interface', done: 18, draft: 6, total: 100 },
+ { name: 'Content', done: 54, draft: 8, total: 200 },
+ ],
+ teamMembers: [
+ {
+ id: 28,
+ name: 'Siobhán Murphy',
+ initials: 'SM',
+ email: 'siobhan.murphy@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Galician - Galego',
+ languageDirection: 'ltr',
+ locale: 'gl',
+ bibleLabel: 'Biblia Galega',
+ translationProgress: [
+ { name: 'Interface', done: 45, draft: 10, total: 100 },
+ { name: 'Content', done: 135, draft: 20, total: 300 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Swiss German Alemannic Alsatian - Alemannisch',
+ languageDirection: 'ltr',
+ locale: 'gsw',
+ bibleLabel: 'Schwyzerdütsch Bibel',
+ translationProgress: [
+ { name: 'Interface', done: 28, draft: 4, total: 100 },
+ { name: 'Content', done: 84, draft: 6, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 29,
+ name: 'Hans Müller',
+ initials: 'HM',
+ email: 'hans.muller@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Gujarati - ગુજરાતી',
+ languageDirection: 'ltr',
+ locale: 'gu',
+ bibleLabel: 'Gujarati Bible',
+ translationProgress: [
+ { name: 'Interface', done: 58, draft: 12, total: 100 },
+ { name: 'Content', done: 174, draft: 18, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 30,
+ name: 'Priya Shah',
+ initials: 'PS',
+ email: 'priya.shah@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ {
+ id: 31,
+ name: 'Raj Patel',
+ initials: 'RP',
+ email: 'raj.patel@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'translator',
+ language: null,
+ hasPendingInvite: true,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Hebrew - עברית',
+ languageDirection: 'rtl',
+ locale: 'he',
+ bibleLabel: 'תנ״ך',
+ translationProgress: [
+ { name: 'Interface', done: 92, draft: 0, total: 100 },
+ { name: 'Content', done: 276, draft: 0, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 32,
+ name: 'David Cohen',
+ initials: 'DC',
+ email: 'david.cohen@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'admin',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Croatian - Hrvatski',
+ languageDirection: 'ltr',
+ locale: 'hr',
+ bibleLabel: 'Biblija',
+ translationProgress: [
+ { name: 'Interface', done: 70, draft: 5, total: 100 },
+ { name: 'Content', done: 210, draft: 10, total: 300 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Hungarian - Magyar',
+ languageDirection: 'ltr',
+ locale: 'hu',
+ bibleLabel: 'Károli Biblia',
+ translationProgress: [
+ { name: 'Interface', done: 85, draft: 0, total: 100 },
+ { name: 'Content', done: 255, draft: 5, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 33,
+ name: 'Zsófia Nagy',
+ initials: 'ZN',
+ email: 'zsofia.nagy@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Armenian - Հայերեն',
+ languageDirection: 'ltr',
+ locale: 'hy',
+ bibleLabel: 'Աստվածաշունչ',
+ translationProgress: [
+ { name: 'Interface', done: 40, draft: 15, total: 100 },
+ { name: 'Content', done: 120, draft: 30, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 34,
+ name: 'Armen Petrosyan',
+ initials: 'AP',
+ email: 'armen.petrosyan@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Indonesian - Bahasa Indonesia',
+ languageDirection: 'ltr',
+ locale: 'id',
+ bibleLabel: 'Alkitab Terjemahan Baru',
+ translationProgress: [
+ { name: 'Interface', done: 100, draft: 0, total: 100 },
+ { name: 'Content', done: 300, draft: 0, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 35,
+ name: 'Budi Santoso',
+ initials: 'BS',
+ email: 'budi.santoso@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ {
+ id: 36,
+ name: 'Siti Wijaya',
+ initials: 'SW',
+ email: 'siti.wijaya@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'translator',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
+ {
+ language: 'Icelandic - Íslenska',
+ languageDirection: 'ltr',
+ locale: 'is',
+ bibleLabel: 'Biblían',
+ translationProgress: [
+ { name: 'Interface', done: 33, draft: 7, total: 100 },
+ { name: 'Content', done: 99, draft: 11, total: 300 },
+ ],
+ teamMembers: [],
+ },
+ {
+ language: 'Georgian - ქართული',
+ languageDirection: 'ltr',
+ locale: 'ka',
+ bibleLabel: 'ბიბლია',
+ translationProgress: [
+ { name: 'Interface', done: 52, draft: 8, total: 100 },
+ { name: 'Content', done: 156, draft: 12, total: 300 },
+ ],
+ teamMembers: [
+ {
+ id: 37,
+ name: 'Nino Beridze',
+ initials: 'NB',
+ email: 'nino.beridze@example.com',
+ isManager: false,
+ isAdmin: false,
+ role: 'editor',
+ language: null,
+ hasPendingInvite: false,
+ isAllowed: isAllowedMock(true),
+ },
+ ],
+ },
];
export const config: UiConfig = {
diff --git a/src/types.ts b/src/types.ts
index d458f410..1afb1741 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -635,7 +635,7 @@ export interface LanguageSpecification {
export interface LanguageTableItem extends LanguageSpecification {
translationProgress?: Omit[];
- teamMembers?: UserInterface[];
+ teamMembers?: UserMeta[];
}
export interface Providers {
@@ -673,7 +673,7 @@ export interface Providers {
/// ----------------------------------------------------
export interface SettingsPageProps {
- sourceLanguage: LanguageSpecification;
+ sourceLanguage: LanguageTableItem;
languageItems: LanguageTableItem[];
providers: Providers;
requireAppUpdate: boolean;