Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLB-269: finish multilingual features #139

Merged
merged 36 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
015df10
feat(SLB-269): add locale arguments to content hub schema
pmelab Feb 7, 2024
f124738
fix: clear content hub queries on all node changes
pmelab Feb 7, 2024
7bdb805
feat(SLB-269): implement content hub locale filter in drupal
pmelab Feb 7, 2024
c8d4461
feat(SLB-269): pass current locale into content hub operation
pmelab Feb 7, 2024
7835ca2
feat(SLB-269): render a content hub page for each language
pmelab Feb 7, 2024
164d30a
test(SLB-269): multilingual content hub integration test
pmelab Feb 7, 2024
c4e212f
feat(SLB-269): translations context for dynamically updating the lang…
pmelab Feb 8, 2024
ddb5bd5
feat(SLB-269): add language switcher component
pmelab Feb 8, 2024
2c185c4
feat(SLB-269): initialize the language switcher from routes
pmelab Feb 8, 2024
b7572b4
refactor: remove leftover debug log
pmelab Feb 8, 2024
ba6c2c6
test(SLB-269): language switcher integration tests
pmelab Feb 8, 2024
bfe1a89
test: add content hub full page story
pmelab Feb 8, 2024
8fe8341
fix: wrong footer navigation
pmelab Feb 8, 2024
7150934
test: menu integration tests
pmelab Feb 8, 2024
df69c17
chore(SLB-269): upgrade @amazeelabs/gatsby-source-silverback
pmelab Feb 8, 2024
3282caf
test(SLB-269): example data for a multilingual decap page
pmelab Feb 8, 2024
e4015b5
fix(SLB-269): make decap page sourcing compatible to multilingual pages
pmelab Feb 8, 2024
403c2fb
feat(SLB-269): gatsby directive to retrieve decap translations
pmelab Feb 8, 2024
deb45ea
test(SLB-269): integration tests for translated decap pages
pmelab Feb 8, 2024
ccb4a12
feat(SLB-269): import translations when prepping database
pmelab Feb 10, 2024
0e42a37
feat(SLB-269): translate interface strings in decap
pmelab Feb 13, 2024
30ad655
feat(SLB-269): graphql schema for translatable strings
pmelab Feb 13, 2024
faab9ad
refactor: upgrade graphql directives module and use simpler @value di…
pmelab Feb 13, 2024
ab1e6fe
feat(SLB-269): source translatable strings into gatsby datastore
pmelab Feb 13, 2024
da0e975
feat(SLB-269): inject translation strings into ui
pmelab Feb 13, 2024
4d663d7
test(SLB-269): integration tests for translation strings
pmelab Feb 13, 2024
cc66d7a
refactor: fix wrong variable naming in menu integration test
pmelab Feb 13, 2024
9379a3f
test(SLB-269): integration tests for translation strings
pmelab Feb 13, 2024
5e4447d
chore(SLB-269): update @amazeelabs/gatsby-source-silverback
pmelab Feb 16, 2024
7d5fc1e
chore(SLB-269): add temporary copies of new directives
pmelab Feb 16, 2024
3cf2db1
feat(SLB-269): source and render drupal translation strings
pmelab Feb 16, 2024
4acc173
test(SLB-269): test case for translation strings from drupal
pmelab Feb 16, 2024
378c95c
chore: lockfile update after rebase
pmelab Feb 16, 2024
ec9e50e
fix: missing directive declaration file
pmelab Feb 16, 2024
55e1dd6
refactor(SLB-269): switch to shared @translatableString directive
pmelab Feb 16, 2024
d033d5d
refactor(SLB-269): switch to shared @translatableString directive
pmelab Feb 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cms/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"php": "^8.2 <8.3",
"amazeeio/drupal_integrations": "^0.3.7",
"amazeelabs/default-content": "^1.2.11",
"amazeelabs/graphql_directives": "^2.1",
"amazeelabs/graphql_directives": "^2.4",
"amazeelabs/proxy-drupal-core": "^1.1.12",
"amazeelabs/proxy-graphql": "^1.1.7",
"amazeelabs/proxy-gutenberg": "^1.3",
Expand Down
29 changes: 16 additions & 13 deletions apps/cms/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions apps/cms/prep-database.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
if [ ! -z $LAGOON ]
then
if [ ! -z $LAGOON ]; then
# Do not touch database on Lagoon
exit 0
fi

if ! test -f web/sites/default/files/.sqlite
then
if ! test -f web/sites/default/files/.sqlite; then
pnpm drupal-install
pnpm export-webforms
else
pnpm drush cr
fi

# In any case, re-import translation string.
pnpm import-translations
pnpm drush cr
Leksat marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 12 additions & 2 deletions apps/cms/scripts/translations-import.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<?php
/** @var \Drupal\silverback_translations\TranslationsProcessorInterface $processor */

/**
* @file
* Import translation strings from the ui package.
*/

/**
* The translations processor for importing.
*
* @var \Drupal\silverback_translations\TranslationsProcessorInterface $processor.
*/
$processor = \Drupal::service('silverback_translations.json_processor');
$sources = file_get_contents('../generated/translations.json');
$sources = file_get_contents('../node_modules/@custom/ui/build/translatables.json');
$processor->createSources($sources, 'gatsby');
16 changes: 13 additions & 3 deletions apps/decap/data/page/decap-example.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
en:
id: BTPzQnq79fWNOF1dFmESP
path: /decap-example
path: /en/decap-example
title: Decap example
teaserImage: /apps/decap/media/landscape.jpg
hero:
headline: Decap
headline: Decap Example
lead: This page was created with Decap CMS
image: /apps/decap/media/landscape.jpg
content:
Expand All @@ -14,4 +14,14 @@ en:
caption: Here is an ***image***, for good measure.
alt: Some architecture.
image: /apps/decap/media/portrait.jpg
de: {}
de:
path: /de/decap-example
title: Decap Beispiel
teaserImage: /apps/decap/media/landscape.jpg
hero:
headline: Decap Beispiel
lead: Diese Seite wurde mit Decap CMS erstellt
image: /apps/decap/media/landscape.jpg
content:
- type: text
text: Dieser Inhalt wird in einer **JSON** Datei gespeichert.
13 changes: 13 additions & 0 deletions apps/decap/data/translatables.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
de:
qA8qQH: '{year} {company_name}. Alle Rechte vorbehalten.'
stJYEY: '{current} von {total}'
JJNc3c: Zurück
e7yFQY: Menü öffnen
FPGwAt: Random Company
SRsuWF: Menü schliessen
9+Ddtu: Weiter
fe0rMF: Suchbegriff
jHJmjf: Keine Ergebnisse
xmcVZ0: Suchen
en:
FPGwAt: Random Company
14 changes: 12 additions & 2 deletions apps/decap/src/collections/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,22 +193,30 @@ export const pageSchema = z.object({

export const getPages: (dir: string) => SilverbackSource<DecapPageSource> =
(dir: string) => () => {
const pages: Array<[string, DecapPageSource]> = [];
const pages: Array<[string, DecapPageSource & { _decap_id: string }]> = [];
fs.readdirSync(dir)
.filter((file) => file.endsWith('.yml'))
.forEach((file) => {
const content = yaml.parse(fs.readFileSync(`${dir}/${file}`, 'utf-8'));
const id = Object.values(content)
.map((page: any) => page.id)
.filter((id) => !!id)
.pop();
Object.keys(content).forEach((lang) => {
if (Object.keys(content[lang]).length < 2) {
return;
}
const input = {
...content[lang],
id,
locale: lang,
};
const page = pageSchema.safeParse(input);
if (page.success) {
pages.push([page.data.id, page.data]);
pages.push([
`${page.data.id}:${lang}`,
{ ...page.data, _decap_id: id },
]);
} else {
console.warn(`Error parsing ${file} (${lang}):`);
console.warn(page.error.message);
Expand All @@ -218,3 +226,5 @@ export const getPages: (dir: string) => SilverbackSource<DecapPageSource> =
});
return pages;
};

export function getPageTranslations() {}
13 changes: 13 additions & 0 deletions apps/decap/src/collections/translatables.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { dirname, resolve } from 'node:path';
import { afterEach } from 'node:test';

import { expect, test, vi } from 'vitest';

import { getTranslatables } from './translatables';

afterEach(vi.resetAllMocks);

test('getTranslatables', () => {
const dir = resolve(dirname(new URL(import.meta.url).pathname), '../../data');
expect(getTranslatables(dir)).not.toThrow();
});
77 changes: 77 additions & 0 deletions apps/decap/src/collections/translatables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { SilverbackSource } from '@amazeelabs/gatsby-source-silverback';
import { Locale } from '@custom/schema';
import { DecapTranslatableStringSource } from '@custom/schema/source';
import { CmsCollection } from 'decap-cms-core';
import fs from 'fs';
import yaml from 'yaml';
import { z } from 'zod';

import rawTranslatables from '../../node_modules/@custom/ui/build/translatables.json';

// Validate that translatables.json contains what we expect.
const translationSources = z
.record(
z.object({
defaultMessage: z.string(),
}),
)
.transform((data) =>
Object.fromEntries(
Object.keys(data).map((key) => [key, data[key].defaultMessage]),
),
)
.parse(rawTranslatables);

export const Translatables: CmsCollection = {
label: 'Translations',
name: 'translations',
i18n: true,
files: [
{
label: 'Translatables',
name: 'Translatables',
description: 'Translate user interface texts',
file: 'apps/decap/data/translatables.yml',
i18n: true,
fields: Object.keys(translationSources).map((key) => ({
name: key,
label: translationSources[key],
widget: 'string',
i18n: true,
required: false,
})),
},
],
};

export function getTranslatables(
dir: string,
): SilverbackSource<DecapTranslatableStringSource> {
return function () {
const rawTranslations = yaml.parse(
fs.readFileSync(`${dir}/translatables.yml`, 'utf-8'),
);
return z
.record(z.record(z.string()))
.transform((data) => {
const translations: Array<[string, DecapTranslatableStringSource]> = [];
Object.keys(data).forEach((langcode) => {
Object.keys(data[langcode]).forEach((key) => {
Object.keys(data).forEach((locale) => {
translations.push([
`${key}:${locale}`,
{
__typename: 'DecapTranslatableString',
source: translationSources[key],
language: locale as Locale,
translation: data[locale][key],
},
]);
});
});
});
return translations;
})
.parse(rawTranslations);
};
}
1 change: 1 addition & 0 deletions apps/decap/src/helpers/frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function PreviewFrame({ children }: PropsWithChildren) {
items: menuItems(4),
},
],
stringTranslations: [],
}));
return <Frame>{children}</Frame>;
}
1 change: 1 addition & 0 deletions apps/decap/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { getPages } from './collections/page.js';
export { getTranslatables } from './collections/translatables.js';
10 changes: 8 additions & 2 deletions apps/decap/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Locale,
PreviewDecapPageQuery,
registerExecutor,
ViewPageQuery,
Expand All @@ -8,9 +9,13 @@ import CMS from 'decap-cms-app';

import css from '../node_modules/@custom/ui/build/styles.css?raw';
import { PageCollection, pageSchema } from './collections/page';
import { Translatables } from './collections/translatables';
import { createPreview } from './helpers/preview';
import { UuidWidget } from './helpers/uuid';

const locales = Object.values(Locale);
const default_locale = locales.includes('en') ? 'en' : locales[0];

CMS.registerPreviewStyle(css, { raw: true });
CMS.registerWidget('uuid', UuidWidget);

Expand All @@ -36,8 +41,8 @@ CMS.init({
},
i18n: {
structure: 'single_file',
locales: ['en', 'de'],
default_locale: 'en',
locales,
default_locale,
},
collections: [
{
Expand All @@ -59,6 +64,7 @@ CMS.init({
},
],
},
Translatables,
PageCollection,
],
},
Expand Down
9 changes: 6 additions & 3 deletions apps/website/gatsby-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
// TS file name should be different from gastby-config.ts, otherwise Gatsby will
// pick it up instead of the JS file.

import { getPages } from '@custom/decap';
import { getPages, getTranslatables } from '@custom/decap';
import autoload from '@custom/schema/gatsby-autoload';
import { resolve } from 'path';

const dir = resolve('node_modules/@custom/decap/data/page');
const dir = resolve('node_modules/@custom/decap/data');

process.env.GATSBY_DRUPAL_URL =
process.env.DRUPAL_EXTERNAL_URL || 'http://127.0.0.1:8888';
Expand Down Expand Up @@ -71,7 +71,10 @@ export default {
type_prefix: '',
schema_configuration: './graphqlrc.yml',
directives: autoload,
sources: { getPages: getPages(dir) },
sources: {
getPages: getPages(`${dir}/page`),
getTranslatables: getTranslatables(dir),
},
},
},
{
Expand Down
8 changes: 8 additions & 0 deletions apps/website/gatsby-node.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ export const createPages = async ({ actions }) => {
});
});

// Create the content hub page in each language.
Object.values(Locale).forEach((locale) => {
actions.createPage({
path: `/${locale}/content-hub`,
component: resolve(`./src/templates/content-hub.tsx`),
});
});

// Broken Gatsby links will attempt to load page-data.json files, which don't exist
// and also should not be piped into the strangler function. Thats why they
// are caught right here.
Expand Down
Loading
Loading