Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
2e635d7
initial code all together
cngonzalez Jun 27, 2021
c811824
changes to adapter
cngonzalez Jun 28, 2021
c2fafcf
old adapter version
cngonzalez Jul 6, 2021
d32f5e7
final adapter tweaks
cngonzalez Jul 6, 2021
b0e4d43
testing published version
cngonzalez Jul 7, 2021
f176111
new readme and update serializer version
cngonzalez Jul 14, 2021
3d8fd35
mv readme
cngonzalez Jul 14, 2021
97e6877
Update README.md
cngonzalez Jul 14, 2021
0e7ccd0
update serialization version
cngonzalez Aug 1, 2021
ba052de
Update README.md
cngonzalez Sep 13, 2021
98d72ab
update to new serializer
cngonzalez Sep 16, 2021
648b30b
Merge branch 'main' of github.com:sanity-io/sanity-plugin-studio-smar…
cngonzalez Sep 16, 2021
57628a2
update dependency versions and change createTask flow
cngonzalez Sep 22, 2021
b04557f
Merge pull request #2 from sanity-io/fix-serializer
cngonzalez Sep 22, 2021
a0f49c3
update packages and account for functionality changes
cngonzalez Dec 2, 2021
6941b97
Update packages (#4)
cngonzalez Dec 2, 2021
382a9b2
Update package.json
cngonzalez Jan 13, 2022
dfc3246
pull correct type
cngonzalez Jan 13, 2022
cedc383
Merge branch 'main' of github.com:sanity-io/sanity-plugin-studio-smar…
cngonzalez Jan 13, 2022
df22ca7
Set workflow in task creation (#6)
apennell Feb 14, 2022
27acf17
V1.4.0 (#8)
cngonzalez Feb 18, 2022
7646f50
workflows
cngonzalez Feb 18, 2022
718950f
Handle error and await for finished promises (#7)
apennell Feb 23, 2022
303d45c
Update package.json
cngonzalez Feb 23, 2022
b6af0d8
update package-lock
cngonzalez Feb 23, 2022
f048f72
All turtles add link (#10)
cngonzalez Mar 22, 2022
acf897a
Update translations-tab dependency (#11)
apennell Apr 12, 2022
1d475a4
Next (#13)
cngonzalez Jul 1, 2022
c854111
update to use latest exports from tab (#14)
cngonzalez Aug 7, 2022
5a84951
update to translations tab 2.0.4 (#15)
cngonzalez Aug 24, 2022
35f0aa9
chore: update translations tab to 2.0.5 (#16)
cngonzalez Aug 24, 2022
214c663
Update README.md
cngonzalez Aug 24, 2022
b42bafd
Update README.md
cngonzalez Aug 25, 2022
bb2426b
Update README.md
cngonzalez Sep 14, 2022
f189f7e
Update README.md
cngonzalez Sep 14, 2022
5aa3402
chore: update to upstream tab version
cngonzalez Sep 27, 2022
33e9aee
feat!: update to studio v3 (#19)
cngonzalez Mar 10, 2023
106aa36
Update README.md
cngonzalez Mar 17, 2023
267dba8
Update README.md
cngonzalez Mar 28, 2023
1e16f14
feat!: upgrade to v4 (#21)
cngonzalez Aug 1, 2023
faa039b
chore: upgrade to upstream doc i18n fixes (#22)
cngonzalez Sep 5, 2023
f3e7d3b
fix: hotfix for custom job names (#23)
cngonzalez Sep 12, 2023
6a3608a
fix: filter out deleted jobs (#24)
arthur-pinner Sep 12, 2023
6e25342
chore: update version
cngonzalez Sep 12, 2023
5665fe0
chore: update version of translations tab and add documentation
cngonzalez Sep 13, 2023
b91ef65
Update 01-advanced-configuration.md
cngonzalez Sep 13, 2023
ec70239
Feat/auto import support (#26)
cngonzalez Nov 6, 2023
896be39
chore: upgrade tab version (#27)
cngonzalez Nov 20, 2023
22c67ce
feat: use translationTab mergeWithTargetLocale (#29)
cngonzalez May 6, 2024
54ac5d3
Update 01-advanced-configuration.md
cngonzalez Sep 26, 2024
a4dbdac
chore: add Renovate configuration file for dependency management
stipsan Jul 10, 2025
de7b36d
Rename .github/workflows/renovate.json to .github/renovate.json
stipsan Jul 10, 2025
1e3de60
fix(deps): allow studio v4 peer dep ranges
stipsan Jul 10, 2025
0196210
chore(release): 4.3.1 [skip ci]
semantic-release-bot Jul 10, 2025
8c81b92
Update README.md
RitaDias Jul 11, 2025
9a8eb09
fix: update package.json and package-lock.json to support Sanity v5 (…
KJHeartbreaker Dec 29, 2025
a14233c
chore(release): 4.3.2 [skip ci]
semantic-release-bot Dec 29, 2025
a64c6d8
fix(deps): Update dependency sanity-translations-tab to v5 (#35)
renovate[bot] Jan 7, 2026
c524d8c
chore(release): 4.3.3 [skip ci]
semantic-release-bot Jan 7, 2026
d5f06e0
Add 'plugins/sanity-plugin-studio-smartling/' from commit 'c524d8c96c…
cursoragent Jun 12, 2026
e5c6d8a
chore(sanity-plugin-studio-smartling): remove files that will be rege…
cursoragent Jun 12, 2026
884332f
feat(sanity-plugin-studio-smartling): migrate plugin to monorepo
cursoragent Jun 12, 2026
f185029
merge: sync with main (Sanity Studio v6 upgrade)
cursoragent Jun 12, 2026
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
15 changes: 15 additions & 0 deletions .changeset/studio-smartling-monorepo-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'sanity-plugin-studio-smartling': major
---

Port sanity-plugin-studio-smartling to the Sanity plugins monorepo

This major release includes several breaking changes as part of the migration to the monorepo:

- **React Compiler enabled**: The plugin is now built with React Compiler targeting React 19
- **ESM-only**: CommonJS support has been removed. The package now ships only ESM
- **React 19.2+ required**: Minimum React version is now 19.2 (previously ^18.3 || ^19)
- **react-dom 19.2+ required**: `react-dom` is now a required peer dependency
- **Sanity Studio v5+ required**: Minimum Sanity version is now v5 (Sanity v3 and v4 are no longer supported)
- **Node.js 20.19+ required**: Minimum Node.js version is now 20.19 (previously >=14)
- **styled-components 6.1+ required**: `styled-components` is now a required peer dependency (required by `sanity-translations-tab`)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Sessions can be compared in the DevTools UI to diff bundle changes between build
| [`sanity-plugin-internationalized-array`](./plugins/sanity-plugin-internationalized-array) | Store localized fields in arrays to save attributes |
| [`sanity-plugin-latex-input`](./plugins/sanity-plugin-latex-input) | LaTeX input for Portable Text Editor |
| [`sanity-plugin-markdown`](./plugins/sanity-plugin-markdown) | Markdown editor input |
| [`sanity-plugin-studio-smartling`](./plugins/sanity-plugin-studio-smartling) | In-studio integration with Smartling for content translation |
| [`sanity-plugin-transifex`](./plugins/sanity-plugin-transifex) | In-studio integration with Transifex for content translation |
| [`sanity-naive-html-serializer`](./plugins/sanity-naive-html-serializer) | Serialize Sanity documents and rich text fields to HTML |
| [`sanity-plugin-utils`](./plugins/sanity-plugin-utils) | Handy hooks and components for Sanity Studio plugins |
Expand Down
1 change: 1 addition & 0 deletions dev/test-studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"sanity-plugin-internationalized-array": "workspace:*",
"sanity-plugin-latex-input": "workspace:*",
"sanity-plugin-markdown": "workspace:*",
"sanity-plugin-studio-smartling": "workspace:*",
"sanity-plugin-transifex": "workspace:*",
"sanity-plugin-utils": "workspace:*",
"sanity-plugin-workflow": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions dev/test-studio/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {richDateInputExample} from '#rich-date-input'
import {sanityNaiveHtmlSerializerExample} from '#sanity-naive-html-serializer'
import {scriptRunnerTool} from '#script-runner'
import {sfccExample} from '#sfcc'
import {smartlingExample} from '#smartling'
import {studioSecretsExample} from '#studio-secrets'
import {transifexExample} from '#transifex'
import {translationsTabExample} from '#translations-tab'
Expand Down Expand Up @@ -65,6 +66,7 @@ export default defineConfig([
createWorkspace({name: 'utils-example', title: 'Utils Example', plugins: [utilsExample()]}),
createWorkspace({name: 'iframe-pane-example', plugins: [iframePaneExample()]}),
createWorkspace({name: 'transifex-example', title: 'Transifex', plugins: [transifexExample()]}),
createWorkspace({name: 'smartling-example', title: 'Smartling', plugins: [smartlingExample()]}),
createWorkspace({name: 'documents-pane-example', plugins: [documentsPaneExample()]}),
createWorkspace({
name: 'translations-tab-example',
Expand Down
72 changes: 72 additions & 0 deletions dev/test-studio/src/smartling/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {EarthGlobeIcon} from '@sanity/icons'
import {defineField, definePlugin, defineType} from 'sanity'
import {defaultFieldLevelConfig, TranslationsTab} from 'sanity-plugin-studio-smartling'
import type {DefaultDocumentNodeResolver} from 'sanity/structure'
import {structureTool} from 'sanity/structure'

const languages = [
{id: 'en', title: 'English', isDefault: true},
{id: 'es', title: 'Spanish'},
{id: 'fr', title: 'French'},
]

const localizedString = defineType({
name: 'smartlingLocalizedString',
type: 'object',
fieldsets: [
{
title: 'Translations',
name: 'translations',
options: {collapsible: true, collapsed: false},
},
],
fields: languages.map((lang) =>
defineField({
name: lang.id,
title: lang.title,
type: 'string',
fieldset: lang.isDefault ? undefined : 'translations',
}),
),
})

const smartlingTest = defineType({
type: 'document',
name: 'smartlingTest',
title: 'Smartling',
icon: EarthGlobeIcon,
fields: [
defineField({type: 'string', name: 'title', title: 'Title'}),
defineField({
type: 'smartlingLocalizedString',
name: 'greeting',
title: 'Greeting',
}),
defineField({
type: 'smartlingLocalizedString',
name: 'description',
title: 'Description',
}),
],
})

const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}) => {
if (schemaType === 'smartlingTest') {
return S.document().views([
S.view.form(),
S.view.component(TranslationsTab).title('Smartling').options(defaultFieldLevelConfig),
])
}

return S.document().views([S.view.form()])
}

export const smartlingExample = definePlugin(() => ({
name: 'smartling-example',
schema: {types: [localizedString, smartlingTest]},
plugins: [
structureTool({
defaultDocumentNode,
}),
],
}))
5 changes: 5 additions & 0 deletions knip.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@
],
},
// add new plugin workspaces here
"plugins/sanity-plugin-studio-smartling": {
"entry": ["package.config.ts"],
"project": ["src/**/*.{ts,tsx}"],
},

"plugins/sanity-plugin-documents-pane": {
"entry": ["package.config.ts"],
"project": ["src/**/*.{ts,tsx}"],
Expand Down
19 changes: 19 additions & 0 deletions plugins/sanity-plugin-studio-smartling/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# sanity-plugin-studio-smartling

## [4.3.3](https://github.com/sanity-io/sanity-plugin-studio-smartling/compare/v4.3.2...v4.3.3) (2026-01-07)

### Bug Fixes

- **deps:** Update dependency sanity-translations-tab to v5 ([#35](https://github.com/sanity-io/sanity-plugin-studio-smartling/issues/35)) ([a64c6d8](https://github.com/sanity-io/sanity-plugin-studio-smartling/commit/a64c6d883ccfecacb5ea5adc335b17008bd5db75))

## [4.3.2](https://github.com/sanity-io/sanity-plugin-studio-smartling/compare/v4.3.1...v4.3.2) (2025-12-29)

### Bug Fixes

- update package.json and package-lock.json to support Sanity v5 ([#34](https://github.com/sanity-io/sanity-plugin-studio-smartling/issues/34)) ([9a8eb09](https://github.com/sanity-io/sanity-plugin-studio-smartling/commit/9a8eb0978b27c4b7c848835645e5c8d99d3d07c3))

## [4.3.1](https://github.com/sanity-io/sanity-plugin-studio-smartling/compare/v4.3.0...v4.3.1) (2025-07-10)

### Bug Fixes

- **deps:** allow studio v4 peer dep ranges ([1e3de60](https://github.com/sanity-io/sanity-plugin-studio-smartling/commit/1e3de60b44d0867bedfb5b91b01c0ee8d6047798))
206 changes: 206 additions & 0 deletions plugins/sanity-plugin-studio-smartling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
## Installation

```sh
npm install sanity-plugin-studio-smartling
```

# Studio Plugin for Sanity & Smartling

![smartling gif](https://user-images.githubusercontent.com/3969996/125689321-bf37021f-ba55-4147-83eb-1745eb8acb1f.gif)

We're proud to be partnered with Smartling and their [official connector](https://help.smartling.com/hc/en-us/articles/1260803085050-Sanity-Connector-Overview-) makes it quick and easy to get your studio content into your Smartling project.

This is a separate plugin, and differs in that it provides editors a visual progress bar for ongoing translations and a way to import translations back into your content at either the document or field level. Feel free to try it out and see which solution works for you!

_Recent updates for v4:_ We've added support for the new document internationalization plugin pattern. Please read the [Document level translations](#document-level-translations) section for more information.

# Table of Contents

- [Quickstart](#quickstart)
- [Assumptions](#assumptions)
- [Studio experience](#studio-experience)
- [Overriding defaults](#overriding-defaults)
- [License](#license)
- [Develop and test](#develop-and-test)

## Quickstart

1. In your studio folder, run:

```sh
npm install sanity-plugin-studio-smartling
```

2. Because of Smartling CORS restrictions, you will need to set up a proxy endpoint to funnel requests to Smartling. We've provided a tiny Next.js app you can set up [here](https://github.com/sanity-io/example-sanity-smartling-proxy). If that's not useful, the important thing to pay attention to is that this endpoint handles requests with an `X-URL` header that contains the Smartling URL configured by the plugin, and can parse a data file to an HTML string and send it back to the adapter.

3. Create or use a Smartling project token.

[Please refer to the Smartling documentation on creating a token if you don't have one already.](https://help.smartling.com/hc/en-us/articles/115004187694-API-Tokens)

In your Studio folder, create a file called `populateSmartlingSecrets.js` with the following contents:

```javascript
// ./populateSmartlingSecrets.js
// Do not commit this file to your repository

import {getCliClient} from 'sanity/cli'

const client = getCliClient({apiVersion: '2023-02-15'})

client.createOrReplace({
// The `.` in this _id will ensure the document is private
// even in a public dataset!
_id: 'translationService.secrets',
_type: 'smartlingSettings',
//replace these with your values
organization: 'YOUR_SMARTLING_ORGANIZATION_HERE',
project: 'YOUR_SMARTLING_PROJECT_HERE',
secret: '{"userIdentifier":"xxxxxx","userSecret":"xxxx"}', //in this format from Smartling when you press the button "copy token" on creation
proxy: 'my-proxy-endpoint.com/api/proxy', //the endpoint you set up in step 2
})
```

On the command line, run the file:

```sh
npx sanity exec populateSmartlingSecrets.js --with-user-token
```

Verify that the document was created using the Vision Tool in the Studio and query `*[_id == 'translationService.secrets']`. Note: If you have multiple datasets, you'll have to do this across all of them.

If the document was found in your dataset(s), delete `populateSmartlingSecrets.js`.

If you have concerns about this being exposed to authenticated users of your studio, you can control access to this path with [role-based access control](https://www.sanity.io/docs/access-control).

4. Get the Smartling tab on your desired document type, using whatever pattern you like. You'll use the [desk structure](https://www.sanity.io/docs/structure-builder-introduction) for this. The options for translation will be nested under this desired document type's views. Here's an example:

```javascript
import {DefaultDocumentNodeResolver} from 'sanity/desk'
//...your other desk structure imports...
import {TranslationsTab, defaultDocumentLevelConfig} from 'sanity-plugin-studio-smartling'
//if you are using field-level translations, you can import the field-level config instead:
//import {TranslationsTab, defaultFieldLevelConfig} from 'sanity-plugin-studio-smartling'
//if you're not sure which, please look at the document-level and field-level sections below

export const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}) => {
if (schemaType === 'myTranslatableDocumentType') {
return S.document().views([
S.view.form(),
//...my other views -- for example, live preview, document pane, etc.,
S.view.component(TranslationsTab).title('Smartling').options(defaultDocumentLevelConfig)
//again, if you're using field-level translations, you can use the field-level config instead:
//S.view.component(TranslationsTab).title('Smartling').options(defaultFieldLevelConfig)
])
}
return S.document()
}
```

And that should do it! Go into your studio, click around, and check the document in Smartling (it should be under its Sanity `_id` by default, but you can override this). Once it's translated, check the import by clicking the `Import` button on your Smartling tab!

## Assumptions

To use the default config mentioned above, we assume that you are following the conventions we outline in [our documentation on localization](https://www.sanity.io/docs/localization).

### Field-level translations

If you are using field-level translation and the `defaultFieldLevelConfig` configuration, we assume any fields you want translated exist in the multi-locale object form we recommend.
For example, a non-localizable "title" field will be a flat string: `title: 'My title is here.'` For a field you want to include many languages for, your title may look like
`
{
title: {
en: 'My title is here.',
es_ES: 'Mi título está aquí.',
etc...
}
}
`
_Important_: Smartling's locale representation includes hyphens, like `fr-FR`. These aren't valid as Sanity field names, so ensure that on your fields you change the hyphens to underscores (like `fr_FR`).

### Document level translations

Since we often find users want to use the [Document internationalization plugin](https://www.sanity.io/plugins/document-internationalization) if they're using document-level translations, we assume that any documents you want in different languages will be present in a `translation.metadata` document.

_Important_: The above is true if you are using the Document Internationalization Plugin at version 2 or above. If you are using version 1 please use the `legacyDocumentLevelConfig` configuration exported from this plugin. This configuration assumes your translations follow the pattern `{id-of-base-language-document}__i18n_{locale}`

### Final note

It's okay if your data doesn't follow these patterns and you don't want to change them! You will simply have to override how the plugin gets and patches back information from your documents. Please see [Overriding defaults](#overriding-defaults).

## Studio experience

By adding the `TranslationsTab` to your desk structure, your users should now have an additional view on their document. The boxes at the top of the tab can be used to send translations off to Smartling, and once those jobs are started, they should see progress bars monitoring the progress of the jobs. They can import a partial or complete job back. They can also re-send a document, which should update the existing job.

## Overriding defaults

To personalize this configuration it's useful to know what arguments go into `TranslationsTab` as options (the `defaultConfigs` are just wrappers for these):

- `exportForTranslation`: a function that takes your document id and returns an object like:

```javascript
{
`name`: /*the field you want to use identify your doc in Smartling (by default this is `_id`) */
`content`: /* a serialized HTML string of all the fields in your document to be translated. */
}
```

- `importTranslation`: a function that takes in `id` (your document id), `localeId` (the locale of the imported language), and `document` (the translated HTML from Smartling). It will deserialize your document back into an object that can be patched into your Sanity data, and then executes that patch.
- `Adapter`: An interface with methods to send things over to Smartling. You likely don't want to override this!

There are several reasons to override these functions. Generally, developers will customize to ensure documents serialize and deserialize correctly. Since the serialization functions are used across all our translation plugins currently, you can find some frequently encountered scenarios at [their repository here](https://github.com/sanity-io/sanity-naive-html-serializer), along with code examples for customized configurations.

## Migrating to Sanity Studio v3

There is one major breaking change in this plugin's migration to Sanity Studio v3: the proxy was set in an environment variable, and now it should be part of the `secrets` document.

In v2, you would set the proxy in a `.env` file, like so:

```env
SANITY_STUDIO_SMARTLING_PROXY=https://my-proxy-endpoint.com/api/proxy
```

In v3, you should set the proxy in the `secrets` document. If you have an existing secrets document, you can patch it like so:

```javascript
// ./patchSmartlingSecrets.js
// Do not commit this file to your repository

import {getCliClient} from 'sanity/cli'

const client = getCliClient({apiVersion: '2023-02-15'})

client.patch('translationService.secrets').set({proxy: 'https://my-proxy.com/api/proxy'}).commit()
```

and run the script with `sanity exec patchSmartlingSecrets.js --with-user-token`.

Alternatively, you can re-run the `populateSmartlingSecrets` script in [Quickstart](#quickstart) to create a new secrets document with the proxy set.

We apologize for the inconvenience. Because of the new embeddability of the studio, developers may find that their v3 Studio is built and deployed in different ways, with access to different environments. Keeping this setting in `secrets` allows developers to set it in a way that works for their deployment and reduce complexity. You can find more information on our guidance around environment variables [here](https://github.com/sanity-io/sanity/releases/tag/v3.5.0).

Otherwise, you should not have to do anything to migrate to Sanity Studio v3. If you are using the default configs, you should be able to upgrade without any changes. If you are using custom serialization, you may need to update how `BaseDocumentSerializer` receives your schema.

These are outlined in the serializer README [here](https://github.com/sanity-io/sanity-naive-html-serializer#v2-to-v3-changes).

The final change from the v2 to v3 version of the plugin is in how progress in a translation job is calculated. The plugin will now count progress as the percentage of all strings that have reached the final stage of a Smartling workflow.

## License

[MIT](LICENSE) © Sanity.io

## Develop & test

This plugin is in early stages. We plan on improving some of the user-facing chrome, sorting out some quiet bugs, figuring out where things don't fail elegantly, etc. Please be a part of our development process!

This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit)
with default configuration for build & watch scripts.

See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio)
on how to run this plugin with hotreload in the studio.

### Release new version

Run ["CI & Release" workflow](https://github.com/sanity-io/sanity-plugin-transifex/actions/workflows/main.yml).
Make sure to select the main branch and check "Release new version".

Semantic release will only release on configured branches, so it is safe to run release on any branch.
Loading
Loading