O plug-in fornece acesso ao conteúdo traduzido do Phrase dentro do estúdio Sanity.
Apenas traduções em nível de documento são suportadas. Traduções em nível de campo não são suportadas.
Recursos:
-
Prévias em tempo real
As traduções são mantidas em sincronia para permitir que linguistas e tradutores vejam as alterações de prévia em tempo real.
-
Re-traduções inteligentes
O plug-in compara o que mudou desde a última tradução e envia apenas essas alterações para o Phrase.
-
Tradução automática de referências
Ao emitir traduções, os editores podem optar por traduzir também documentos referenciados pelo atual e o plug-in os vinculará automaticamente pelo idioma de destino.
-
Esquemas flexíveis
Não importa a estrutura, o plug-in se adapta a ela e garante que o conteúdo traduzido final esteja de acordo com os esquemas do Sanity.
-
Fluxos de trabalho do Phrase
Os fluxos de trabalho de tradução no Phrase permanecem os mesmos; não é necessário re-treinar ou reconfigurar operações.
A instalação é realizada na linha de comando.
Assume-se que um gerador de site e o Sanity Studio já estão configurados. Se não, use um dos modelos starter fornecidos pelo Sanity.
Instalação
Navegue até o projeto contendo a instância do Sanity Studio e instale o plug-in:
npm install sanity-plugin-phrase # ou pnpm, yarn, bun
Variáveis de ambiente
Antes de configurar o plug-in, as seguintes variáveis de ambiente devem ser configuradas. Crie um arquivo `.env` (ou `.env.local` para Next.js) na raiz do projeto.
Os exemplos abaixo são dados para NextJS. Para outros frameworks, consulte a documentação específica deles e observe que o prefixo `NEXT_PUBLIC_` pode precisar ser removido para variáveis públicas.
Importante
Variáveis do lado do servidor (SANITY_WRITE_TOKEN, PHRASE_USER_NAME, PHRASE_PASSWORD) nunca devem ser expostas ao cliente. No Next.js, apenas variáveis com o prefixo NEXT_PUBLIC_ são expostas ao navegador.
# URL base do seu site (usada para links de prévia)
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
# URL onde o manipulador de backend do plug-in estará localizado
NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT="http://localhost:3000/api/phrase"
# Região do datacenter do Phrase ('eu' ou 'us')
NEXT_PUBLIC_PHRASE_REGION="eu"
# Configuração do projeto Sanity
NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
NEXT_PUBLIC_SANITY_DATASET="production"
# Token da API do Sanity com permissões de escrita (apenas do lado do servidor)
SANITY_WRITE_TOKEN=""
# Credenciais do Phrase (apenas do lado do servidor)
# Nota: A API Phrase espera apenas a parte do nome de usuário, NÃO o endereço de e-mail completo
PHRASE_USER_NAME="phraseUsername"
PHRASE_PASSWORD="secretPassword"
Configuração do plug-in
O plug-in é adicionado a sanity.config.ts com as opções de configuração necessárias:
// sanity.config.ts
import { defineConfig } from 'sanity'
importar {
phrasePlugin,
definePhraseOptions,
documentInternationalizationAdapter,
} de 'sanity-plugin-phrase'
const CONFIGURAÇÃO_PHRASE = definirOpçõesPhrase({
// Obrigatório: adaptador i18n para internacionalização de documentos
i18nAdapter: documentInternationalizationAdapter(),
// Obrigatório: Tipos de documentos que podem ser traduzidos
translatableTypes: ['page', 'post', 'article'],
// Obrigatório: Idioma de origem (idioma principal)
// Isso deve corresponder ao idioma definido no seu modelo de projeto Phrase
sourceLang: 'en',
// Obrigatório: Idiomas de destino que os usuários podem traduzir
// Use os mesmos códigos que seus documentos Sanity
// Esta lista deve corresponder aos idiomas definidos no seu modelo de projeto Phrase
supportedTargetLangs: ['es', 'fr', 'de', 'pt'],
// Obrigatório: A URL do seu endpoint da API de backend
apiEndpoint: process.env.NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT!
// Obrigatório: Região do datacenter do Phrase ('eu' ou 'us')
phraseRegion: process.env.NEXT_PUBLIC_PHRASE_REGION as 'eu' | 'us',
// Obrigatório: Modelos de projeto do Phrase disponíveis para editores
phraseTemplates: [
{
templateUid: 'YOUR_TEMPLATE_UID_HERE',
label: 'Modelo de Tradução Padrão',
},
],
// Obrigatório: Gerar URLs de prévia para linguistas
getDocumentPreview: (doc, sanityClient) => {
const publishedId = doc._id.replace('drafts.', '')
return `${process.env.NEXT_PUBLIC_BASE_URL}/api/draft?id=${publishedId}`
},
// Configurações opcionais
// Profundidade máxima para traduzir documentos referenciados (padrão: 3)
maxReferencesDepth: 3,
// Permitir tradução de documentos em rascunho (padrão: false)
translateDrafts: false,
// Transformadores de dados personalizados para tipos de conteúdo especiais
dataTransformers: [],
// Configuração de registro para depuração
logger: {
minimumLogLevel: 'info', // 'debug' | 'info' | 'warning' | 'error' | 'fatal'
},
// Ocultar Phrase painel de controle com base nos papéis do usuário
isPhraseDashboardHidden: (context) =>
!(context.currentUser.roles || []).some((r) => r.name === 'admin'),
})
export default defineConfig({
// ... sua configuração existente
plugins: [
phrasePlugin(PHRASE_CONFIG),
// ... outros plug-ins
],
})// sanity.config.(js|ts)
importar {
phrasePlugin,
documentInternationalizationAdapter,
} de 'sanity-plugin-phrase'
const CONFIGURAÇÃO_PHRASE = definirOpçõesPhrase({
/**
* O adaptador i18n a ser usado para este plug-in.
* Ele será responsável por buscar e modificar documentos para cada idioma-alvo.
*
* Veja abaixo para mais informações sobre adaptadores.
*/
i18nAdapter: documentInternationalizationAdapter(),
/**
* Tipos de esquema do Sanity que o plug-in pode traduzir
*/
translatableTypes: ['page', 'post', 'course', 'lesson', 'definition'],
/**
* Código do idioma de todos os idiomas que os usuários podem traduzir.
* Deve ser o mesmo que o armazenado em seus documentos do Sanity e usado pelo seu front-end. O plug-in irá automaticamente traduzi-lo para o formato do Phrase.
*/
supportedTargetLangs: ['cz', 'es', 'pt', 'fr', 'de', 'it', 'nl', 'pl', 'ru'],
/**
* Código do idioma da língua de origem que será traduzida.
* Deve ser o mesmo que o armazenado em seus documentos do Sanity e usado pelo seu front-end. O plug-in irá automaticamente traduzi-lo para o formato do Phrase.
*/
sourceLang: 'en',
/**
* Conforme definido nas configurações da sua conta do Phrase
* Pode ser `eu` ou `us`
*/
phraseRegion: 'us|eu',
/**
* A URL para a sua API de backend do plug-in configurado.
*
* **Nota:** siga os passos para configurar o endpoint, descritos abaixo
*/
apiEndpoint: 'https://my-site.com/api/phrase',
/**
* Usado para redirecionar linguistas do painel de controle do Phrase para a prévia front-end de suas traduções.
*/
getDocumentPreview: async (doc, sanityClient) => {
const publishedId = doc._id.replace('drafts.', '')
return `${process.env.NEXT_PUBLIC_FRONT_END_URL}/api/draft?publishedId=${publishedId}`
},
/**
* Modelos de projeto do Phrase que seus editores podem usar ao solicitar traduções.
*
* **Nota:** siga os passos para configurar os modelos, descritos abaixo
*/
phraseTemplates: [
{
templateUid: '1jYg0Pc1d8kAHUyM0tgdmt',
label: '[Sanity.io] Modelo padrão',
},
],
/**
* @opcional
* Caso você queira mostrar ou ocultar o painel de controle do Phrase de acordo com os privilégios do usuário.
*
* Recebe um contexto com o usuário atual e o documento e deve retornar um booleano.
*/
isPhraseDashboardHidden: (context) =>
!(context.currentUser.roles || []).some((r) => r.name === 'admin'),
})
export default defineConfig({
// ...
plugins: [
// ...
phrasePlugin(PHRASE_CONFIG),
],
})
Injeção de esquema
Para informar ao plug-in quais tipos de documentos podem ser traduzidos, passe um array de tipos de documentos para a função injectPhraseIntoSchema no arquivo sanity.config.ts:
// sanity.config.ts
import { injectPhraseIntoSchema } from 'sanity-plugin-phrase'
// Lista de tipos de esquema traduzíveis. Geralmente exportado de um arquivo índice
// onde quer que seu esquema Sanity esteja localizado
const ESQUEMAS_TRADUZÍVEIS = ['página', 'postagem', 'curso', 'aula', 'definição']
export default defineConfig({
schema: {
types: injectPhraseIntoSchema(TRANSLATABLE_SCHEMAS, PHRASE_CONFIG),
templates: (prev) =>
prev.filter((template) => !TRANSLATABLE_SCHEMAS.includes(template.id)),
},
plugins: [
// ...
phrasePlugin({
// Suas opções de configuração aqui
}),
],
})
Excluindo PTDs das listas de documentos
PTDs (Documentos de Tradução de Frases) são documentos temporários que não devem aparecer nas listas normais de documentos dentro do Sanity Studio. A constante NÃO_PTD fornece um filtro GROQ para esse propósito:
// sanity.config.ts
importar { NÃO_PTD } de 'sanity-plugin-phrase/utils'
export default defineConfig({
// ... other config
plugins: [
structureTool({
structure: (S) =>
S.list()
.title('Content')
.items([
S.listItem()
.title('Posts')
.schemaType('post')
.child(
S.documentList()
.title('Posts')
.filter(`_type == "post" && ${NOT_PTD}`),
),
// ... outros itens
]),
}),
],
})
Ocultando o menu de tradução dos PTDs
Ao usar o plug-in de internacionalização de documentos, o menu de tradução deve ser ocultado dos PTDs. A utilidade éIdPtd identifica documentos PTD:
importar { éIdPtd } de 'sanity-plugin-phrase/utils'
importar { MenuInternacionalizaçãoDocumento } de '@sanity/document-internationalization'
// Use o mesmo array que translatableTypes de sua PHRASE_CONFIG
const TIPOS_TRADUZÍVEIS = ['página', 'postagem', 'artigo']
export default defineConfig({
documento: {
unstable_languageFilter: (prev, ctx) => {
const { schemaType, documentId } = ctx
// Mostre o menu de tradução apenas para documentos reais, não para PTDs
return TIPOS_TRADUZÍVEIS.includes(tipoDeEsquema) &&
documentId &&
!isPtdId(documentId)
? [...prev, DocumentInternationalizationMenu]
: prev
},
},
})
O plug-in não possui configuração na interface da Frase, mas a Frase deve ser configurada para enviar notificações de webhook para o endpoint da API de backend. Isso permite atualizações em tempo real à medida que as traduções progridem.
Criar um webhook
Criar um webhook com estas configurações:
-
URL
A URL para o endpoint da API do plug-in, conforme configurado na opção .
-
Eventos:
-
Trabalhos
-
Trabalho excluído
-
Trabalho atribuído
-
Data de entrega do trabalho alterada
-
Tradução do trabalho atualizada
-
-
Projetos
-
Projeto excluído
-
Data de entrega do projeto alterada
-
-
Outros
-
Pré-tradução concluída
-
-
Isso garante que o plug-in seja notificado sobre quaisquer alterações nos projetos Phrase e possa manter os dados do Sanity sincronizados.
Configurar modelo(s) de projeto(s)
Configurar modelo(s) de projeto do Phrase com as propriedades necessárias para fluxos de trabalho e requisitos da equipe. Um ou mais modelos podem ser oferecidos para escolha ao solicitar uma nova tradução. Os modelos de projeto do Phrase devem ter configurações específicas de importação JSON para que o plug-in funcione corretamente. Essas configurações controlam quais campos são enviados aos tradutores e quais são preservados como metadados.
Importação de arquivo JSON
Use regex para excluir chaves específicas:
(^|.*\/) (_createdAt|_id|_rev|_type|_updatedAt|_ref|_key|_sanityRev|_sanityContext|_strengthenOnPublish|phraseMetadata|_spanMeta|_blockMeta|_diff|marks|SUAS_CHAVES_IGNORADAS_AQUI| (_createdAt|_id|_rev|_type|_updatedAt|_ref|_key|_sanityRev|_sanityContext|_strengthenOnPublish|phraseMetadata|_spanMeta|_blockMeta|_diff|marks|SUAS_CHAVES_IGNORADAS_AQUI) /.*)
Essa expressão inclui chaves duplicadas de propósito para garantir que sejam ignoradas pelo analisador RegEx do Phrase. Certifique-se de que estão corretamente duplicadas.
-
Excluir dados específicos de localização, como o idioma de um determinado documento se usar
@sanity/document-internationalization. -
Incluir quaisquer chaves específicas do projeto que não requerem tradução, como um slug para conteúdo usando o mesmo caminho em todos os idiomas. Substituir
SUAS_CHAVES_IGNORADAS_AQUIpor uma lista de chaves separadas por pipe para ignorar. -
Context note:
/_sanityContext
Idioma do texto original
Atualmente, este plug-in opera sob a suposição de ter um único idioma de origem. O(s) modelo(s) do projeto devem ter a mesma origem que a configurada no sourceLanguage do plug-in.
Idiomas de destino
Certifique-se de que os idiomas escolhidos no Phrase estejam sincronizados com o que está na configuração do plug-in.
Este é o endpoint que o plug-in usa para se comunicar com o Sanity Studio. É usado para autenticar na API do Phrase, receber webhooks e solicitações de usuários do Sanity Studio.
Crie um endpoint de API personalizado no projeto Sanity para lidar com essas solicitações. Uma das maneiras mais fáceis de fazer isso é usar funções serverless através de frameworks front-end como NextJS, Remix, SvelteKit ou Nuxt.
Acesse configure o manipulador com um padrão de Requisição-Resposta via import {createRequestHandler} de backend ou use o manipulador interno diretamente via import {createInternalHandler} de . Certifique-se de que as solicitações CORS sejam tratadas corretamente, pois o estúdio e o endpoint têm origens diferentes.
O diretório de aplicativos do NextJS não é atualmente suportado, pois analisa incorretamente o manipulador de backend como um componente cliente React.
Este exemplo demonstra a criação de um Manipulador de Rota no caminho apiEndpoint configurado em /api/phrase usando o Next.js Pages Router:
// app/api/phrase/route.ts
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
import { PHRASE_CONFIG } from 'phraseConfig'
import { createInternalHandler } from 'sanity-plugin-phrase/backend'
import { writeToken } from '~/lib/sanity.api'
importar { cliente } de '~/lib/sanity.client'
exportar const maxDuration = 60
exportar const dynamic = 'force-dynamic'
const phraseHandler = createInternalHandler({
phraseCredentials: {
userName: process.env.PHRASE_USER_NAME || '',
password: process.env.PHRASE_PASSWORD || '',
},
sanityClient: client.withConfig({ token: writeToken }),
pluginOptions: PHRASE_CONFIG,
})
exportar default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', '*')
if (req.method?.toUpperCase() === 'OPTIONS') {
res.status(200).json({})
return
}
if (
!req.method ||
(req.method.toUpperCase() !== 'POST' && req.method.toUpperCase() !== 'GET')
) {
res.status(405).json({ error: 'Método não permitido' })
return
}
const phraseRes = await phraseHandler(
req.method.toUpperCase() === 'POST' ? req.body : req.query,
)
const resBody = await phraseRes.json().catch(() => {})
Array.from(phraseRes.headers.entries()).forEach((value: [string, any]) => {
res.setHeader(value[0], value[1])
})
res.status(phraseRes.status).json(resBody)
}// src/pages/api/phrase.ts
// Next.js API route: https://nextjs.org/docs/pages/building-your-application/routing/api-routes
import type { NextApiRequest, NextApiResponse } from 'next'
import { PHRASE_CONFIG } from 'phraseConfig'
import { createInternalHandler } from 'sanity-plugin-phrase/backend'
import { writeToken } from '~/lib/sanity.api'
importar { cliente } de '~/lib/sanity.client'
const phraseHandler = createInternalHandler({
phraseCredentials: {
userName: process.env.PHRASE_USER_NAME || '',
password: process.env.PHRASE_PASSWORD || '',
},
sanityClient: client.withConfig({ token: writeToken }),
pluginOptions: PHRASE_CONFIG,
})
exportar default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', '*')
if (req.method?.toUpperCase() === 'OPTIONS') {
res.status(200).json({})
return
}
if (
!req.method ||
(req.method.toUpperCase() !== 'POST' && req.method.toUpperCase() !== 'GET')
) {
res.status(405).json({ error: 'Método não permitido' })
return
}
const phraseRes = await phraseHandler(
req.method.toUpperCase() === 'POST' ? req.body : req.query,
)
const resBody = await phraseRes.json().catch(() => {})
Array.from(phraseRes.headers.entries()).forEach((value) => {
res.setHeader(value[0], value[1])
})
res.status(phraseRes.status).json(resBody)
}
adaptadores i18n
A Sanity não possui uma abordagem prescritiva para internacionalização, e existem muitas maneiras de implementá-la. Este plug-in utiliza um padrão de adaptador para permitir a configuração com base em como o conteúdo está estruturado e como deve ser traduzido.
Atualmente, o único adaptador disponível é , que é o utilizado pelo plug-in oficial de document-internationalization da Sanity (versão ^2.0.0). Registre um problema se um adaptador específico for necessário ou consulte este repositório's package/src/adapters/document-internationalization.ts para um exemplo de como implementar um personalizado.
Transformadores de dados personalizados
Se for necessário transformar dados antes de enviá-los para o Phrase, use a opção. Isso é útil se for necessário alterar a estrutura dos dados ou se for necessário excluir certos campos de serem traduzidos.
Cada transformador de dados precisa codificar os dados antes de enviá-los para o Phrase; e decodificá-los ao recebê-los de volta para transformá-los antes de salvar na Sanity. Vários transformadores podem ser empilhados e executados sequencialmente.
O plug-in não oferece uma maneira de testar transformadores isoladamente, então o desenvolvimento pode ser complexo. Salve documentos reais de destino do conjunto de dados da Sanity em .JSON e use-os como dados de teste para cada função de codificação/decodificação.
Exemplo de modificação de arquivos VTT codificados em JSON para HTML para que o Phrase possa segmentar melhor o conteúdo das legendas:
import { DataTransformer } from 'sanity-plugin-phrase'
const vttJsonTransformer: DataTransformer = {
encode: {
array(arr) {
// Verifique se o array contém nós de legenda VTT
if (
arr.every(
(item) =>
typeof item === 'object' &&
!!item &&
'_type' in item &&
typeof item._type === 'string' &&
item._type.startsWith('vtt.'),
)
) {
return encodeSubtitles(arr as StoredSubtitleNode[])
}
return undefined // Retornar indefinido para pular a transformação
},
},
decode: {
object(obj) {
if (!!obj && '_type' in obj && obj._type === 'encodedSubtitles') {
return decodeSubtitles(obj as EncodedSubtitles)
}
return undefined
},
},
}
export const PHRASE_CONFIG = definePhraseOptions({
// ...
dataTransformers: [vttJsonTransformer],
})export const PHRASE_CONFIG = definePhraseOptions({
// ...
dataTransformers: [vttJsonTransformer],
})
const vttJsonTransformer: DataTransformer = {
encode: {
array(arr) {
if (
arr.every(
(item) =>
typeof item === 'object' &&
!!item &&
'_type' in item &&
typeof item._type === 'string' &&
item._type.startsWith('vtt.'),
)
) {
return encodeSubtitles(arr as StoredSubtitleNode[])
}
return undefined
},
},
decode: {
object(obj) {
if (!!obj && '_type' in obj && obj._type === 'encodedSubtitles') {
return decodeSubtitles(obj as EncodedSubtitles)
}
return undefined
},
},
}
// Ignorando implementação
// Consulte /demo-nextjs/src/utils/vttJsonTransformer.ts para o código-fonte completo
declare function decodeSubtitles(
encoded: EncodedSubtitles,
): StoredSubtitleNode[]
declare function encodeSubtitles(nodes: StoredSubtitleNode[]): EncodedSubtitles
Limitando o acesso do editor
Quais editores podem acessar o painel de controle do Phrase podem ser limitados implementando a opção i. Esta função é equivalente àquela passada para a propriedade hidden de um campo no Sanity. Ela recebe um contexto com o usuário e documento atuais e deve retornar um booleano.
Exemplo de limitação de acesso ao painel de controle do Phrase para usuários com o papel de admin:
const CONFIGURAÇÃO_PHRASE = definirOpçõesPhrase({
// ...
isPhraseDashboardHidden: (context) => {
const isAdmin = (context.currentUser.roles || []).some(
(r) => r.name === 'admin',
)
// Ocultar se não for um admin
return !isAdmin
},
})