Skip to content

Commit

Permalink
feat: allows translation of texts larger than 5K chars in getTranslat…
Browse files Browse the repository at this point in the history
…ion and <Translate />
  • Loading branch information
MiracleUFO committed Sep 21, 2023
1 parent af9549b commit 726ea71
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 9 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ module.exports = {
unnamedComponents: 'arrow-function',
},
],
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
};
2 changes: 0 additions & 2 deletions src/context/languageContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import language from '../types/language';
const LanguageContext = createContext({
languageFrom: DEFAULT_LANGUAGE_FROM,
languageTo: DEFAULT_BROWSER_LANGUAGE,
// eslint-disable-next-line no-unused-vars
setLanguageFrom: (_from: language) => {},
// eslint-disable-next-line no-unused-vars
setLanguageTo: (_to: language) => {},
resetLanguages: () => {},
resetFrom: () => {},
Expand Down
27 changes: 25 additions & 2 deletions src/tests/Translate.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { render, waitFor } from './utils-test';
import { Translate } from '../index';
import {
CHAR_LIMIT_TEXT_ENGLISH,
CHAR_LIMIT_TEXT_FRENCH,
render,
waitFor,
getDefaultNormalizer,
} from './utils-test';
import { HELLO_IN_ENGLISH, HELLO_IN_FRENCH, HELLO_IN_SPANISH } from '../constants';
import language from '../types/language';

// eslint-disable-next-line max-len
const renderTranslate = async (from?: language, to?: language, shouldFallback?: boolean, text?:string) => render(
const renderTranslate = async (from?: language, to?: language, shouldFallback?: boolean, text?: string) => render(
<Translate
to={to}
from={from}
Expand Down Expand Up @@ -48,3 +54,20 @@ describe('Translate if language to and/or from NOT specified', () => {
expect(getByText(HELLO_IN_ENGLISH)).toBeInTheDocument();
});
});

describe('No Character limit required check', () => {
it('should correctly translate text > 5000 characters', async () => {
const { getByText } = await renderTranslate('en', 'fr', true, CHAR_LIMIT_TEXT_ENGLISH.repeat(10));

// options to pass for both substrings & exact matches (which suffices)
const normalizerOpts = {
exact: false,
normalizer: getDefaultNormalizer({ trim: false, collapseWhitespace: false }),
};

// does not check for repeating text exactly
// because google translate API tends to use synonyms when paragraphs repeat
await waitFor(() => getByText(CHAR_LIMIT_TEXT_FRENCH, normalizerOpts));
expect(getByText(CHAR_LIMIT_TEXT_FRENCH, normalizerOpts)).toBeInTheDocument();
});
});
11 changes: 11 additions & 0 deletions src/tests/getTranslation.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import getTranslation from '../scripts/getTranslation';
import { CHAR_LIMIT_TEXT_ENGLISH, CHAR_LIMIT_TEXT_FRENCH } from './utils-test';
import { HELLO_IN_ENGLISH, HELLO_IN_FRENCH, HELLO_IN_SPANISH } from '../constants';

describe('Gets translation correctly when language to and from specified correctly', () => {
Expand Down Expand Up @@ -28,3 +29,13 @@ describe('Translate if language to and/or from NOT specified', () => {
expect(result).toBe(HELLO_IN_ENGLISH);
});
});

describe('No Character limit required check', () => {
it('should correctly translate text > 5000 characters', async () => {
const result = await getTranslation(CHAR_LIMIT_TEXT_ENGLISH.repeat(10), 'en', 'fr');

// uses `toContain` instead of `toBe`
// because google translate API tends to use synonyms when paragraphs repeat
expect(result).toContain(CHAR_LIMIT_TEXT_FRENCH);
});
});
6 changes: 6 additions & 0 deletions src/tests/utils-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,11 @@ const customRender = (
options?: Omit<RenderOptions, 'wrapper'>,
) => render(ui, { wrapper: Providers, ...options });

// google-translate-api char limit per request is 5000 chars
// Used for testing
const CHAR_LIMIT_TEXT_ENGLISH = ('Be kind to everyone, regardless of their race, religion, gender, or sexual orientation. Help those in need, whether it\'s by volunteering our time, donating to charity, or simply lending a helping hand. Protect our planet by reducing our consumption of resources, recycling, and using renewable energy sources. Educate ourselves about the world around us and the challenges that we face. Get involved in our communities and make our voices heard. Vote for candidates who share our values and who will work to make the world a better place.');
const CHAR_LIMIT_TEXT_FRENCH = ('Soyez gentil avec tout le monde, quels que soient leur race, leur religion, leur sexe ou leur orientation sexuelle. Aidez ceux qui en ont besoin, que ce soit en donnant de notre temps, en faisant un don à une œuvre caritative ou simplement en donnant un coup de main. Protégez notre planète en réduisant notre consommation de ressources, en recyclant et en utilisant des sources d\'énergie renouvelables. Instruisons-nous sur le monde qui nous entoure et les défis auxquels nous sommes confrontés. Impliquez-vous dans nos communautés et faites entendre nos voix. Votez pour des candidats qui partagent nos valeurs et qui travailleront à rendre le monde meilleur.');

export { CHAR_LIMIT_TEXT_ENGLISH, CHAR_LIMIT_TEXT_FRENCH };
export * from '@testing-library/react';
export { customRender as render };
20 changes: 20 additions & 0 deletions src/utils/chunkRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const chunkRequest = async (
text: string | string[],
translationReqFunc: (_chunk: string | string[]) => Promise<string | undefined>,
chunkSize: number = 0,
) => {
const promises = [];

for (let i = 0; i < text.length; i += chunkSize) {
// issue: need better way to handle arrays with text items > 5000 chars
// below just takes arrays with more than 5000 items and slices it.
// nested approach required?
const chunk = text.slice(i, i + chunkSize);
promises.push(translationReqFunc(chunk));
}

const translatedChunks = await Promise.all(promises);
return translatedChunks.join('');
};

export default chunkRequest;
16 changes: 11 additions & 5 deletions src/utils/getTranslation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { translate } from '@vitalets/google-translate-api';

import chunkRequest from './chunkRequest';
import enableCors from './enableCorsAndLimitRate';
import language from '../types/language';

Expand All @@ -7,12 +9,16 @@ const getTranslation = async (
from?: language,
to?: language,
) : Promise<string | undefined> => {
// rate limit (1 request per second) and CORS policy overriding (if any)
enableCors(1);
const translateRequest = async (chunk: string | string[]) => {
// CORS policy overriding (if any)
enableCors(1);

// translating happens here. ✨ bing! ✨
const translation = await translate(chunk as string, { from, to });
return translation.text;
};

// translating happens here. ✨ bing! ✨
const translation = await translate(text as string, { from, to });
return translation.text;
return chunkRequest(text, translateRequest, 5000);
};

export default getTranslation;

0 comments on commit 726ea71

Please sign in to comment.