Skip to content

Commit bebce77

Browse files
committed
feat(translation): connect with ifrc translation service
1 parent 784d64c commit bebce77

30 files changed

+1506
-1215
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ env:
1313
APP_RISK_API_ENDPOINT: 'https://go-risk-staging.northeurope.cloudapp.azure.com/api/v1/'
1414
APP_TINY_API_KEY: 'dummy-api-key'
1515
APP_TITLE: 'IFRC Go Test'
16+
APP_TRANSLATION_API_ENDPOINT: 'https://ifrc-translationapi.azurewebsites.net/'
1617

1718
concurrency:
1819
group: ${{ github.workflow }}-${{ github.ref }}

app/env.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export default defineConfig({
1616
return value as ('production' | 'staging' | 'testing' | `alpha-${number}` | 'development' | 'APP_ENVIRONMENT_PLACEHOLDER');
1717
},
1818
APP_API_ENDPOINT: Schema.string({ format: 'url', protocol: true, tld: false }),
19+
20+
APP_TRANSLATION_API_ENDPOINT: Schema.string({ format: 'url', protocol: true, tld: false }),
21+
APP_TRANSLATION_API_KEY: Schema.string(),
22+
1923
APP_ADMIN_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }),
2024
APP_MAPBOX_ACCESS_TOKEN: Schema.string(),
2125
APP_TINY_API_KEY: Schema.string(),

app/package.json

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313
"translatte": "tsx scripts/translatte/main.ts",
1414
"translatte:generate": "pnpm translatte generate-migration ../translationMigrations ./src/**/i18n.json ../packages/ui/src/**/i18n.json",
1515
"translatte:lint": "pnpm translatte lint ./src/**/i18n.json ../packages/ui/src/**/i18n.json",
16-
"initialize:type": "mkdir -p generated/ && pnpm initialize:type:go-api && pnpm initialize:type:risk-api",
16+
"translatte:lint-migrations": "pnpm translatte lint-migrations ../translationMigrations",
17+
"initialize:type": "mkdir -p generated/ && pnpm initialize:type:go-api && pnpm initialize:type:risk-api && pnpm initialize:type:translations",
1718
"initialize:type:go-api": "test -f ./generated/types.ts && true || cp types.stub.ts ./generated/types.ts",
1819
"initialize:type:risk-api": "test -f ./generated/riskTypes.ts && true || cp types.stub.ts ./generated/riskTypes.ts",
19-
"generate:type": "pnpm generate:type:go-api && pnpm generate:type:risk-api",
20+
"initialize:type:translations": "test -f ./generated/translationTypes.ts && true || cp types.stub.ts ./generated/translationTypes.ts",
21+
"generate:type": "pnpm generate:type:go-api && pnpm generate:type:risk-api && pnpm generate:type:translations",
2022
"generate:type:go-api": "openapi-typescript ../go-api/assets/openapi-schema.yaml -o ./generated/types.ts --alphabetize",
21-
"generate:type:risk-api": "dotenv -- cross-var openapi-typescript ../go-risk-module-api/openapi-schema.yaml -o ./generated/riskTypes.ts --alphabetize",
23+
"generate:type:risk-api": "openapi-typescript ../go-risk-module-api/openapi-schema.yaml -o ./generated/riskTypes.ts --alphabetize",
24+
"generate:type:translations": "dotenv -- cross-var openapi-typescript \"%APP_TRANSLATION_API_ENDPOINT%swagger/v1/swagger.json\" -o ./generated/translationTypes.ts --alphabetize",
25+
"postgenerate:type:translations": "tsx scripts/fix-generated.ts",
2226
"prestart": "pnpm initialize:type",
2327
"start": "pnpm -F @ifrc-go/ui build && vite",
2428
"prebuild": "pnpm initialize:type",
@@ -29,7 +33,7 @@
2933
"prelint:js": "pnpm initialize:type",
3034
"lint:js": "eslint src",
3135
"lint:css": "stylelint \"./src/**/*.css\"",
32-
"lint:translation": "pnpm translatte:lint",
36+
"lint:translation": "pnpm translatte:lint && pnpm translatte:lint-migrations",
3337
"lint": "pnpm lint:js && pnpm lint:css && pnpm lint:translation",
3438
"lint:fix": "pnpm lint:js --fix && pnpm lint:css --fix",
3539
"test": "vitest",
@@ -48,7 +52,7 @@
4852
"@togglecorp/toggle-request": "^1.0.0-beta.3",
4953
"@turf/bbox": "^6.5.0",
5054
"@turf/buffer": "^6.5.0",
51-
"exceljs": "^4.3.0",
55+
"exceljs": "^4.4.0",
5256
"file-saver": "^2.0.5",
5357
"html-to-image": "^1.11.11",
5458
"mapbox-gl": "^1.13.0",
@@ -57,7 +61,8 @@
5761
"react": "^18.2.0",
5862
"react-dom": "^18.2.0",
5963
"react-router-dom": "^6.18.0",
60-
"sanitize-html": "^2.10.0"
64+
"sanitize-html": "^2.10.0",
65+
"xlsx": "^0.18.5"
6166
},
6267
"devDependencies": {
6368
"@eslint/eslintrc": "^3.2.0",

app/scripts/fix-generated.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { readFileSync, writeFileSync } from 'fs';
2+
3+
const path = 'generated/translationTypes.ts';
4+
5+
const content = readFileSync(path, 'utf-8');
6+
7+
// If already added, skip
8+
writeFileSync(path, `// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-nocheck\n${content}`);
9+
console.log('✔ Added // @ts-nocheck to translationTypes.ts');

app/scripts/translatte/commands/applyMigrations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ async function applyMigrations(
129129

130130
const migrationFilesAttrs = await getMigrationFilesAttrs(projectPath, migrationFilePath);
131131
const selectedMigrationFilesAttrs = from
132-
? migrationFilesAttrs.filter((item) => (item.migrationName > from))
132+
? migrationFilesAttrs.filter((item) => (item.migrationFileName > from))
133133
: migrationFilesAttrs;
134134

135135
console.info(`Found ${selectedMigrationFilesAttrs.length} migration files`);
@@ -139,7 +139,7 @@ async function applyMigrations(
139139
}
140140

141141
const selectedMigrations = await readMigrations(
142-
selectedMigrationFilesAttrs.map((migration) => migration.fileName),
142+
selectedMigrationFilesAttrs.map((migration) => migration.filePath),
143143
);
144144

145145
const lastMigration = selectedMigrations[selectedMigrations.length - 1];

app/scripts/translatte/commands/generateMigration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ async function generate(
132132
const selectedMigrationFilesAttrs = migrationFilesAttrs;
133133
console.info(`Found ${selectedMigrationFilesAttrs.length} migration files`);
134134
const selectedMigrations = await readMigrations(
135-
selectedMigrationFilesAttrs.map((migration) => migration.fileName),
135+
selectedMigrationFilesAttrs.map((migration) => migration.filePath),
136136
);
137137
const mergedMigrationActions = merge(
138138
selectedMigrations.map((migration) => migration.content),
@@ -170,7 +170,7 @@ async function generate(
170170
const lastMigration = migrationFilesAttrs[migrationFilesAttrs.length - 1];
171171

172172
const migrationContent: MigrationFileContent = {
173-
parent: lastMigration?.migrationName,
173+
parent: lastMigration?.migrationFileName,
174174
actions: migrationActionItems,
175175
}
176176

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { listToGroupList } from '@togglecorp/fujs';
2+
import { getMigrationFilesAttrs } from '../utils';
3+
4+
async function lintMigrations(projectPath: string, path: string) {
5+
const migrationFileAttrs = await getMigrationFilesAttrs(projectPath, path);
6+
console.info(`Found ${migrationFileAttrs.length} migration files.`);
7+
8+
const migrationGroups = listToGroupList(
9+
migrationFileAttrs,
10+
({ migrationName }) => migrationName,
11+
);
12+
13+
const duplicates = Object.values(migrationGroups).filter((group) => group.length > 1);
14+
15+
if (duplicates.length > 0) {
16+
const duplicateStr = duplicates.map((duplicate) => (
17+
duplicate.map(({ migrationName }) => migrationName).join(' <> ')
18+
)).join('\n');
19+
20+
console.info(duplicateStr);
21+
22+
throw `Error: found divirging migrations!`;
23+
}
24+
25+
console.info('All good! No divirging migrations!');
26+
}
27+
28+
export default lintMigrations;

app/scripts/translatte/commands/mergeMigrations.ts

Lines changed: 5 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,12 @@
1-
import { listToMap, isDefined } from '@togglecorp/fujs';
2-
31
import { MigrationActionItem, MigrationFileContent } from '../types';
42
import {
5-
concat,
6-
removeUndefinedKeys,
73
getMigrationFilesAttrs,
84
readMigrations,
95
removeFiles,
10-
writeFilePromisify
6+
writeFilePromisify,
7+
mergeMigrationActionItems
118
} from '../utils';
129

13-
function getCanonicalKey(
14-
item: MigrationActionItem,
15-
opts: { useNewKey: boolean },
16-
) {
17-
if (opts.useNewKey && item.action === 'update') {
18-
return concat(
19-
item.newNamespace ?? item.namespace,
20-
item.newKey ?? item.key,
21-
);
22-
}
23-
return concat(
24-
item.namespace,
25-
item.key,
26-
);
27-
}
28-
29-
function mergeMigrationActionItems(
30-
prevMigrationActionItems: MigrationActionItem[],
31-
nextMigrationActionItems: MigrationActionItem[],
32-
) {
33-
interface PrevMappings {
34-
[key: string]: MigrationActionItem,
35-
}
36-
37-
const prevCanonicalKeyMappings: PrevMappings = listToMap(
38-
prevMigrationActionItems,
39-
(item) => getCanonicalKey(item, { useNewKey: true }),
40-
(item) => item,
41-
);
42-
43-
interface NextMappings {
44-
[key: string]: MigrationActionItem | null,
45-
}
46-
47-
const nextMappings = nextMigrationActionItems.reduce<NextMappings>(
48-
(acc, nextMigrationActionItem) => {
49-
const canonicalKey = getCanonicalKey(nextMigrationActionItem, { useNewKey: false })
50-
51-
const prevItemWithCanonicalKey = prevCanonicalKeyMappings[canonicalKey];
52-
// const prevItemWithKey = prevKeyMappings[nextMigrationActionItem.key];
53-
54-
if (!prevItemWithCanonicalKey) {
55-
return {
56-
...acc,
57-
[canonicalKey]: nextMigrationActionItem,
58-
};
59-
}
60-
61-
if (prevItemWithCanonicalKey.action === 'add' && nextMigrationActionItem.action === 'add') {
62-
throw `Action 'add' already exists for '${canonicalKey}'`;
63-
}
64-
if (prevItemWithCanonicalKey.action === 'add' && nextMigrationActionItem.action === 'remove') {
65-
return {
66-
...acc,
67-
[canonicalKey]: null,
68-
};
69-
}
70-
if (prevItemWithCanonicalKey.action === 'add' && nextMigrationActionItem.action === 'update') {
71-
const newKey = nextMigrationActionItem.newKey
72-
?? prevItemWithCanonicalKey.key;
73-
const newNamespace = nextMigrationActionItem.newNamespace
74-
?? prevItemWithCanonicalKey.namespace;
75-
76-
const newMigrationItem = removeUndefinedKeys<MigrationActionItem>({
77-
action: 'add',
78-
namespace: newNamespace,
79-
key: newKey,
80-
value: nextMigrationActionItem.newValue
81-
?? prevItemWithCanonicalKey.value,
82-
});
83-
84-
const newCanonicalKey = getCanonicalKey(newMigrationItem, { useNewKey: true });
85-
if (acc[newCanonicalKey] !== undefined && acc[newCanonicalKey] !== null) {
86-
throw `Action 'update' cannot be applied to '${newCanonicalKey}' as the key already exists`;
87-
}
88-
89-
return {
90-
...acc,
91-
// Setting null so that we remove them on the mappings.
92-
// No need to set null, if we have already overridden with other value
93-
[canonicalKey]: acc[canonicalKey] === undefined || acc[canonicalKey] === null
94-
? null
95-
: acc[canonicalKey],
96-
[newCanonicalKey]: newMigrationItem,
97-
}
98-
}
99-
if (prevItemWithCanonicalKey.action === 'remove' && nextMigrationActionItem.action === 'add') {
100-
return {
101-
...acc,
102-
[canonicalKey]: removeUndefinedKeys<MigrationActionItem>({
103-
action: 'update',
104-
namespace: prevItemWithCanonicalKey.namespace,
105-
key: prevItemWithCanonicalKey.key,
106-
newValue: nextMigrationActionItem.value,
107-
})
108-
};
109-
}
110-
if (prevItemWithCanonicalKey.action === 'remove' && nextMigrationActionItem.action === 'remove') {
111-
// pass
112-
return acc;
113-
}
114-
if (prevItemWithCanonicalKey.action === 'remove' && nextMigrationActionItem.action === 'update') {
115-
throw `Action 'update' cannot be applied to '${canonicalKey}' after action 'remove'`;
116-
}
117-
if (prevItemWithCanonicalKey.action === 'update' && nextMigrationActionItem.action === 'add') {
118-
throw `Action 'add' cannot be applied to '${canonicalKey}' after action 'update'`;
119-
}
120-
if (prevItemWithCanonicalKey.action === 'update' && nextMigrationActionItem.action === 'update') {
121-
return {
122-
...acc,
123-
[canonicalKey]: removeUndefinedKeys<MigrationActionItem>({
124-
action: 'update',
125-
namespace: prevItemWithCanonicalKey.namespace,
126-
key: prevItemWithCanonicalKey.key,
127-
newNamespace: nextMigrationActionItem.newNamespace ?? prevItemWithCanonicalKey.newNamespace,
128-
newKey: nextMigrationActionItem.newKey ?? prevItemWithCanonicalKey.newKey,
129-
newValue: nextMigrationActionItem.newValue ?? prevItemWithCanonicalKey.newValue,
130-
}),
131-
};
132-
}
133-
if (prevItemWithCanonicalKey.action === 'update' && nextMigrationActionItem.action === 'remove') {
134-
return {
135-
...acc,
136-
[canonicalKey]: removeUndefinedKeys<MigrationActionItem>({
137-
action: 'remove',
138-
namespace: prevItemWithCanonicalKey.namespace,
139-
key: prevItemWithCanonicalKey.key,
140-
}),
141-
};
142-
}
143-
return acc;
144-
},
145-
{},
146-
);
147-
148-
const finalMappings = {
149-
...prevCanonicalKeyMappings,
150-
...nextMappings,
151-
};
152-
153-
return Object.values(finalMappings).filter(isDefined);
154-
}
155-
15610
export function merge(migrationFileContents: MigrationFileContent[]) {
15711
const migrationActionItems = migrationFileContents.reduce<MigrationActionItem[]>(
15812
(acc, migrationActionItem) => {
@@ -174,21 +28,21 @@ async function mergeMigrations(
17428
) {
17529
const migrationFilesAttrs = await getMigrationFilesAttrs(projectPath, path);
17630
const selectedMigrationFilesAttrs = migrationFilesAttrs.filter(
177-
(item) => (item.migrationName >= from && item.migrationName <= to)
31+
(item) => (item.migrationFileName >= from && item.migrationFileName <= to)
17832
);
17933
console.info(`Found ${selectedMigrationFilesAttrs.length} migration files`);
18034

18135
if (selectedMigrationFilesAttrs.length <= 1) {
18236
throw 'There should be atleast 2 migration files';
18337
}
18438
const selectedMigrations = await readMigrations(
185-
selectedMigrationFilesAttrs.map((migration) => migration.fileName),
39+
selectedMigrationFilesAttrs.map((migration) => migration.filePath),
18640
);
18741

18842
const firstMigration= selectedMigrations[0];
18943
const lastMigration = selectedMigrations[selectedMigrations.length - 1];
19044

191-
const selectedMigrationsFileNames = selectedMigrationFilesAttrs.map((migration) => migration.fileName);
45+
const selectedMigrationsFileNames = selectedMigrationFilesAttrs.map((migration) => migration.filePath);
19246

19347
const mergedMigrationContent = {
19448
actions: merge(selectedMigrations.map((migration) => migration.content)),

0 commit comments

Comments
 (0)