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

231127 use flatt object #12

Merged
merged 6 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@emotion/styled": "^11.11.0",
"@mui/joy": "^5.0.0-beta.11",
"@octokit/rest": "^20.0.2",
"flat": "^6.0.1",
"next": "13.5.6",
"react": "^18",
"react-dom": "^18",
Expand All @@ -25,6 +26,8 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^6",
"@typescript-eslint/parser": "^6",
"eslint": "^7.15.0",
"eslint-config-next": "^12.0.10",
"eslint-config-prettier": "^8.3.0",
Expand All @@ -35,8 +38,6 @@
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-yml": "^0.13.0",
"@typescript-eslint/eslint-plugin": "^6",
"@typescript-eslint/parser": "^6",
"prettier": "^2.5.1",
"typescript": "^5"
}
Expand Down
57 changes: 57 additions & 0 deletions webapp/src/Store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* global globalThis */

import { flatten } from 'flat';
import fs from 'fs/promises';
import { LanguageNotFound } from '@/errors';
import { parse } from 'yaml';
import { envVarNotFound, logDebug } from '@/utils/util';
import { simpleGit, SimpleGit, SimpleGitOptions } from 'simple-git';

const REPO_PATH = process.env.REPO_PATH ?? envVarNotFound('REPO_PATH');
const MAIN_BRANCH = 'main';

export class Store {
public static async getLanguage(lang: string) {
let languages: Map<string, Record<string, string>>;
if (!globalThis.languages) {
logDebug('Initializing languages');
const options: Partial<SimpleGitOptions> = {
baseDir: REPO_PATH,
binary: 'git',
maxConcurrentProcesses: 1,
trimmed: false,
};
const git: SimpleGit = simpleGit(options);
logDebug('git checkout main pull...');
await git.checkout(MAIN_BRANCH);
logDebug('git pull...');
await git.pull();
logDebug('git done checkout main branch and pull');
languages = new Map<string, Record<string, string>>();
globalThis.languages = languages;
} else {
logDebug('find languages in Memory');
languages = globalThis.languages;
}

let translation: Record<string, string>;
if (!languages.has(lang)) {
logDebug('read language[' + lang + '] from file');
// TODO: read this from .lyra.yml setting file in client repo
const yamlPath = REPO_PATH + `/src/locale/${lang}.yml`;

const yamlBuf = await fs.readFile(yamlPath);
translation = flatten(parse(yamlBuf.toString()));
languages.set(lang, translation);
} else {
logDebug('read language [' + lang + '] from Memory');
translation = languages.get(lang) ?? Store.throwLangNotFound(lang);
}

return translation;
}

private static throwLangNotFound(lang: string): never {
throw new LanguageNotFound(`Language ${lang} not found`);
}
}
3 changes: 1 addition & 2 deletions webapp/src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function Home({ params }: { params: { lang: string } }) {
key={msg.id}
message={msg}
onSave={async (text) => {
const res = await fetch(
await fetch(
`/api/translations/${params.lang}/${msg.id}`,
{
body: JSON.stringify({
Expand All @@ -66,7 +66,6 @@ export default function Home({ params }: { params: { lang: string } }) {
},
);

await res.json();
setTranslations((cur) => ({
...cur,
[msg.id]: text,
Expand Down
51 changes: 0 additions & 51 deletions webapp/src/app/api/languages.ts

This file was deleted.

4 changes: 4 additions & 0 deletions webapp/src/app/api/messages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ const REPO_PATH = process.env.REPO_PATH ?? envVarNotFound('REPO_PATH');

export async function GET() {
const messages: MessageData[] = [];
// TODO: read path or src from .lyra.yml setting file in client repo
for await (const item of getMessageFiles(REPO_PATH + '/src')) {
messages.push(...readTypedMessages(item));
}

// TODO: change data instruction to be a map of key to value, instead of object
// message id is the key, and value is an object with default and params
// example: { 'key1.key2.key3': { default: 'default text', params: [] }}
return NextResponse.json({
data: messages,
});
Expand Down
4 changes: 3 additions & 1 deletion webapp/src/app/api/pull-request/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* global globalThis */

import fs from 'fs/promises';
import { NextResponse } from 'next/server';
import { Octokit } from '@octokit/rest';
import { stringify } from 'yaml';
import { unflatten } from 'flat';
import { version } from '@/../package.json';
import { envVarNotFound, logError, logWarn } from '@/utils/util';
import { simpleGit, SimpleGit, SimpleGitOptions } from 'simple-git';
Expand Down Expand Up @@ -39,7 +41,7 @@ export async function POST() {
const languages = globalThis.languages;
for (const lang of languages.keys()) {
const yamlPath = REPO_PATH + `/src/locale/${lang}.yml`;
const yamlOutput = stringify(languages.get(lang), {
const yamlOutput = stringify(unflatten(languages.get(lang)), {
doubleQuotedAsJSON: true,
singleQuote: true,
});
Expand Down
29 changes: 19 additions & 10 deletions webapp/src/app/api/translations/[lang]/[msgId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getLanguage } from '@/app/api/languages';
import { LanguageNotFound } from '@/errors';
import { Store } from '@/Store';
import { NextRequest, NextResponse } from 'next/server';

export async function PUT(
Expand All @@ -14,16 +15,24 @@ export async function PUT(
const { lang, msgId } = context.params;
const { text } = payload;

const objKeyPath = msgId.split('.');
let curObj = await getLanguage(lang);
objKeyPath.forEach((key, index) => {
if (index == objKeyPath.length - 1) {
curObj[key] = text;
} else {
curObj[key] = { ...(curObj[key] as Record<string, unknown>) };
curObj = curObj[key] as Record<string, unknown>;
try {
const translations = await Store.getLanguage(lang);
if (translations[msgId] === undefined) {
return NextResponse.json(
{ message: 'message id [' + msgId + '] not found' },
{ status: 404 },
);
}
});
translations[msgId] = text;
} catch (e) {
if (e instanceof LanguageNotFound) {
return NextResponse.json(
{ message: 'language [' + lang + '] not found' },
{ status: 404 },
);
}
throw e;
}

return NextResponse.json({
lang,
Expand Down
45 changes: 15 additions & 30 deletions webapp/src/app/api/translations/[lang]/route.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,25 @@
import { getLanguage } from '@/app/api/languages';
import { LanguageNotFound } from '@/errors';
import { Store } from '@/Store';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(
req: NextRequest, // keep this here even if unused
context: { params: { lang: string; msgId: string } },
) {
const lang = context.params.lang;
const langObj = await getLanguage(lang);
const flattenLangObj = flattenObject(langObj);

return NextResponse.json({
lang,
translations: flattenLangObj,
});
}

function flattenObject(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
obj: Record<string, any>,
parentKey: string = '',
): Record<string, string> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: Record<string, any> = {};

for (const key in obj) {
// eslint-disable-next-line no-prototype-builtins
if (obj.hasOwnProperty(key)) {
const newKey = parentKey ? `${parentKey}.${key}` : key;

if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
Object.assign(result, flattenObject(obj[key], newKey));
} else {
result[newKey] = obj[key];
}
try {
const translations = await Store.getLanguage(lang);
return NextResponse.json({
lang,
translations,
});
} catch (e) {
if (e instanceof LanguageNotFound) {
return NextResponse.json(
{ message: 'language [' + lang + '] not found' },
{ status: 404 }
);
}
throw e;
}

return result;
}
5 changes: 5 additions & 0 deletions webapp/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class LanguageNotFound extends Error {
constructor(lang: string) {
super(`Language ${lang} not found`);
}
}
2 changes: 1 addition & 1 deletion webapp/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type LanguageMap = Map<string, Record<string, unknown>>;
export type LanguageMap = Map<string, Record<string, string>>;

declare global {
// eslint-disable-next-line
Expand Down
5 changes: 5 additions & 0 deletions webapp/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,11 @@ flat-cache@^3.0.4:
keyv "^4.5.3"
rimraf "^3.0.2"

flat@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/flat/-/flat-6.0.1.tgz#09070cf918293b401577f20843edeadf4d3e8755"
integrity sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==

flatted@^3.2.9:
version "3.2.9"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf"
Expand Down