Skip to content

Commit cf3711c

Browse files
authored
feat: [v2] cms ui updates for translations v2 (#422)
1 parent 146bd92 commit cf3711c

File tree

21 files changed

+1282
-500
lines changed

21 files changed

+1282
-500
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import PagesSchema from './Pages.schema.js';
2+
3+
export default {
4+
...PagesSchema,
5+
name: 'Pages [SANDBOX]',
6+
description: 'Sandbox Pages',
7+
url: '/sandbox/[...slug]',
8+
};

docs/layouts/BaseLayout.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import {Body, Head, Html, Script, useTranslations} from '@blinkk/root';
1+
import {
2+
Body,
3+
Head,
4+
Html,
5+
Script,
6+
useRequestContext,
7+
useTranslations,
8+
} from '@blinkk/root';
29
import {ComponentChildren} from 'preact';
310
import {GlobalFooter} from '@/components/GlobalFooter/GlobalFooter.js';
411
import {GlobalHeader} from '@/components/GlobalHeader/GlobalHeader.js';
@@ -39,9 +46,10 @@ export function BaseLayout(props: BaseLayoutProps) {
3946
width: 1200,
4047
jpg: true,
4148
});
49+
const ctx = useRequestContext();
4250

4351
return (
44-
<Html>
52+
<Html lang={ctx.locale}>
4553
<Head>
4654
<title>{t(title)}</title>
4755
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

docs/root-cms.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,27 @@ export interface PagesFields {
196196
/** Generated from `/collections/Pages.schema.ts`. */
197197
export type PagesDoc = RootCMSDoc<PagesFields>;
198198

199+
/** Generated from `/collections/PagesSandbox.schema.ts`. */
200+
export interface PagesSandboxFields {
201+
/** Meta */
202+
meta?: {
203+
/** Title. Page title. */
204+
title?: string;
205+
/** Description. Description for SEO and social shares. */
206+
description?: string;
207+
/** Image. Meta image for social shares. Recommended: 1400x600 JPG. */
208+
image?: RootCMSImage;
209+
};
210+
/** Content */
211+
content?: {
212+
/** Modules. Compose the page by adding one or more modules. */
213+
modules?: RootCMSOneOf[];
214+
};
215+
}
216+
217+
/** Generated from `/collections/PagesSandbox.schema.ts`. */
218+
export type PagesSandboxDoc = RootCMSDoc<PagesSandboxFields>;
219+
199220
/** Generated from `/components/Button/Button.schema.ts`. */
200221
export interface ButtonFields {
201222
/** Button Options */

docs/routes/sandbox/[sandbox].tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {cmsRoute} from '@/utils/cms-route.js';
2+
import Page from '../[[...page]].js';
3+
4+
export default Page;
5+
6+
export const {handle} = cmsRoute({
7+
collection: 'PagesSandbox',
8+
slugParam: 'sandbox',
9+
});

packages/root-cms/core/client.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ export interface TranslationsDoc {
4242
sys: {
4343
modifiedAt: Timestamp;
4444
modifiedBy: string;
45+
publishedAt?: Timestamp;
46+
publishedBy?: string;
47+
linkedSheet?: {
48+
spreadsheetId: string;
49+
gid: number;
50+
linkedAt: Timestamp;
51+
linkedBy: string;
52+
};
4553
};
4654
strings: TranslationsMap;
4755
}
@@ -631,31 +639,39 @@ export class RootCMSClient {
631639
}
632640

633641
/**
634-
* Loads translations saved in the translations collection, optionally
635-
* filtered by tag.
642+
* Loads all published strings in the db.
636643
*
637-
* Returns a map like:
638644
* ```
639645
* {
640646
* "<hash>": {"source": "Hello", "es": "Hola", "fr": "Bonjour"},
641647
* }
642648
* ```
649+
*
650+
* @deprecated Use `createBatchRequest()` to fetch draft/published translations.
643651
*/
644652
async loadTranslations(
645653
options?: LoadTranslationsOptions
646654
): Promise<TranslationsMap> {
647-
const dbPath = `Projects/${this.projectId}/Translations`;
648-
let query: Query = this.db.collection(dbPath);
649-
if (options?.tags) {
650-
query = query.where('tags', 'array-contains-any', options.tags);
651-
}
652-
653-
const querySnapshot = await query.get();
655+
const dbPath = `Projects/${this.projectId}/TranslationsManager/published/Translations`;
656+
const query: Query = this.db.collection(dbPath);
657+
const snapshot = await query.get();
654658
const translationsMap: TranslationsMap = {};
655-
querySnapshot.forEach((doc) => {
656-
const hash = doc.id;
657-
translationsMap[hash] = doc.data() as Translation;
659+
660+
const addTranslations = (hash: string, translations: Translation) => {
661+
translationsMap[hash] = Object.assign(
662+
translationsMap[hash] || {},
663+
translations
664+
);
665+
};
666+
667+
snapshot.forEach((doc) => {
668+
const data = doc.data();
669+
const strings = (data.strings || {}) as Record<string, Translation>;
670+
Object.entries(strings).forEach(([hash, translations]) => {
671+
addTranslations(hash, translations);
672+
});
658673
});
674+
659675
return translationsMap;
660676
}
661677

@@ -666,6 +682,9 @@ export class RootCMSClient {
666682
* "Hello": {"es": "Hola", "fr": "Bonjour"},
667683
* });
668684
* ```
685+
*
686+
* @deprecated Use `saveDraftTranslations()` and `publishTranslations()`
687+
* instead.
669688
*/
670689
async saveTranslations(
671690
translations: {
@@ -695,6 +714,35 @@ export class RootCMSClient {
695714
await batch.commit();
696715
}
697716

717+
async saveDraftTranslations(
718+
translationsId: string,
719+
translations: {[source: string]: {[locale: string]: string}},
720+
options?: {modifiedby?: string}
721+
) {
722+
const docSlug = translationsId.replaceAll('/', '--');
723+
const docPath = `Projects/${this.projectId}/TranslationsManager/draft/${docSlug}`;
724+
const docRef = this.db.doc(docPath);
725+
726+
const snapshot = await docRef.get();
727+
const data = snapshot.data() || {
728+
id: translationsId.replaceAll('--', '/'),
729+
sys: {},
730+
strings: {},
731+
};
732+
data.sys.modifiedAt = Timestamp.now();
733+
data.sys.modifiedBy = options?.modifiedby || 'root-cms-client';
734+
735+
const strings = data.strings || {};
736+
Object.entries(translations).forEach(([source, row]) => {
737+
const normalizedSource = this.normalizeString(source);
738+
const hash = this.getTranslationKey(normalizedSource);
739+
strings[hash] = {...strings[hash], ...row, source: normalizedSource};
740+
});
741+
data.strings = strings;
742+
await docRef.set(data);
743+
this.logAction('translations.save', {metadata: {translationsId}});
744+
}
745+
698746
/**
699747
* Returns the "key" used for a translation as stored in the db. Translations
700748
* are stored under `Projects/<project id>/Translations/<sha1 hash>`.
@@ -1467,6 +1515,7 @@ export function translationsForLocale(
14671515
for (const fallbackLocale of fallbackLocales) {
14681516
if (string[fallbackLocale]) {
14691517
translation = string[fallbackLocale];
1518+
break;
14701519
}
14711520
}
14721521
localeTranslations[source] = translation;
Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {RootConfig} from '@blinkk/root';
2-
import {FieldValue} from 'firebase-admin/firestore';
3-
import type {CMSPlugin} from './plugin.js';
2+
import {Timestamp} from 'firebase-admin/firestore';
3+
import {RootCMSClient, Translation, TranslationsDoc} from './client.js';
44

55
/**
66
* Runs compatibility checks to ensure the current version of root supports
@@ -11,12 +11,10 @@ import type {CMSPlugin} from './plugin.js';
1111
* latest version, a backwards-friendly "migration" is done to ensure
1212
* compatibility on both the old and new versions.
1313
*/
14-
export async function runCompatibilityChecks(
15-
rootConfig: RootConfig,
16-
cmsPlugin: CMSPlugin
17-
) {
18-
const projectId = cmsPlugin.getConfig().id || 'default';
19-
const db = cmsPlugin.getFirestore();
14+
export async function runCompatibilityChecks(rootConfig: RootConfig) {
15+
const cmsClient = new RootCMSClient(rootConfig);
16+
const projectId = cmsClient.projectId;
17+
const db = cmsClient.db;
2018
const projectConfigDocRef = db.doc(`Projects/${projectId}`);
2119
const projectConfigDoc = await projectConfigDocRef.get();
2220
const projectConfig = projectConfigDoc.data() || {};
@@ -26,7 +24,7 @@ export async function runCompatibilityChecks(
2624

2725
const translationsVersion = compatibilityVersions.translations || 0;
2826
if (translationsVersion < 2) {
29-
await migrateTranslationsToV2(rootConfig, cmsPlugin);
27+
await migrateTranslationsToV2(rootConfig, cmsClient);
3028
compatibilityVersions.translations = 2;
3129
versionsChanged = true;
3230
}
@@ -42,14 +40,14 @@ export async function runCompatibilityChecks(
4240
*/
4341
async function migrateTranslationsToV2(
4442
rootConfig: RootConfig,
45-
cmsPlugin: CMSPlugin
43+
cmsClient: RootCMSClient
4644
) {
4745
if (rootConfig.experiments?.rootCmsDisableTranslationsToV2Check) {
4846
return;
4947
}
5048

51-
const projectId = cmsPlugin.getConfig().id || 'default';
52-
const db = cmsPlugin.getFirestore();
49+
const projectId = cmsClient.projectId;
50+
const db = cmsClient.db;
5351
const dbPath = `Projects/${projectId}/Translations`;
5452
const query = db.collection(dbPath);
5553
const querySnapshot = await query.get();
@@ -58,42 +56,65 @@ async function migrateTranslationsToV2(
5856
}
5957

6058
console.log('[root cms] updating translations v2 compatibility');
61-
const translationsFiles: Record<string, Record<string, any>> = {};
59+
60+
const translationsDocs: Record<string, TranslationsDoc> = {};
6261
querySnapshot.forEach((doc) => {
6362
const hash = doc.id;
64-
const translation = doc.data();
63+
const translation = doc.data() as Translation;
6564
const tags = translation.tags || [];
6665
delete translation.tags;
6766
for (const tag of tags) {
6867
if (tag.includes('/')) {
69-
const translationsId = tag.replaceAll('/', '--');
70-
translationsFiles[translationsId] ??= {};
71-
translationsFiles[translationsId][hash] = translation;
68+
const translationsId = tag;
69+
translationsDocs[translationsId] ??= {
70+
id: translationsId,
71+
sys: {
72+
modifiedAt: Timestamp.now(),
73+
modifiedBy: 'root-cms-client',
74+
publishedAt: Timestamp.now(),
75+
publishedBy: 'root-cms-client',
76+
},
77+
strings: {},
78+
};
79+
translationsDocs[translationsId].strings[hash] = translation;
7280
}
7381
}
7482
});
7583

84+
if (Object.keys(translationsDocs).length === 0) {
85+
console.log('[root cms] no translations to save');
86+
return;
87+
}
88+
89+
// Move the doc's "l10nSheet" to the translations doc's "linkedSheet".
90+
91+
for (const docId in translationsDocs) {
92+
const [collection, slug] = docId.split('/');
93+
if (collection && slug) {
94+
const doc: any = await cmsClient.getDoc(collection, slug, {
95+
mode: 'draft',
96+
});
97+
const linkedSheet = doc?.sys?.l10nSheet;
98+
if (linkedSheet) {
99+
translationsDocs[docId].sys.linkedSheet = linkedSheet;
100+
}
101+
}
102+
}
103+
76104
const batch = db.batch();
77-
Object.entries(translationsFiles).forEach(([translationsId, strings]) => {
78-
const updates = {
79-
id: translationsId.replaceAll('--', '/'),
80-
sys: {
81-
modifiedAt: FieldValue.serverTimestamp(),
82-
modifiedBy: 'root-cms-client',
83-
},
84-
strings: strings,
85-
};
105+
Object.entries(translationsDocs).forEach(([translationsId, data]) => {
86106
const draftRef = db.doc(
87107
`Projects/${projectId}/TranslationsManager/draft/Translations/${translationsId}`
88108
);
89109
const publishedRef = db.doc(
90110
`Projects/${projectId}/TranslationsManager/published/Translations/${translationsId}`
91111
);
92-
batch.set(draftRef, updates, {merge: true});
93-
batch.set(publishedRef, updates, {merge: true});
94-
const len = Object.keys(strings).length;
112+
batch.set(draftRef, data, {merge: true});
113+
batch.set(publishedRef, data, {merge: true});
114+
const len = Object.keys(data.strings).length;
95115
console.log(`[root cms] saving ${len} string(s) to ${translationsId}...`);
96116
});
97117
await batch.commit();
118+
98119
console.log('[root cms] done migrating translations to v2');
99120
}

packages/root-cms/core/plugin.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,7 @@ export function cmsPlugin(options: CMSPluginOptions): CMSPlugin {
454454
*/
455455
startup: async ({command, rootConfig}) => {
456456
if (command === 'dev' || command === 'build') {
457-
const cmsPlugin = getCmsPlugin(rootConfig);
458-
await runCompatibilityChecks(rootConfig, cmsPlugin);
457+
await runCompatibilityChecks(rootConfig);
459458
}
460459
},
461460
},

0 commit comments

Comments
 (0)