diff --git a/README.md b/README.md index c3c7475544..85761e541d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![@tolgee/ui version](https://img.shields.io/npm/v/@tolgee/ui?label=%40tolgee%2Fui) ![types typescript](https://img.shields.io/badge/Types-Typescript-blue) ![licence](https://img.shields.io/github/license/tolgee/tolgee-js) -![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social) +[![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social)](https://twitter.com/Tolgee_i18n) [![github stars](https://img.shields.io/github/stars/tolgee/tolgee-js?style=social)](https://github.com/tolgee/tolgee-js) # Tolgee JS diff --git a/e2e/cypress/integration/react/base.spec.ts b/e2e/cypress/integration/react/base.spec.ts index c350ddd419..3431ba29d1 100644 --- a/e2e/cypress/integration/react/base.spec.ts +++ b/e2e/cypress/integration/react/base.spec.ts @@ -19,4 +19,40 @@ context('Base test', () => { it('shows key as default when default not provided ', () => { cy.contains('unknown key').should('be.visible'); }); + + it('opens key context menu when multiple keys are in one element', () => { + cy.get('.multiple-keys') + .should('be.visible') + .should('contain', 'First one') + .trigger('keydown', { key: 'Alt' }) + .trigger('mouseover') + .click(); + + cy.get('#__tolgee_dev_tools') + .find('li') + .should('have.length', 3) + .should('contain', 'key') + .should('contain', 'key 2') + .should('contain', 'key 3'); + + cy.get('#__tolgee_dev_tools').find('li').contains('key 2').click(); + cy.get('#__tolgee_dev_tools').should('contain', 'Quick translation'); + cy.get('#__tolgee_dev_tools') + .find('textarea') + .should('contain', 'Second one'); + }); + + it.only('opens dialog when element contains same key multiple times', () => { + cy.get('.same-key-multiple') + .should('be.visible') + .should('contain', 'First one') + .trigger('keydown', { key: 'Alt' }) + .trigger('mouseover') + .click(); + + cy.get('#__tolgee_dev_tools').should('contain', 'Quick translation'); + cy.get('#__tolgee_dev_tools') + .find('textarea') + .should('contain', 'First one'); + }); }); diff --git a/packages/core/README.md b/packages/core/README.md index 219456b34f..0a00542c90 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,7 +1,7 @@ ![test workflow](https://github.com/tolgee/tolgee-js/actions/workflows/test.yml/badge.svg) ![@tolgee/core version](https://img.shields.io/npm/v/@tolgee/core?label=%40tolgee%2Fcore) ![types typescript](https://img.shields.io/badge/Types-Typescript-blue) -![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social) +[![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social)](https://twitter.com/Tolgee_i18n) [![github stars](https://img.shields.io/github/stars/tolgee/tolgee-js?style=social)](https://github.com/tolgee/tolgee-js) # Tolgee core library diff --git a/packages/core/src/__testFixtures/createElement.ts b/packages/core/src/__testFixtures/createElement.ts index a0055702ec..3f473ea474 100644 --- a/packages/core/src/__testFixtures/createElement.ts +++ b/packages/core/src/__testFixtures/createElement.ts @@ -20,6 +20,7 @@ export const createElement = ( keys.push({ key: `key${sameKeys ? `` : ` ${keyNum++}`}`, params: { a: 'aaa' }, + defaultValue: 'default value', }); } node._tolgee = { diff --git a/packages/core/src/handlers/WrappedHandler.ts b/packages/core/src/handlers/WrappedHandler.ts index ba9800a6a9..f20c1836fe 100644 --- a/packages/core/src/handlers/WrappedHandler.ts +++ b/packages/core/src/handlers/WrappedHandler.ts @@ -25,6 +25,8 @@ export class WrappedHandler extends AbstractHandler { elementWithMeta._tolgee.wrappedWithElementOnlyKey = element.getAttribute( TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE ); + elementWithMeta._tolgee.wrappedWithElementOnlyDefaultHtml = + element.innerHTML; this.elementRegistrar.register(elementWithMeta); }); } diff --git a/packages/core/src/highlighter/MouseEventHandler.ts b/packages/core/src/highlighter/MouseEventHandler.ts index c1d7decc8a..16aae07bb3 100644 --- a/packages/core/src/highlighter/MouseEventHandler.ts +++ b/packages/core/src/highlighter/MouseEventHandler.ts @@ -7,7 +7,6 @@ export class MouseEventHandler { private keysDown = new Set(); private mouseOn: Set = new Set(); private highlighted: ElementWithMeta; - private highlightedInitialBackgroundColor: string; private mouseOnChanged = new EventEmitterImpl(); private keysChanged: EventEmitterImpl = new EventEmitterImpl(); diff --git a/packages/core/src/highlighter/TranslationHighlighter.test.ts b/packages/core/src/highlighter/TranslationHighlighter.test.ts index f2f1f51a61..0ea4a93607 100644 --- a/packages/core/src/highlighter/TranslationHighlighter.test.ts +++ b/packages/core/src/highlighter/TranslationHighlighter.test.ts @@ -43,7 +43,7 @@ describe('TranslationHighlighter', () => { await savedCallback(openEvent); expect(rendererViewerMock).toBeCalledTimes(1); - expect(rendererViewerMock).toBeCalledWith('key'); + expect(rendererViewerMock).toBeCalledWith('key', 'default value'); }); test('will open translation dialog when single key multiplied', async () => { @@ -52,7 +52,7 @@ describe('TranslationHighlighter', () => { await savedCallback(openEvent); expect(rendererViewerMock).toBeCalledTimes(1); - expect(rendererViewerMock).toBeCalledWith('key'); + expect(rendererViewerMock).toBeCalledWith('key', 'default value'); }); }); diff --git a/packages/core/src/highlighter/TranslationHighlighter.ts b/packages/core/src/highlighter/TranslationHighlighter.ts index 9e097e91b3..ee3b901cde 100644 --- a/packages/core/src/highlighter/TranslationHighlighter.ts +++ b/packages/core/src/highlighter/TranslationHighlighter.ts @@ -2,8 +2,11 @@ import { ElementWithMeta } from '../types'; import { PluginManager } from '../toolsManager/PluginManager'; import { DependencyStore } from '../services/DependencyStore'; +type KeyWithDefault = { key: string; defaultValue?: string }; + export class TranslationHighlighter { public pluginManager: PluginManager; + constructor(private dependencies: DependencyStore) {} private _renderer: any; @@ -19,13 +22,18 @@ export class TranslationHighlighter { return this._renderer; } - private static getKeyOptions(node: ElementWithMeta): Set { + private static getKeyOptions(node: ElementWithMeta): KeyWithDefault[] { const nodes = Array.from(node._tolgee.nodes); - const keys = nodes.reduce( - (acc, curr) => [...acc, ...curr._tolgee.keys.map((k) => k.key)], + return nodes.reduce( + (acc, curr) => [ + ...acc, + ...curr._tolgee.keys.map((k) => ({ + key: k.key, + defaultValue: k.defaultValue, + })), + ], [] ); - return new Set(keys); } listen(element: ElementWithMeta & ElementCSSInlineStyle) { @@ -36,19 +44,36 @@ export class TranslationHighlighter { ); } - private async getKey( + private async getKeyAndDefault( mouseEvent: MouseEvent, element: ElementWithMeta - ): Promise { + ): Promise { if (element._tolgee.wrappedWithElementOnlyKey) { - return element._tolgee.wrappedWithElementOnlyKey; + return { + key: element._tolgee.wrappedWithElementOnlyKey, + defaultValue: element._tolgee.wrappedWithElementOnlyDefaultHtml, + }; } - const keys = TranslationHighlighter.getKeyOptions(element); - if (keys.size > 1) { - return await this.renderer.getKey({ keys: keys, openEvent: mouseEvent }); + const keysWithDefaults = TranslationHighlighter.getKeyOptions(element); + + // create Set to remove duplicated key values + const keySet = new Set( + keysWithDefaults.map((keyWithDefault) => keyWithDefault.key) + ); + if (keySet.size > 1) { + // this opens the popover where user chooses the key + const selectedKey = await this.renderer.getKey({ + keys: keySet, + openEvent: mouseEvent, + }); + // get the key with default + const found = keysWithDefaults.find((kwd) => kwd.key === selectedKey); + if (found) { + return found; + } } - if (keys.size === 1) { - return Array.from(keys)[0]; + if (keySet.size === 1) { + return keysWithDefaults[0]; } // eslint-disable-next-line no-console console.error('No key to translate. This seems like a bug in tolgee.'); @@ -56,9 +81,9 @@ export class TranslationHighlighter { private translationEdit = async (e: MouseEvent, element: ElementWithMeta) => { if (typeof this.renderer === 'object') { - const key = await this.getKey(e, element); + const key = await this.getKeyAndDefault(e, element); if (key) { - this.renderer.renderViewer(key); + this.renderer.renderViewer(key.key, key.defaultValue); return; } return; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6a5563132d..1b597bf850 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -55,6 +55,7 @@ export type ElementWithMeta = Element & export type ElementMeta = { wrappedWithElementOnlyKey?: string; + wrappedWithElementOnlyDefaultHtml?: string; nodes: Set; listeningForHighlighting?: boolean; removeAllEventListeners?: () => void; diff --git a/packages/ngx/projects/ngx-tolgee/README.md b/packages/ngx/projects/ngx-tolgee/README.md index f09ae1cf4c..cba6c9361e 100755 --- a/packages/ngx/projects/ngx-tolgee/README.md +++ b/packages/ngx/projects/ngx-tolgee/README.md @@ -2,7 +2,7 @@ ![@tolgee/ngx version](https://img.shields.io/npm/v/@tolgee/ngx?label=%40tolgee%2Fngx) ![types typescript](https://img.shields.io/badge/Types-Typescript-blue) ![licence](https://img.shields.io/github/license/tolgee/tolgee-js) -![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social) +[![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social)](https://twitter.com/Tolgee_i18n) [![github stars](https://img.shields.io/github/stars/tolgee/tolgee-js?style=social)](https://github.com/tolgee/tolgee-js) # Tolgee for Angular diff --git a/packages/react/README.md b/packages/react/README.md index c57ac4142c..15d46087fc 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -2,7 +2,7 @@ ![@tolgee/react version](https://img.shields.io/npm/v/@tolgee/react?label=%40tolgee%2Freact) ![types typescript](https://img.shields.io/badge/Types-Typescript-blue) ![licence](https://img.shields.io/github/license/tolgee/tolgee-js) -![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social) +[![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social)](https://twitter.com/Tolgee_i18n) [![github stars](https://img.shields.io/github/stars/tolgee/tolgee-js?style=social)](https://github.com/tolgee/tolgee-js) # Tolgee for React diff --git a/packages/ui/README.md b/packages/ui/README.md index 270d2e8272..0cf9c92213 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -1,7 +1,7 @@ ![test workflow](https://github.com/tolgee/tolgee-js/actions/workflows/test.yml/badge.svg) ![@tolgee/ui version](https://img.shields.io/npm/v/@tolgee/ui?label=%40tolgee%2Fui) ![types typescript](https://img.shields.io/badge/Types-Typescript-blue) -![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social) +[![twitter](https://img.shields.io/twitter/follow/Tolgee_i18n?style=social)](https://twitter.com/Tolgee_i18n) [![github stars](https://img.shields.io/github/stars/tolgee/tolgee-js?style=social)](https://github.com/tolgee/tolgee-js) # Tolgee JS UI library diff --git a/packages/ui/src/KeyDialog/KeyDialog.tsx b/packages/ui/src/KeyDialog/KeyDialog.tsx index 84840f71c1..b84f8e988f 100644 --- a/packages/ui/src/KeyDialog/KeyDialog.tsx +++ b/packages/ui/src/KeyDialog/KeyDialog.tsx @@ -12,7 +12,8 @@ export type Props = { export class KeyDialog extends React.Component { state = { - translationInput: null, + key: null, + defaultValue: undefined, dialogOpened: false, }; @@ -20,11 +21,12 @@ export class KeyDialog extends React.Component { super(props); } - public translationEdit(input) { + public translationEdit(key: string, defaultValue?: string) { this.setState({ ...this.state, dialogOpened: true, - translationInput: input, + defaultValue: defaultValue, + key: key, }); } @@ -33,8 +35,9 @@ export class KeyDialog extends React.Component { diff --git a/packages/ui/src/KeyDialog/TranslationDialogContextProvider.tsx b/packages/ui/src/KeyDialog/TranslationDialogContextProvider.tsx index 9f596ea24d..b8d18dd199 100644 --- a/packages/ui/src/KeyDialog/TranslationDialogContextProvider.tsx +++ b/packages/ui/src/KeyDialog/TranslationDialogContextProvider.tsx @@ -23,6 +23,7 @@ interface TranslationInterface { type DialogProps = { input: string; + defaultValue: string; open: boolean; onClose: () => void; dependencies: ComponentDependencies; @@ -100,7 +101,7 @@ export const TranslationDialogContextProvider: FunctionComponent = setTranslationsForm({ ...translationsForm }); }; - const loadTranslations = (languages?: Set, reinitiliaze = true) => { + const loadTranslations = (languages?: Set, reinitialize = true) => { translationService .getTranslationsOfKey(props.input, languages) .then(([result, languages]) => { @@ -119,10 +120,12 @@ export const TranslationDialogContextProvider: FunctionComponent = setSelectedLanguages(new Set(languages)); } - if (!translationsForm || reinitiliaze) { - setTranslationsForm( - responseToTranslationData(result?.translations) + if (!translationsForm || reinitialize) { + const translationsData = responseToTranslationData( + result?.translations ); + setTranslationsForm(translationsData); + setScreenshots( result?.screenshots?.map((sc) => ({ ...sc, @@ -270,6 +273,34 @@ export const TranslationDialogContextProvider: FunctionComponent = properties.preferredLanguages || new Set([properties.currentLanguage]) ); + // sets the default value for base language if is not stored already + useEffect(() => { + if ( + props.defaultValue && + availableLanguages && + selectedLanguages && + translationsForm + ) { + const baseLanguageDefinition = availableLanguages.find((l) => l.base); + if ( + baseLanguageDefinition && + selectedLanguages.has(baseLanguageDefinition.tag) + ) { + if (!translationsForm[baseLanguageDefinition.tag]) { + setTranslationsForm({ + ...translationsForm, + [baseLanguageDefinition.tag]: props.defaultValue, + }); + } + } + } + }, [ + availableLanguages, + translationsForm, + selectedLanguages, + props.defaultValue, + ]); + const onSelectedLanguagesChange = (value: Set) => { if (value.size) { setSelectedLanguages(value); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 874121a27b..2c293b0b56 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -36,8 +36,8 @@ export class UI { ); } - public renderViewer(key: string) { - this.viewerComponent.translationEdit(key); + public renderViewer(key: string, defaultValue?: string) { + this.viewerComponent.translationEdit(key, defaultValue); } public async getKey(props: { diff --git a/testapps/react/src/Page.tsx b/testapps/react/src/Page.tsx index 80fc8742ad..27d755daf3 100644 --- a/testapps/react/src/Page.tsx +++ b/testapps/react/src/Page.tsx @@ -88,8 +88,33 @@ export const Page: FunctionComponent = () => {
- {t('hey', undefined, true)} - {t('hey', undefined, false)} +

Keys with defaults

+

+ This is default +

+

{t({ key: 'key', defaultValue: 'This is default value!' })}

+
+ +
+

Same key multiplied in same element

+
+ {t({ key: 'key', defaultValue: 'First one' })} +
+ {t({ key: 'key', defaultValue: 'Second one' })} +
+
+ +
+

Multiple keys in same element

+
+ {t({ key: 'key', defaultValue: 'First one' })} +
+ {t({ key: 'key 2', defaultValue: 'Second one' })} +
+ {t({ key: 'key 3', defaultValue: 'Third one' })} +
+ {t({ key: 'key 3', defaultValue: 'Third one again' })} +
);