mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(packages): i18n (#1708)
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
'use client';
|
||||
|
||||
import type { AlertDialogCore } from '@videojs/core';
|
||||
import { getErrorDialogDismissLabel } from '@videojs/core';
|
||||
import { resolveTranslation } from '@videojs/core/i18n';
|
||||
import { forwardRef, type ReactNode, useCallback } from 'react';
|
||||
import { useTranslator } from '../../i18n/context';
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
import { useAlertDialogContext } from '../alert-dialog/context';
|
||||
|
||||
export interface ErrorDialogCloseProps extends UIComponentProps<'button', AlertDialogCore.State> {}
|
||||
|
||||
export const ErrorDialogClose = forwardRef<HTMLButtonElement, ErrorDialogCloseProps>(function ErrorDialogClose(
|
||||
{ render, className, style, disabled, children, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const t = useTranslator();
|
||||
const { dialog, state, stateAttrMap } = useAlertDialogContext();
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (disabled) return;
|
||||
dialog.close();
|
||||
}, [dialog, disabled]);
|
||||
|
||||
const content: ReactNode = children ?? resolveTranslation(t, getErrorDialogDismissLabel());
|
||||
|
||||
return renderElement(
|
||||
'button',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: forwardedRef,
|
||||
props: [{ type: 'button' as const, disabled, onClick: handleClick, children: content }, elementProps],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export namespace ErrorDialogClose {
|
||||
export type Props = ErrorDialogCloseProps;
|
||||
export type State = AlertDialogCore.State;
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import type { AlertDialogCore } from '@videojs/core';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { resolveErrorDialogDescription } from '@videojs/core';
|
||||
import { resolveTranslation } from '@videojs/core/i18n';
|
||||
import { forwardRef, type ReactNode } from 'react';
|
||||
import { useTranslator } from '../../i18n/context';
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
import { useAlertDialogContext } from '../alert-dialog/context';
|
||||
import { useErrorDialogContext } from './context';
|
||||
|
||||
const FALLBACK_MESSAGE = 'An error occurred. Please try again.';
|
||||
|
||||
export interface ErrorDialogDescriptionProps extends UIComponentProps<'p', AlertDialogCore.State> {}
|
||||
|
||||
export const ErrorDialogDescription = forwardRef<HTMLParagraphElement, ErrorDialogDescriptionProps>(
|
||||
function ErrorDialogDescription({ render, className, style, children, ...elementProps }, forwardedRef) {
|
||||
const t = useTranslator();
|
||||
const { state, stateAttrMap } = useAlertDialogContext();
|
||||
const { lastError } = useErrorDialogContext();
|
||||
const description = resolveErrorDialogDescription(lastError);
|
||||
const content: ReactNode = children ?? resolveTranslation(t, description);
|
||||
|
||||
return renderElement(
|
||||
'p',
|
||||
@@ -24,10 +27,7 @@ export const ErrorDialogDescription = forwardRef<HTMLParagraphElement, ErrorDial
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: forwardedRef,
|
||||
props: [
|
||||
{ id: state.descriptionId, children: children ?? lastError?.message ?? FALLBACK_MESSAGE },
|
||||
elementProps,
|
||||
],
|
||||
props: [{ id: state.descriptionId, children: content }, elementProps],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import type { AlertDialogCore } from '@videojs/core';
|
||||
import { getErrorDialogTitleLabel } from '@videojs/core';
|
||||
import { resolveTranslation } from '@videojs/core/i18n';
|
||||
import { forwardRef, type ReactNode } from 'react';
|
||||
import { useTranslator } from '../../i18n/context';
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
import { useAlertDialogContext } from '../alert-dialog/context';
|
||||
|
||||
export interface ErrorDialogTitleProps extends UIComponentProps<'h2', AlertDialogCore.State> {}
|
||||
|
||||
export const ErrorDialogTitle = forwardRef<HTMLHeadingElement, ErrorDialogTitleProps>(function ErrorDialogTitle(
|
||||
{ render, className, style, children, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const t = useTranslator();
|
||||
const { state, stateAttrMap } = useAlertDialogContext();
|
||||
const content: ReactNode = children ?? resolveTranslation(t, getErrorDialogTitleLabel());
|
||||
|
||||
return renderElement(
|
||||
'h2',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: forwardedRef,
|
||||
props: [{ id: state.titleId, children: content }, elementProps],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export namespace ErrorDialogTitle {
|
||||
export type Props = ErrorDialogTitleProps;
|
||||
export type State = AlertDialogCore.State;
|
||||
}
|
||||
@@ -1,17 +1,11 @@
|
||||
export {
|
||||
AlertDialogClose as Close,
|
||||
type AlertDialogCloseProps as CloseProps,
|
||||
} from '../alert-dialog/alert-dialog-close';
|
||||
export {
|
||||
AlertDialogPopup as Popup,
|
||||
type AlertDialogPopupProps as PopupProps,
|
||||
} from '../alert-dialog/alert-dialog-popup';
|
||||
export {
|
||||
AlertDialogTitle as Title,
|
||||
type AlertDialogTitleProps as TitleProps,
|
||||
} from '../alert-dialog/alert-dialog-title';
|
||||
export { ErrorDialogClose as Close, type ErrorDialogCloseProps as CloseProps } from './error-dialog-close';
|
||||
export {
|
||||
ErrorDialogDescription as Description,
|
||||
type ErrorDialogDescriptionProps as DescriptionProps,
|
||||
} from './error-dialog-description';
|
||||
export { ErrorDialogRoot as Root, type ErrorDialogRootProps as RootProps } from './error-dialog-root';
|
||||
export { ErrorDialogTitle as Title, type ErrorDialogTitleProps as TitleProps } from './error-dialog-title';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { cleanup, render, screen } from '@testing-library/react';
|
||||
import { registerI18n, resetI18nRegistry } from '@videojs/core/i18n';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { I18nProvider } from '../../../i18n';
|
||||
import { createPlayerWrapper } from '../../../testing/mocks';
|
||||
import { ErrorDialog } from '..';
|
||||
|
||||
afterEach(() => {
|
||||
resetI18nRegistry();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('ErrorDialog', () => {
|
||||
it('shows translated title, description, and dismiss label when locale is es', () => {
|
||||
registerI18n('es', {
|
||||
'Something went wrong.': 'Algo salió mal.',
|
||||
OK: 'Aceptar',
|
||||
'A network error caused the media download to fail.': 'Error de red.',
|
||||
'An error occurred. Please try again.': 'Ocurrió un error. Inténtalo de nuevo.',
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: 2,
|
||||
message: 'A network error caused the media download to fail.',
|
||||
};
|
||||
const { Wrapper } = createPlayerWrapper({
|
||||
error,
|
||||
dismissError: vi.fn(),
|
||||
});
|
||||
|
||||
render(
|
||||
<Wrapper>
|
||||
<I18nProvider locale="es">
|
||||
<ErrorDialog.Root>
|
||||
<ErrorDialog.Popup>
|
||||
<ErrorDialog.Title data-testid="title" />
|
||||
<ErrorDialog.Description data-testid="description" />
|
||||
<ErrorDialog.Close data-testid="close" />
|
||||
</ErrorDialog.Popup>
|
||||
</ErrorDialog.Root>
|
||||
</I18nProvider>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('title').textContent).toBe('Algo salió mal.');
|
||||
expect(screen.getByTestId('description').textContent).toBe('Error de red.');
|
||||
expect(screen.getByTestId('close').textContent).toBe('Aceptar');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user