Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feats: translation metadata for docs and hoisted serialization #30

Merged
merged 2 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export const getDefaultDocumentNode = ({schemaType}) => {
localeIdAdapter: (translationVendorId) => sanityId,

/**
* For field-level translations, the key for the "source content"
* the key for the "source content" (for field level) or the code in the
* language field on the "base document" (for document level)
* (e.g. "en" or "en_US").
*/
baseLanguage: 'en_US',
Expand Down
46 changes: 46 additions & 0 deletions docs/00-faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## Where should I place the Translations Tab?
The Translations Tab should be invoked as a view. This can be done in the `getDefaultDocumentNode` function in `schemas.js`:
```js
import {TranslationsTab, defaultDocumentLevelConfig, defaultFieldLevelConfig} from 'sanity-translations-tab'

export const defaultDocumentNodeResolver = (S) =>
S.document().views([
// Give all documents the JSON preview,
// as well as the default form view
S.view.form(),
S.view.component(TranslationsTab).options(defaultDocumentLevelConfig)
])
```
If using a document-level translation pattern, you should likely only include this view in your "base" language. Please see the document internationalization plugin on building different desk structure options for different locales. (example code coming soon)

## What happens when I click the "Create Job" button?
The tab will do the following:
1. Fetch the latest draft of the document you're currently in.
2. Filter out (as best as it can) any fields that should not be translated (references, dates, numbers, etc.). It will also utilize options passed in to ignore certain fields and objects. See more in the Advanced Configuration docs.
3. Serialize the document into an HTML string. It will utilize options to serialize objects in particular ways, if provided.
4. Send the HTML string to the translation vendor's API, along with the locale code of the language(s) you want to translate to.
5. Look up the translation job ID in the response from the translation vendor (this will either match your document ID or be invoked by a custom job name resolver [coming soon]).
6. Show you the progress of the ongoing translation and a link to the job in the vendor, if available.

## How am I seeing my progress?
On load, the tab fetches the total amount of strings that have reached the LAST stage of translation and are ready to be imported. That is divided over the total amount of strings in the document, and the progress bar is updated accordingly.

## How do I import translations?
When the translation vendor has completed the translation, you can click the "Import Translations" button. This will do the following:
1. Deserialize the HTML string from the translation vendor into a Sanity document.
2. Fetch the revision of the document you're currently in at the time the translation was sent, if available. (This is to resolve the translation as smoothly as possible, in case the document has changed since it was sent to translation and cannot resolve conflicts)
3. Compare the two documents and merge the translated content with anything that was not sent for translation.
4. Issue a patch with the relevant merges to the relevant document. If using document internationalization, this will also update translation metadata.

## How can I prevent certain fields from being sent to translation?
You can pass in a `stopTypes` parameter to name all objects you do not want translated. Alternately, the serializer also introspects your schema. You can set `localize: false` on any field you do not want translated.
```js
fields: [
defineField({
name: 'categories',
type: 'array',
//ts-ignore
localize: false,
...
})]
```
60 changes: 60 additions & 0 deletions docs/01-advanced-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## Advanced configuration

This plugin provides defaults for most configuration options, but they can be overridden as needed. Please refer to the types to see what you should declare, but we also provide the type for all options, which we recommend using for quicker development.

```typescript
//sanity.config.ts
import {TranslationsTab, defaultDocumentLevelConfig, defaultFieldLevelConfig, TranslationsTabConfigOptions} from 'sanity-translations-tab'

const customConfig = {
...defaultDocumentLevelConfig,
//baseLanguage is `en` by default, overwrite as needed. This is important for coalescing translations and creating accurate translation metadata.
baseLanguage: 'en_US',
//this is the field that will be used to determine the language of the document. It is `language` by default.
languageField: 'locale',
serializationOptions: {
//use this option to exclude objects that do not need to be translated. The plugin already uses defaults for references, dates, numbers, etc.
additionalStopTypes: ['colorTheme'],
//use this option to serialize objects in a particular way. The plugin will try to filter and serialize objects as best as it can, but you can override this behavior here.
//For now, you should also include these for annotations and inline objects in PortableText, if you want them to be translated.
//NOTE: it is VERY important to include the _type as a class and the _key as id for accurately deserializing and coalescing
additionalSerializers: {
testObject: ({value}) => {
return `<span class="${value._type}" id="${value._key}">${value.title}</span>`
}
},
//Create a method to deserialize any custom serialization rules
additonalDeserializers: {
testObject: ({node}) => {
return {
_type: 'testObject',
_key: node.id,
title: node.textContent
}
}
},
//Block text requires a special deserialization format based on @sanity/block-tools. Use this option for any annotations or inline objects that need to be translated.
additionalBlockDeserializers: [
{
deserialize(el, next): TypedObject | undefined {
if (!el.className || el.className?.toLowerCase() !== 'myannotation') {
return undefined
}

const markDef = {
_key: el.id,
_type: 'myAnnotation',
}

return {
_type: '__annotation',
markDef: markDef,
children: next(el.childNodes),
}
},
},
]
}
//adapter, baseLanguage, secretsNamespace, importTranslation, exportForTranslation should likely not be touched unless you very much want to customize your plugin.
} satisfies TranslationsTabConfigOptions
```
116 changes: 89 additions & 27 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sanity-translations-tab",
"version": "4.0.1",
"version": "4.1.0",
"description": "This is the base module for implementing common translation vendor tasks from a Studio, such as sending content to be translated in some specific languages, importing content back etc. Not useful on its own, but vendor-specific plugins will use this for its chrome.",
"keywords": [
"sanity",
Expand Down Expand Up @@ -53,6 +53,8 @@
"watch": "pkg-utils watch --strict"
},
"dependencies": {
"@portabletext/to-html": "^2.0.3",
"@sanity/block-tools": "^3.16.4",
"@sanity/incompatible-plugin": "^1.0.4",
"@sanity/ui": "^1",
"@sanity/util": "^3.16.4",
Expand Down
32 changes: 13 additions & 19 deletions src/components/TranslationsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,13 @@ import {TranslationContext} from './TranslationContext'
import {TranslationView} from './TranslationView'
import {useClient} from '../hooks/useClient'
import {useSecrets} from '../hooks/useSecrets'
import {
Adapter,
ExportForTranslation,
ImportTranslation,
Secrets,
WorkflowIdentifiers,
} from '../types'
import {Secrets, TranslationsTabConfigOptions} from '../types'

type TranslationTabProps = {
document: {
displayed: SanityDocument
}
options: {
adapter: Adapter
baseLanguage: string
secretsNamespace: string | null
exportForTranslation: ExportForTranslation
importTranslation: ImportTranslation
workflowOptions?: WorkflowIdentifiers[]
localeIdAdapter?: (id: string) => string
}
options: TranslationsTabConfigOptions
}

const TranslationTab = (props: TranslationTabProps) => {
Expand All @@ -49,6 +35,7 @@ const TranslationTab = (props: TranslationTabProps) => {
displayed && displayed._id ? (displayed._id.split('drafts.').pop() as string) : ''

const {errors, importTranslation, exportForTranslation} = useMemo(() => {
const {serializationOptions, baseLanguage, languageField} = props.options
const ctx = {
client,
schema,
Expand All @@ -69,8 +56,15 @@ const TranslationTab = (props: TranslationTabProps) => {
}

const contextImportTranslation = (localeId: string, doc: string) => {
const baseLanguage = props.options.baseLanguage
return importTranslationFunc(documentId, localeId, doc, ctx, baseLanguage)
return importTranslationFunc(
documentId,
localeId,
doc,
ctx,
baseLanguage,
serializationOptions,
languageField,
)
}

const exportTranslationFunc = props.options.exportForTranslation
Expand All @@ -86,7 +80,7 @@ const TranslationTab = (props: TranslationTabProps) => {
}

const contextExportForTranslation = (id: string) => {
return exportTranslationFunc(id, ctx)
return exportTranslationFunc(id, ctx, baseLanguage, serializationOptions, languageField)
}

return {
Expand Down
Loading