Add server translation (#9847)
First proof of concept for server-side translation. The goal was to translate one metadata item: <img width="939" alt="Screenshot 2025-01-26 at 08 18 41" src="https://github.com/user-attachments/assets/e42a3f7f-f5e3-4ee7-9be5-272a2adccb23" />
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
@ -29,6 +29,7 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||
currentWorkspaceState,
|
||||
);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const setCurrentUser = useSetRecoilState(currentUserState);
|
||||
const setCurrentWorkspaceMember = useSetRecoilState(
|
||||
currentWorkspaceMemberState,
|
||||
@ -61,6 +62,7 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||
connectToDevTools: isDebugMode,
|
||||
// We don't want to re-create the client on token change or it will cause infinite loop
|
||||
initialTokenPair: tokenPair,
|
||||
currentWorkspaceMember: currentWorkspaceMember,
|
||||
onTokenPairChange: (tokenPair) => {
|
||||
setTokenPair(tokenPair);
|
||||
},
|
||||
@ -105,5 +107,11 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||
}
|
||||
}, [tokenPair]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (isDefined(apolloRef.current)) {
|
||||
apolloRef.current.updateWorkspaceMember(currentWorkspaceMember);
|
||||
}
|
||||
}, [currentWorkspaceMember]);
|
||||
|
||||
return apolloClient;
|
||||
};
|
||||
|
||||
@ -21,12 +21,22 @@ jest.mock('@/auth/services/AuthService', () => {
|
||||
const mockOnError = jest.fn();
|
||||
const mockOnNetworkError = jest.fn();
|
||||
|
||||
const mockWorkspaceMember = {
|
||||
id: 'workspace-member-id',
|
||||
locale: 'en',
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
};
|
||||
|
||||
const createMockOptions = (): Options<any> => ({
|
||||
uri: 'http://localhost:3000',
|
||||
initialTokenPair: {
|
||||
accessToken: { token: 'mockAccessToken', expiresAt: '' },
|
||||
refreshToken: { token: 'mockRefreshToken', expiresAt: '' },
|
||||
},
|
||||
currentWorkspaceMember: mockWorkspaceMember,
|
||||
cache: new InMemoryCache(),
|
||||
isDebugMode: true,
|
||||
onError: mockOnError,
|
||||
@ -50,13 +60,21 @@ const makeRequest = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
describe('xApolloFactory', () => {
|
||||
describe('ApolloFactory', () => {
|
||||
it('should create an instance of ApolloFactory', () => {
|
||||
const options = createMockOptions();
|
||||
const apolloFactory = new ApolloFactory(options);
|
||||
expect(apolloFactory).toBeInstanceOf(ApolloFactory);
|
||||
});
|
||||
|
||||
it('should initialize with the correct workspace member', () => {
|
||||
const options = createMockOptions();
|
||||
const apolloFactory = new ApolloFactory(options);
|
||||
expect(apolloFactory['currentWorkspaceMember']).toEqual(
|
||||
mockWorkspaceMember,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call onError when encountering "Unauthorized" error', async () => {
|
||||
const errors = [{ message: 'Unauthorized' }];
|
||||
fetchMock.mockResponse(() =>
|
||||
@ -138,4 +156,21 @@ describe('xApolloFactory', () => {
|
||||
expect(mockOnNetworkError).toHaveBeenCalledWith(mockError);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
it('should update workspace member when calling updateWorkspaceMember', () => {
|
||||
const options = createMockOptions();
|
||||
const apolloFactory = new ApolloFactory(options);
|
||||
|
||||
const newWorkspaceMember = {
|
||||
id: 'new-workspace-member-id',
|
||||
locale: 'fr',
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
};
|
||||
|
||||
apolloFactory.updateWorkspaceMember(newWorkspaceMember);
|
||||
expect(apolloFactory['currentWorkspaceMember']).toEqual(newWorkspaceMember);
|
||||
});
|
||||
});
|
||||
|
||||
@ -12,6 +12,7 @@ import { RetryLink } from '@apollo/client/link/retry';
|
||||
import { createUploadLink } from 'apollo-upload-client';
|
||||
|
||||
import { renewToken } from '@/auth/services/AuthService';
|
||||
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { AuthTokenPair } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { logDebug } from '~/utils/logDebug';
|
||||
@ -28,6 +29,7 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
|
||||
onTokenPairChange?: (tokenPair: AuthTokenPair) => void;
|
||||
onUnauthenticatedError?: () => void;
|
||||
initialTokenPair: AuthTokenPair | null;
|
||||
currentWorkspaceMember: CurrentWorkspaceMember | null;
|
||||
extraLinks?: ApolloLink[];
|
||||
isDebugMode?: boolean;
|
||||
}
|
||||
@ -35,6 +37,7 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
|
||||
export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
private client: ApolloClient<TCacheShape>;
|
||||
private tokenPair: AuthTokenPair | null = null;
|
||||
private currentWorkspaceMember: CurrentWorkspaceMember | null = null;
|
||||
|
||||
constructor(opts: Options<TCacheShape>) {
|
||||
const {
|
||||
@ -44,12 +47,14 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
onTokenPairChange,
|
||||
onUnauthenticatedError,
|
||||
initialTokenPair,
|
||||
currentWorkspaceMember,
|
||||
extraLinks,
|
||||
isDebugMode,
|
||||
...options
|
||||
} = opts;
|
||||
|
||||
this.tokenPair = initialTokenPair;
|
||||
this.currentWorkspaceMember = currentWorkspaceMember;
|
||||
|
||||
const buildApolloLink = (): ApolloLink => {
|
||||
const httpLink = createUploadLink({
|
||||
@ -64,6 +69,9 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
authorization: this.tokenPair?.accessToken.token
|
||||
? `Bearer ${this.tokenPair?.accessToken.token}`
|
||||
: '',
|
||||
...(this.currentWorkspaceMember?.locale
|
||||
? { 'x-locale': this.currentWorkspaceMember.locale }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -157,6 +165,10 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
this.tokenPair = tokenPair;
|
||||
}
|
||||
|
||||
updateWorkspaceMember(workspaceMember: CurrentWorkspaceMember | null) {
|
||||
this.currentWorkspaceMember = workspaceMember;
|
||||
}
|
||||
|
||||
getClient() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { ApolloClient } from '@apollo/client';
|
||||
|
||||
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { AuthTokenPair } from '~/generated/graphql';
|
||||
|
||||
export interface ApolloManager<TCacheShape> {
|
||||
getClient(): ApolloClient<TCacheShape>;
|
||||
updateTokenPair(tokenPair: AuthTokenPair | null): void;
|
||||
updateWorkspaceMember(workspaceMember: CurrentWorkspaceMember | null): void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user