Skip to content

Commit

Permalink
feat(i18n): convert file, image and imagetool input strings to use i1…
Browse files Browse the repository at this point in the history
…8n primitives (#4984)
  • Loading branch information
bjoerge authored and rexxars committed Nov 17, 2023
1 parent 5a4fc06 commit 5cab5ee
Show file tree
Hide file tree
Showing 17 changed files with 345 additions and 66 deletions.
112 changes: 112 additions & 0 deletions dev/test-studio/plugins/locale-no-nb/bundles/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,118 @@ const studioResources: Record<StudioLocaleResourceKeys, string> = {
/** Error label for toast when array could not resolve the initial value */
'inputs.array.error.cannot-resolve-initial-value-title':
'Kan ikke finne startverdi for type: {{schemaTypeTitle}}: {{errorMessage}}.',

/** --- File (Image, File and ImageTool) Inputs --- */

/** Accessibility label for button to open image edit dialog */
'inputs.files.image.actions-menu.edit-details.aria-label': 'Åpne bilderedigeringsdialog',

/** Open image options menu */
'inputs.files.image.actions-menu.options.label': 'Åpne bildeinnstillingsmeny',

/** Tooltip text for action to crop image */
'inputs.files.image.actions-menu.crop-image-tooltip': 'Beskjær bilde',

/** The upload could not be completed at this time. */
'inputs.files.image.upload-error.description':
'Opplastingen kunne ikke fullføres på dette tidspunktet.',

/** Upload failed */
'inputs.files.image.upload-error.title': 'Opplasting mislyktes',

/** Edit hotspot and crop */
'inputs.files.image.hotspot-dialog.title': 'Rediger fokuspunkt og beskjær',

/** Preview of uploaded image */
'inputs.files.image.preview-uploaded-image': 'Forhåndsvisning av opplastet bilde',

/** Cannot upload this file here */
'inputs.files.image.drag-overlay.cannot-upload-here': 'Kan ikke laste opp denne filen her',

/** This field is read only */
'inputs.files.image.drag-overlay.this-field-is-read-only': 'Dette feltet er skrivebeskyttet',

/** Drop image to upload */
'inputs.files.image.drag-overlay.drop-to-upload-image': 'Slipp bilde for å laste opp',

/** Invalid image value */
'inputs.files.image.invalid-image-warning.title': 'Ugyldig bildeverdi',

/** The value of this field is not a valid image. Resetting this field will let you choose a new image. */
'inputs.files.image.invalid-image-warning.description':
'Verdien i dette feltet er ikke et gyldig bilde. Ved å tilbakestille dette feltet kan du velge et nytt bilde.',

/** The URL is copied to the clipboard */
'inputs.files.common.actions-menu.notification.url-copied':
'URL-en er kopiert til utklippstavlen',

/** Replace */
'inputs.files.common.actions-menu.replace.label': 'Erstatt',

/** Upload */
'inputs.files.common.actions-menu.upload.label': 'Last opp',

/** Download */
'inputs.files.common.actions-menu.download.label': 'Last ned',

/** Copy URL */
'inputs.files.common.actions-menu.copy-url.label': 'Kopier URL',

/** Clear field */
'inputs.files.common.actions-menu.clear-field.label': 'Tøm felt',

/** Can't upload files here */
'inputs.files.common.placeholder.upload-not-supported': 'Kan ikke laste opp filer her',

/** Read only */
'inputs.files.common.placeholder.read-only': 'Skrivebeskyttet',

/** Drop to upload file */
'inputs.files.common.placeholder.drop-to-upload_file': 'Slipp for å laste opp fil',

/** Drop to upload image */
'inputs.files.common.placeholder.drop-to-upload_image': 'Slipp for å laste opp bilde',

/** Cannot upload `{{count}}` files */
'inputs.files.common.placeholder.cannot-upload-some-files_one': 'Kan ikke laste opp fil',
'inputs.files.common.placeholder.cannot-upload-some-files_other':
'Kan ikke laste opp {{count}} filer',

/** Drag or paste file here */
'inputs.files.common.placeholder.drag-or-paste-to-upload_file': 'Dra eller lim inn fil her',
/** Drag or paste image here */
'inputs.files.common.placeholder.drag-or-paste-to-upload_image': 'Dra eller lim inn bilde her',

/** Drop to upload */
'inputs.files.common.drop-message.drop-to-upload': 'Slipp for å laste opp',

/** Drop to upload `{{count}}` file */
'inputs.files.common.drop-message.drop-to-upload-multi_one':
'Slipp for å laste opp {{count}} fil',

/** Drop to upload `{{count}}` files */
'inputs.files.common.drop-message.drop-to-upload-multi_other':
'Slipp for å laste opp {{count}} filer',

/** Uploading <FileName/> */
'input.files.common.upload-progress': 'Laster opp <FileName/>',

/** Incomplete upload */
'inputs.files.common.stale-upload-warning.title': 'Ufullstendig opplasting',

/** An upload has made no progress for at least `{{staleThresholdMinutes}}` minutes and likely got interrupted. You can safely clear the incomplete upload and try uploading again. */
'inputs.files.common.stale-upload-warning.description':
'En opplasting har ikke gjort fremskritt på minst {{staleThresholdMinutes}} minutter og ble sannsynligvis avbrutt. Du kan trygt fjerne den ufullstendige opplastingen og prøve å laste opp på nytt.',

/** Clear upload */
'inputs.files.common.stale-upload-warning.clear': 'Fjern opplasting',

/** Hotspot & Crop */
'inputs.files.imagetool.field.title': 'Fokuspunkt & beskjæring',

/** Adjust the rectangle to crop image. Adjust the circle to specify the area that should always be visible. */
'inputs.files.imagetool.field.description':
'Juster rektangelet for å beskjære bildet. Juster sirkelen for å spesifisere området som alltid skal være synlig.',
}

export default studioResources
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ describe('FileInput with empty state', () => {
observeAsset: observeAssetStub,
render: (inputProps) => <BaseFileInput {...inputProps} />,
})

expect(result.queryByTestId('file-button-input')!.getAttribute('value')).toBe('')
expect(result.queryByText('Drag or paste file here')).toBeInTheDocument()
})

it.todo('renders new file when a new file in uploaded')
Expand Down Expand Up @@ -127,7 +125,9 @@ describe('FileInput with empty state', () => {
render: (inputProps) => <BaseFileInput {...inputProps} directUploads={false} />,
})

expect(result.queryByText(`Can't upload files here`)).toBeInTheDocument()
expect(result.queryByTestId('file-input-upload-button')!.getAttribute('data-disabled')).toBe(
'true',
)
})

/* readOnly - the file input is read only or not */
Expand Down Expand Up @@ -184,7 +184,7 @@ describe('FileInput with empty state', () => {
})

await waitFor(() => {
expect(result.queryByText(`Read only`)).toBeInTheDocument()
expect(result.queryByText(`inputs.files.common.placeholder.read-only`)).toBeInTheDocument()
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useGlobalKeyDown,
} from '@sanity/ui'
import styled from 'styled-components'
import {useTranslation} from '../../../../i18n'

export const MenuActionsWrapper = styled(Inline)`
position: absolute;
Expand Down Expand Up @@ -88,12 +89,16 @@ export function ImageActionsMenu(props: ImageActionsMenuProps) {
}
}, [isMenuOpen, menuElement])

const {t} = useTranslation()
return (
<MenuActionsWrapper data-buttons space={1} padding={2}>
{showEdit && (
<Tooltip content={<Text size={1}>Crop image</Text>} padding={2}>
<Tooltip
content={<Text size={1}>{t('inputs.files.image.actions-menu.crop-image-tooltip')}</Text>}
padding={2}
>
<Button
aria-label="Open image edit dialog"
aria-label={t('inputs.files.image.actions-menu.edit-details.aria-label')}
data-testid="options-menu-edit-details"
icon={CropIcon}
mode="ghost"
Expand All @@ -112,7 +117,7 @@ export function ImageActionsMenu(props: ImageActionsMenuProps) {
constrainSize
>
<Button
aria-label="Open image options menu"
aria-label={t('inputs.files.image.actions-menu.options.label')}
data-testid="options-menu-button"
icon={EllipsisVerticalIcon}
mode="ghost"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {PresenceOverlay} from '../../../../presence'
import {FIXME} from '../../../../FIXME'
import {ImperativeToast} from '../../../../components'
import {ChangeIndicator} from '../../../../changeIndicators'
import {TFunction} from '../../../../i18n'
import {ImageActionsMenu} from './ImageActionsMenu'
import {ImagePreview} from './ImagePreview'
import {InvalidImageWarning} from './InvalidImageWarning'
Expand All @@ -74,6 +75,7 @@ export interface BaseImageInputProps
observeAsset: (documentId: string) => Observable<ImageAsset>
resolveUploader: UploaderResolver
client: SanityClient
t: (key: string, values?: Record<string, string>) => string
}

const getDevicePixelRatio = () => {
Expand Down Expand Up @@ -182,7 +184,7 @@ export class BaseImageInput extends React.PureComponent<BaseImageInputProps, Bas
}

uploadWith = (uploader: Uploader, file: File, assetDocumentProps: UploadOptions = {}) => {
const {schemaType, onChange, client} = this.props
const {schemaType, onChange, client, t} = this.props
const {label, title, description, creditLine, source} = assetDocumentProps
const options = {
metadata: get(schemaType, 'options.metadata'),
Expand All @@ -208,8 +210,8 @@ export class BaseImageInput extends React.PureComponent<BaseImageInputProps, Bas
console.error(err)
this.toast?.push({
status: 'error',
description: 'The upload could not be completed at this time.',
title: 'Upload failed',
description: t('inputs.files.image.upload-error.description'),
title: t('inputs.files.image.upload-error.title'),
})

this.clearUploadStatus()
Expand Down Expand Up @@ -393,14 +395,14 @@ export class BaseImageInput extends React.PureComponent<BaseImageInputProps, Bas
}

renderHotspotInput = (hotspotInputProps: Omit<InputProps, 'renderDefault'>) => {
const {value, changed, id, imageUrlBuilder} = this.props
const {value, changed, id, imageUrlBuilder, t} = this.props

const withImageTool = this.isImageToolEnabled() && value && value.asset

return (
<Dialog
__unstable_autoFocus={false}
header="Edit hotspot and crop"
header={t('inputs.files.image.hotspot-dialog.title')}
id={`${id}_dialog`}
onClickOutside={this.handleCloseDialog}
onClose={this.handleCloseDialog}
Expand All @@ -426,7 +428,7 @@ export class BaseImageInput extends React.PureComponent<BaseImageInputProps, Bas
}

renderPreview = () => {
const {value, schemaType, readOnly, directUploads, imageUrlBuilder, resolveUploader} =
const {value, schemaType, readOnly, directUploads, imageUrlBuilder, t, resolveUploader} =
this.props

if (!value || !isImageSource(value)) {
Expand All @@ -452,7 +454,7 @@ export class BaseImageInput extends React.PureComponent<BaseImageInputProps, Bas
isRejected={rejectedFilesCount > 0 || !directUploads}
readOnly={readOnly}
src={imageUrl}
alt="Preview of uploaded image"
alt={t('inputs.files.image.preview-uploaded-image')}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {ComponentProps, useCallback, useEffect, useState} from 'react'

import {AccessDeniedIcon, ImageIcon, ReadOnlyIcon} from '@sanity/icons'
import {Box, Card, CardTone, Heading, Text, useElementRect} from '@sanity/ui'
import {useTranslation} from '../../../../i18n'
import {
MAX_DEFAULT_HEIGHT,
RatioBox,
Expand Down Expand Up @@ -67,6 +68,7 @@ export function ImagePreview(props: ComponentProps<typeof Card> & Props) {
setLoaded(true)
}, [])

const {t} = useTranslation()
return (
<RatioBox {...rest} ref={setRootElement} style={{height: rootHeight}} tone="transparent">
<Card data-container tone="inherit">
Expand All @@ -91,7 +93,7 @@ export function ImagePreview(props: ComponentProps<typeof Card> & Props) {
<HoverIcon isRejected={isRejected} readOnly={readOnly} />
</Heading>
</Box>
<HoverText isRejected={isRejected} readOnly={readOnly} />
<Text size={1}>{t(getHoverTextTranslationKey({isRejected, readOnly}))}</Text>
</>
}
/>
Expand All @@ -110,16 +112,19 @@ function HoverIcon({isRejected, readOnly}: {isRejected: boolean; readOnly?: bool
return <ImageIcon />
}

function HoverText({isRejected, readOnly}: {isRejected: boolean; readOnly?: boolean}) {
let message = 'Drop image to upload'
function getHoverTextTranslationKey({
isRejected,
readOnly,
}: {
isRejected: boolean
readOnly?: boolean
}) {
if (isRejected) {
message = 'Cannot upload this file here'
return 'inputs.files.image.drag-overlay.this-field-is-read-only'
}
if (readOnly) {
message = 'This field is read only'
}

return <Text size={1}>{message}</Text>
return readOnly
? 'inputs.files.image.drag-overlay.cannot-upload-here'
: 'inputs.files.image.drag-overlay.drop-to-upload-image'
}

function OverlayComponent({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {ResetIcon, WarningOutlineIcon} from '@sanity/icons'
import {Card, Flex, Box, Text, Stack, Button} from '@sanity/ui'
import React from 'react'
import styled from 'styled-components'
import {useTranslation} from '../../../../i18n'

type Props = {
onClearValue?: () => void
Expand All @@ -12,6 +13,7 @@ const ButtonWrapper = styled(Button)`
`

export function InvalidImageWarning({onClearValue}: Props) {
const {t} = useTranslation('sanity')
return (
<Card tone="caution" padding={4} border radius={2}>
<Flex gap={4} marginBottom={4}>
Expand All @@ -22,12 +24,9 @@ export function InvalidImageWarning({onClearValue}: Props) {
</Box>
<Stack space={3}>
<Text size={1} weight="semibold">
Invalid image value
</Text>
<Text size={1}>
The value of this field is not a valid image. Resetting this field will let you choose a
new image.
{t('inputs.files.image.invalid-image-warning.title')}
</Text>
<Text size={1}>{t('inputs.files.image.invalid-image-warning.description')}</Text>
</Stack>
</Flex>
<ButtonWrapper icon={ResetIcon} text="Reset value" onClick={onClearValue} mode="ghost" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {ObjectInputProps} from '../../../types'
import {useDidUpdate} from '../../../hooks/useDidUpdate'
import {ChangeIndicator} from '../../../../changeIndicators'
import {EMPTY_ARRAY} from '../../../../util'
import {useTranslation} from '../../../../i18n'
import {ImageTool, HotspotImage, DEFAULT_CROP, DEFAULT_HOTSPOT} from './imagetool'
import {useLoadImage} from './useLoadImage'

Expand Down Expand Up @@ -108,11 +109,12 @@ export function ImageToolInput(props: ImageToolInputProps) {
[onChange, readOnly, schemaType.fields],
)

const {t} = useTranslation()
return (
<FormField
title="Hotspot &amp; crop"
title={t('inputs.files.imagetool.field.title')}
level={level}
description="Adjust the rectangle to crop image. Adjust the circle to specify the area that should always be visible."
description={t('inputs.files.imagetool.field.description')}
__unstable_presence={presence}
>
<div>
Expand Down
Loading

0 comments on commit 5cab5ee

Please sign in to comment.