Skip to content

Commit d270826

Browse files
committed
feat(translation): connect with ifrc translation service
1 parent d36918a commit d270826

File tree

13 files changed

+260
-88
lines changed

13 files changed

+260
-88
lines changed

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
"initialize:type": "mkdir -p generated/ && pnpm initialize:type:go-api && pnpm initialize:type:risk-api",
1717
"initialize:type:go-api": "test -f ./generated/types.ts && true || cp types.stub.ts ./generated/types.ts",
1818
"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",
19+
"generate:type": "pnpm generate:type:go-api && pnpm generate:type:risk-api && pnpm generate:type:translations",
2020
"generate:type:go-api": "GO_API_HASH=$(git rev-parse HEAD:go-api); dotenv -- cross-var openapi-typescript https://raw.githubusercontent.com/IFRCGo/go-api-artifacts/refs/heads/main/generated/$GO_API_HASH/openapi-schema.yaml -o ./generated/types.ts --alphabetize",
2121
"generate:type:risk-api": "dotenv -- cross-var openapi-typescript ../go-risk-module-api/openapi-schema.yaml -o ./generated/riskTypes.ts --alphabetize",
22+
"generate:type:translations": "dotenv -- cross-var openapi-typescript \"%APP_TRANSLATION_API_ENDPOINT%swagger/v1/swagger.json/\" -o ./generated/translationTypes.ts --alphabetize",
2223
"prestart": "pnpm initialize:type",
2324
"start": "pnpm -F @ifrc-go/ui build && vite",
2425
"prebuild": "pnpm initialize:type",
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
2+
import { readFileSync } from "fs";
3+
4+
// FIXME: get this from params
5+
const applicationId = 18;
6+
7+
function resolveUrl(from: string, to: string) {
8+
const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
9+
if (resolvedUrl.protocol === 'resolve:') {
10+
const { pathname, search, hash } = resolvedUrl;
11+
return pathname + search + hash;
12+
}
13+
return resolvedUrl.toString();
14+
}
15+
16+
/*
17+
async function fetchTranslations(ifrcApiUrl: string, ifrcApiKey: string) {
18+
const endpoint = resolveUrl(ifrcApiUrl, `Application/${applicationId}/Translation/`);
19+
20+
const headers: RequestInit['headers'] = {
21+
'Accept': 'application/json',
22+
'X-API-KEY': ifrcApiKey,
23+
}
24+
25+
const promise = fetch(
26+
endpoint,
27+
{
28+
method: 'GET',
29+
headers,
30+
}
31+
);
32+
33+
return promise;
34+
}
35+
36+
async function postTranslation(ifrcApiUrl: string, ifrcApiKey: string) {
37+
const endpoint = resolveUrl(ifrcApiUrl, `Application/${applicationId}/Translation`);
38+
39+
const headers: RequestInit['headers'] = {
40+
// 'Accept': 'application/json',
41+
'X-API-KEY': ifrcApiKey,
42+
'Content-Type': 'application/json',
43+
}
44+
45+
const promise = fetch(
46+
endpoint,
47+
{
48+
method: 'POST',
49+
headers,
50+
body: JSON.stringify({
51+
page: 'home',
52+
keyName: 'pageTitle',
53+
value: 'IFRC GO | Home',
54+
languageCode: 'en',
55+
}),
56+
}
57+
);
58+
59+
return promise;
60+
}
61+
*/
62+
63+
export async function fullAppimport(importFilePath: string, ifrcApiUrl: string, ifrcApiKey: string) {
64+
const endpoint = resolveUrl(ifrcApiUrl, `Application/${applicationId}/Translation/fullappimport`);
65+
const translationFile = readFileSync(importFilePath);
66+
const uint8FileData = new Uint8Array(translationFile);
67+
const blob = new Blob([uint8FileData], {
68+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
69+
});
70+
71+
const formData = new FormData();
72+
formData.append('files', blob, 'translations.xlsx');
73+
74+
const headers: RequestInit['headers'] = {
75+
'Accept': 'application/json',
76+
'X-API-KEY': ifrcApiKey,
77+
}
78+
79+
const promise = fetch(
80+
endpoint,
81+
{
82+
method: 'POST',
83+
headers,
84+
body: formData,
85+
}
86+
);
87+
88+
return promise;
89+
}
90+
91+
async function pushStringsFromExcelToIfrc(importFilePath: string, apiUrl: string, apiKey: string) {
92+
const response = await fullAppimport(importFilePath, apiUrl, apiKey);
93+
94+
try {
95+
const responseJson = await response.json();
96+
console.info(responseJson);
97+
} catch(e) {
98+
console.info(e);
99+
const responseText = await response.text();
100+
console.info(responseText);
101+
}
102+
}
103+
104+
export default pushStringsFromExcelToIfrc;

app/scripts/translatte/main.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import pushStringsFromExcel from './commands/pushStringsFromExcel';
1515
import exportServerStringsToExcel from './commands/exportServerStringsToExcel';
1616
import clearServerStrings from './commands/clearServerStrings';
1717
import pushStringsDref from './commands/pushStringsDref';
18+
import pushStringsFromExcelToIfrc from './commands/pushStringsFromExcelToIfrc';
1819

1920
const currentDir = cwd();
2021

@@ -255,6 +256,37 @@ yargs(hideBin(process.argv))
255256
);
256257
},
257258
)
259+
.command(
260+
'push-strings-from-excel-to-ifrc <IMPORT_FILE_PATH>',
261+
'Import migration from excel file and push it to server',
262+
(yargs) => {
263+
yargs.positional('IMPORT_FILE_PATH', {
264+
type: 'string',
265+
describe: 'Find the import file on IMPORT_FILE_PATH',
266+
});
267+
yargs.options({
268+
'api-key': {
269+
type: 'string',
270+
describe: 'API key to access the API server',
271+
require: true,
272+
},
273+
'api-url': {
274+
type: 'string',
275+
describe: 'URL for the API server',
276+
require: true,
277+
}
278+
});
279+
},
280+
async (argv) => {
281+
const importFilePath = (argv.IMPORT_FILE_PATH as string);
282+
283+
await pushStringsFromExcelToIfrc(
284+
importFilePath,
285+
argv.apiUrl as string,
286+
argv.apiKey as string,
287+
);
288+
},
289+
)
258290
.command(
259291
'push-strings-dref <IMPORT_FILE_PATH>',
260292
'IMPORTANT!!! Temporary command, do not use!',
@@ -282,7 +314,7 @@ yargs(hideBin(process.argv))
282314
await pushStringsDref(
283315
importFilePath,
284316
argv.apiUrl as string,
285-
argv.authToken as string,
317+
argv.apiKey as string,
286318
);
287319
},
288320
)

app/src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const {
66
APP_MAPBOX_ACCESS_TOKEN,
77
APP_TINY_API_KEY,
88
APP_RISK_API_ENDPOINT,
9+
APP_TRANSLATION_API_ENDPOINT,
10+
APP_TRANSLATION_API_KEY,
911
APP_SDT_URL,
1012
APP_POWER_BI_REPORT_ID_1,
1113
APP_SENTRY_DSN,
@@ -30,9 +32,11 @@ export const api = APP_API_ENDPOINT;
3032
export const adminUrl = APP_ADMIN_URL ?? `${api}admin/`;
3133
export const mbtoken = APP_MAPBOX_ACCESS_TOKEN;
3234
export const riskApi = APP_RISK_API_ENDPOINT;
35+
export const translationApi = APP_TRANSLATION_API_ENDPOINT;
3336
export const sdtUrl = APP_SDT_URL;
3437
export const powerBiReportId1 = APP_POWER_BI_REPORT_ID_1;
3538

39+
export const translationApiKey = APP_TRANSLATION_API_KEY;
3640
export const tinyApiKey = APP_TINY_API_KEY;
3741
export const sentryAppDsn = APP_SENTRY_DSN;
3842
export const sentryTracesSampleRate = APP_SENTRY_TRACES_SAMPLE_RATE;

app/src/utils/restRequest/go.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { type ContextInterface } from '@togglecorp/toggle-request';
99
import {
1010
api,
1111
riskApi,
12+
translationApi,
13+
translationApiKey,
1214
} from '#config';
1315
import { type UserAuth } from '#contexts/user';
1416
import {
@@ -39,8 +41,10 @@ export interface TransformedError {
3941
debugMessage: string;
4042
}
4143

44+
type ApiType = 'go' | 'risk' | 'translation';
45+
4246
export interface AdditionalOptions {
43-
apiType?: 'go' | 'risk';
47+
apiType?: ApiType;
4448
formData?: boolean;
4549
isCsvRequest?: boolean;
4650
enforceEnglishForQuery?: boolean;
@@ -111,6 +115,18 @@ type GoContextInterface = ContextInterface<
111115
AdditionalOptions
112116
>;
113117

118+
function getEndPoint(apiType: ApiType | undefined) {
119+
if (apiType === 'risk') {
120+
return riskApi;
121+
}
122+
123+
if (apiType === 'translation') {
124+
return translationApi;
125+
}
126+
127+
return api;
128+
}
129+
114130
export const processGoUrls: GoContextInterface['transformUrl'] = (url, _, additionalOptions) => {
115131
if (isFalsyString(url)) {
116132
return '';
@@ -124,7 +140,7 @@ export const processGoUrls: GoContextInterface['transformUrl'] = (url, _, additi
124140
const { apiType } = additionalOptions;
125141

126142
return resolveUrl(
127-
apiType === 'risk' ? riskApi : api,
143+
getEndPoint(apiType),
128144
url,
129145
);
130146
};
@@ -164,6 +180,7 @@ export const processGoOptions: GoContextInterface['transformOptions'] = (
164180
} = requestOptions;
165181

166182
const {
183+
apiType,
167184
formData,
168185
isCsvRequest,
169186
isExcelRequest,
@@ -176,10 +193,15 @@ export const processGoOptions: GoContextInterface['transformOptions'] = (
176193
const user = getFromStorage<UserAuth | undefined>(KEY_USER_STORAGE);
177194
const token = user?.token;
178195

196+
// FIXME: only inject on go apis
179197
const defaultHeaders: HeadersInit = {
180198
Authorization: token ? `Token ${token}` : '',
181199
};
182200

201+
if (apiType === 'translation') {
202+
defaultHeaders['x-api-key'] = translationApiKey;
203+
}
204+
183205
if (method === 'GET') {
184206
// Query
185207
defaultHeaders['Accept-Language'] = enforceEnglishForQuery ? 'en' : currentLanguage;
@@ -239,7 +261,17 @@ const isSuccessfulStatus = (status: number): boolean => status >= 200 && status
239261

240262
const isContentTypeExcel = (res: Response): boolean => res.headers.get('content-type') === CONTENT_TYPE_EXCEL;
241263

242-
const isContentTypeJson = (res: Response): boolean => res.headers.get('content-type') === CONTENT_TYPE_JSON;
264+
const isContentTypeJson = (res: Response): boolean => {
265+
const contentTypeHeaders = res.headers.get('content-type');
266+
267+
if (isNotDefined(contentTypeHeaders)) {
268+
return false;
269+
}
270+
271+
const mediaTypes = contentTypeHeaders.split('; ');
272+
273+
return mediaTypes[0]?.toLowerCase() === CONTENT_TYPE_JSON;
274+
};
243275

244276
const isLoginRedirect = (url: string): boolean => new URL(url).pathname.includes('login');
245277

app/src/utils/restRequest/index.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@togglecorp/toggle-request';
66

77
import type { paths as riskApiPaths } from '#generated/riskTypes';
8+
import type { paths as translationApiPaths } from '#generated/translationTypes';
89
import type { paths as goApiPaths } from '#generated/types';
910

1011
import type {
@@ -18,24 +19,36 @@ import type {
1819
VALID_METHOD,
1920
} from './overrideTypes';
2021

22+
export type TranslationApiResponse<URL extends keyof translationApiPaths, METHOD extends 'GET' | 'POST' | 'PUT' | 'PATCH' = 'GET'> = ApiResponse<translationApiPaths, URL, METHOD>;
23+
export type TranslationApiUrlQuery<URL extends keyof translationApiPaths, METHOD extends 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET'> = ApiUrlQuery<translationApiPaths, URL, METHOD>
24+
export type TranslationApiBody<URL extends keyof translationApiPaths, METHOD extends 'POST' | 'PUT' | 'PATCH'> = ApiBody<translationApiPaths, URL, METHOD>
25+
2126
export type GoApiResponse<URL extends keyof goApiPaths, METHOD extends 'GET' | 'POST' | 'PUT' | 'PATCH' = 'GET'> = ApiResponse<goApiPaths, URL, METHOD>;
2227
export type GoApiUrlQuery<URL extends keyof goApiPaths, METHOD extends 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET'> = ApiUrlQuery<goApiPaths, URL, METHOD>
2328
export type GoApiBody<URL extends keyof goApiPaths, METHOD extends 'POST' | 'PUT' | 'PATCH'> = ApiBody<goApiPaths, URL, METHOD>
2429

2530
export type RiskApiResponse<URL extends keyof riskApiPaths, METHOD extends 'GET' | 'POST' | 'PUT' | 'PATCH' = 'GET'> = ApiResponse<riskApiPaths, URL, METHOD>;
26-
// type RiskApiUrlQuery<
27-
// URL extends keyof riskApiPaths,
28-
// METHOD extends 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET'
29-
// > = ApiUrlQuery<riskApiPaths, URL, METHOD>
30-
// type RiskApiBody<
31-
// URL extends keyof riskApiPaths,
32-
// METHOD extends 'POST' | 'PUT' | 'PATCH'
33-
// > = ApiBody<riskApiPaths, URL, METHOD>
3431

3532
export type ListResponseItem<RESPONSE extends {
3633
results?: Array<unknown>
3734
} | undefined> = NonNullable<NonNullable<RESPONSE>['results']>[number];
3835

36+
const useTranslationRequest = useRequest as <
37+
PATH extends keyof translationApiPaths,
38+
METHOD extends VALID_METHOD | undefined = 'GET',
39+
>(
40+
requestOptions: CustomRequestOptions<translationApiPaths, PATH, METHOD> & { apiType: 'translation' }
41+
) => CustomRequestReturn<translationApiPaths, PATH, METHOD>;
42+
43+
// FIXME: identify a way to do this without a cast
44+
const useTranslationLazyRequest = useLazyRequest as <
45+
PATH extends keyof translationApiPaths,
46+
CONTEXT = unknown,
47+
METHOD extends VALID_METHOD | undefined = 'GET',
48+
>(
49+
requestOptions: CustomLazyRequestOptions<translationApiPaths, PATH, METHOD, CONTEXT> & { apiType: 'translation' }
50+
) => CustomLazyRequestReturn<translationApiPaths, PATH, METHOD, CONTEXT>;
51+
3952
// FIXME: identify a way to do this without a cast
4053
const useGoRequest = useRequest as <
4154
PATH extends keyof goApiPaths,
@@ -76,4 +89,6 @@ export {
7689
useGoRequest as useRequest,
7790
useRiskLazyRequest,
7891
useRiskRequest,
92+
useTranslationLazyRequest,
93+
useTranslationRequest,
7994
};

0 commit comments

Comments
 (0)