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

Persist state #136

Merged
merged 20 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules/
.idea/
config/
webapp/store.json

# macOS
.DS_Store
45 changes: 0 additions & 45 deletions webapp/src/Cache.ts

This file was deleted.

4 changes: 2 additions & 2 deletions webapp/src/RepoGit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Octokit } from '@octokit/rest';
import path from 'path';
import { stringify } from 'yaml';

import { Cache } from '@/Cache';
import { IGit } from '@/utils/git/IGit';
import { LyraConfig } from '@/utils/lyraConfig';
import packageJson from '../package.json';
Expand All @@ -15,6 +14,7 @@ import { debug, info, warn } from '@/utils/log';
import { WriteLanguageFileError, WriteLanguageFileErrors } from '@/errors';
import { type TranslationMap } from '@/utils/adapters';
import { getTranslationsBySourceFile } from '@/utils/translationObjectUtil';
import { Store } from '@/store/Store';

export class RepoGit {
private static repositories: {
Expand Down Expand Up @@ -83,7 +83,7 @@ export class RepoGit {
public async saveLanguageFiles(projectPath: string): Promise<string[]> {
const lyraConfig = await this.getLyraConfig();
const projectConfig = lyraConfig.getProjectConfigByPath(projectPath);
const projectStore = await Cache.getProjectStore(projectConfig);
const projectStore = await Store.getProjectStore(projectConfig);
const languages = await projectStore.getLanguageData();

return await this.writeLangFiles(
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/actions/updateTranslation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use server';

import { Cache } from '@/Cache';
import { RepoGit } from '@/RepoGit';
import { Store } from '@/store/Store';
import { ServerConfig } from '@/utils/serverConfig';

export type TranslationSuccess = {
Expand Down Expand Up @@ -77,9 +77,10 @@ export default async function updateTranslation(
};
}

const projectStore = await Cache.getProjectStore(projectConfig);
const projectStore = await Store.getProjectStore(projectConfig);
try {
await projectStore.updateTranslation(languageName, messageId, translation);
await Store.persistToDisk();
} catch (e) {
return {
errorMessage: 'Failed to update translation',
Expand Down
19 changes: 12 additions & 7 deletions webapp/src/dataAccess.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Cache } from '@/Cache';
import { RepoGit } from '@/RepoGit';
import { ServerConfig, ServerProjectConfig } from '@/utils/serverConfig';
import { getTranslationsIdText } from './utils/translationObjectUtil';
import { LanguageNotSupported } from './errors';
import { Store } from '@/store/Store';

export async function accessProjects() {
const serverConfig = await ServerConfig.read();
Expand Down Expand Up @@ -41,12 +42,16 @@ export async function accessLanguage(
await repoGit.checkoutBaseAndPull();
const lyraConfig = await repoGit.getLyraConfig();
const projectConfig = lyraConfig.getProjectConfigByPath(project.projectPath);
const projectStore = await Cache.getProjectStore(projectConfig);
const projectStore = await Store.getProjectStore(projectConfig);
const messages = await projectStore.getMessageIds();
const translationsWithFilePath = await Cache.getLanguage(
projectName,
languageName,
);

if (!projectConfig.isLanguageSupported(languageName)) {
throw new LanguageNotSupported(languageName, projectName);
}

const translationsWithFilePath =
await projectStore.getTranslations(languageName);

const translations = getTranslationsIdText(translationsWithFilePath);

return {
Expand All @@ -61,7 +66,7 @@ async function readProject(project: ServerProjectConfig) {
await repoGit.checkoutBaseAndPull();
const lyraConfig = await repoGit.getLyraConfig();
const projectConfig = lyraConfig.getProjectConfigByPath(project.projectPath);
const store = await Cache.getProjectStore(projectConfig);
const store = await Store.getProjectStore(projectConfig);
const messages = await store.getMessageIds();
const languagesWithTranslations = projectConfig.languages.map(
async (lang) => {
Expand Down
7 changes: 6 additions & 1 deletion webapp/src/store/ProjectStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ export class ProjectStore {
constructor(
messageAdapter: IMessageAdapter,
translationAdapter: ITranslationAdapter,
initialState?: StoreData,
) {
this.data = {
this.data = initialState || {
languages: {},
messages: [],
};
Expand Down Expand Up @@ -59,6 +60,10 @@ export class ProjectStore {
return this.data.messages;
}

toJSON(): StoreData {
return this.data;
}

async updateTranslation(lang: string, id: string, text: string) {
await this.refresh();

Expand Down
61 changes: 61 additions & 0 deletions webapp/src/store/Store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,59 @@
import fs from 'fs/promises';

import { ProjectStore } from '@/store/ProjectStore';
import MessageAdapterFactory from '@/utils/adapters/MessageAdapterFactory';
import YamlTranslationAdapter from '@/utils/adapters/YamlTranslationAdapter';
import { LyraProjectConfig } from '@/utils/lyraConfig';
import { StoreData } from './types';

const FILE_PATH = './store.json';

export class Store {
private data = new Map<string, ProjectStore>();
private initialState: Record<string, StoreData | undefined> = {};

public static async getProjectStore(
lyraProjectConfig: LyraProjectConfig,
): Promise<ProjectStore> {
if (!globalThis.store) {
globalThis.store = new Store();
await globalThis.store.loadFromDisk();
}
WULCAN marked this conversation as resolved.
Show resolved Hide resolved

if (!globalThis.store.hasProjectStore(lyraProjectConfig.absPath)) {
const initialProjectState =
globalThis.store.initialState[lyraProjectConfig.absPath];

const projectStore = new ProjectStore(
MessageAdapterFactory.createAdapter(lyraProjectConfig),
new YamlTranslationAdapter(lyraProjectConfig.absTranslationsPath),
initialProjectState,
);
globalThis.store.addProjectStore(lyraProjectConfig.absPath, projectStore);
}

return globalThis.store.getProjectStore(lyraProjectConfig.absPath);
}

public static async persistToDisk(): Promise<void> {
if (!globalThis.store) {
return;
}

const payload = globalThis.store.toJSON();
const json = JSON.stringify(payload);

await fs.writeFile(FILE_PATH, json);
WULCAN marked this conversation as resolved.
Show resolved Hide resolved
}

public toJSON(): Record<string, unknown> {
const data: Record<string, StoreData> = {};
this.data.forEach((value, key) => {
data[key] = value.toJSON();
});

return data;
}

public addProjectStore(projectPath: string, projectStore: ProjectStore) {
this.data.set(projectPath, projectStore);
Expand All @@ -18,4 +70,13 @@ export class Store {
}
return projectStore;
}

public async loadFromDisk(): Promise<void> {
try {
const json = await fs.readFile(FILE_PATH);
this.initialState = JSON.parse(json.toString());
WULCAN marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
// Do nothing. It's fine to start from scratch sometimes.
}
WULCAN marked this conversation as resolved.
Show resolved Hide resolved
}
}