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
  • Loading branch information
bjoerge committed Oct 10, 2023
1 parent ab1477a commit 85f0269
Show file tree
Hide file tree
Showing 15 changed files with 318 additions and 61 deletions.
102 changes: 102 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 @@ -101,6 +101,108 @@ const studioResources: Record<StudioLocaleResourceKeys, string> = {

/** Label for selecting a hour preset. Receives a `time` param as a string on hh:mm format and a `date` param as a Date instance denoting the preset date */
'inputs.datetime.calendar.action.set-to-time-preset': '{{time}} on {{date, datetime}}',

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

/** Open image edit dialog */
'inputs.files.image.actions-menu.edit-details.label': 'Åpne bilde redigeringsdialog',

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

/** 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 `{{type}}` */
'inputs.files.common.placeholder.drop-to-upload': 'Slipp for å laste opp {{type}}',

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

/** Drag or paste `{{type}}` here */
'inputs.files.common.placeholder.drag-or-paste-to-upload': 'Dra eller lim inn {{type}} 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 @@ -2,6 +2,7 @@ import React, {MouseEventHandler, ReactNode, useCallback, useEffect, useState} f
import {EllipsisVerticalIcon, CropIcon} from '@sanity/icons'
import {Button, Inline, Menu, Popover, useClickOutside, useGlobalKeyDown} from '@sanity/ui'
import styled from 'styled-components'
import {useTranslation} from '../../../../i18n'

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

const {t} = useTranslation()
return (
<MenuActionsWrapper data-buttons space={1} padding={2}>
{showEdit && (
<Button
aria-label="Open image edit dialog"
aria-label={t('inputs.files.image.actions-menu.edit-details.label')}
data-testid="options-menu-edit-details"
icon={CropIcon}
mode="ghost"
Expand All @@ -101,7 +103,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: TFunction
}

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 @@ -395,14 +397,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 @@ -428,7 +430,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 @@ -454,7 +456,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
33 changes: 26 additions & 7 deletions packages/sanity/src/core/form/inputs/files/common/ActionsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {MouseEventHandler, useCallback} from 'react'

import {UploadIcon, CopyIcon, ResetIcon, DownloadIcon} from '@sanity/icons'
import {Box, MenuItem, MenuDivider, Label, useToast} from '@sanity/ui'
import {useTranslation} from '../../../../i18n'
import {FileInputMenuItem} from './FileInputMenuItem/FileInputMenuItem'

interface Props {
Expand All @@ -19,40 +20,58 @@ export function ActionsMenu(props: Props) {
const {onUpload, onReset, readOnly, accept, directUploads, browse, downloadUrl, copyUrl} = props

const {push: pushToast} = useToast()
const {t} = useTranslation()

const handleCopyURL = useCallback(() => {
navigator.clipboard.writeText(copyUrl || '')
pushToast({closable: true, status: 'success', title: 'The URL is copied to the clipboard'})
}, [pushToast, copyUrl])
pushToast({
closable: true,
status: 'success',
title: t('inputs.files.common.actions-menu.notification.url-copied'),
})
}, [copyUrl, pushToast, t])

return (
<>
<Box padding={2}>
<Label muted size={1}>
Replace
{t('inputs.files.common.actions-menu.replace.label')}
</Label>
</Box>
<FileInputMenuItem
icon={UploadIcon}
mode="bleed"
onSelect={onUpload}
accept={accept}
text="Upload"
text={t('inputs.files.common.actions-menu.upload.label')}
data-testid="file-input-upload-button"
disabled={readOnly || !directUploads}
fontSize={2}
/>
{browse}

{(downloadUrl || copyUrl) && <MenuDivider />}
{downloadUrl && <MenuItem as="a" icon={DownloadIcon} text="Download" href={downloadUrl} />}
{copyUrl && <MenuItem icon={CopyIcon} text="Copy URL" onClick={handleCopyURL} />}
{downloadUrl && (
<MenuItem
as="a"
icon={DownloadIcon}
text={t('inputs.files.common.actions-menu.download.label')}
href={downloadUrl}
/>
)}
{copyUrl && (
<MenuItem
icon={CopyIcon}
text={t('inputs.files.common.actions-menu.copy-url.label')}
onClick={handleCopyURL}
/>
)}

<MenuDivider />
<MenuItem
tone="critical"
icon={ResetIcon}
text="Clear field"
text={t('inputs.files.common.actions-menu.clear-field.label')}
onClick={onReset}
disabled={readOnly}
data-testid="file-input-clear"
Expand Down
Loading

0 comments on commit 85f0269

Please sign in to comment.