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 irá vinculá-los 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; re-treinamento ou reconfiguração de operações não é necessário.
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 que contém 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 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
importar { defineConfig } de 'sanity'
importar {
phrasePlugin,
definePhraseOptions,
documentInternationalizationAdapter,
} de 'sanity-plugin-phrase'
const PHRASE_CONFIG = definePhraseOptions({
// Obrigatório: adaptador i18n para internacionalização de documentos
i18nAdapter: documentInternationalizationAdapter(),
// Obrigatório: Tipos de documentos que podem ser traduzidos
translatableTypes: ['página', 'postagem', 'artigo'],
// Obrigatório: Idioma de origem (idioma principal)
// Isso deve corresponder ao idioma definido no modelo de projeto Phrase
sourceLang: 'pt-br',
// Obrigatório: Idiomas de destino que os usuários podem traduzir para
// 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: Sua URL de endpoint da API de backend
apiEndpoint: process.env.NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT!,
// Obrigatório: Região do datacenter Phrase ('eu' ou 'us')
phraseRegion: process.env.NEXT_PUBLIC_PHRASE_REGION como 'eu' | 'us',
// Obrigatório: Modelos de projeto Phrase disponíveis para editores
phraseTemplates: [
{
templateUid: 'YOUR_TEMPLATE_UID_HERE',
etiqueta: '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 de rascunho (padrão: falso)
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 painel de controle Phrase 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 PHRASE_CONFIG = definePhraseOptions({
/**
* 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 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 Sanity e usado pelo seu front-end. O plug-in irá traduzir automaticamente 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á traduzido.
* Deve ser o mesmo que o armazenado em seus documentos Sanity e usado pelo seu front-end. O plug-in irá traduzir automaticamente para o formato do Phrase.
*/
sourceLang: 'pt-br',
/**
* Conforme definido nas configurações da sua conta do Phrase
* Pode ser `eu` ou `us`
*/
phraseRegion: 'us|eu',
/**
* A URL para a API do 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 definir 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
importar { injectPhraseIntoSchema } de 'sanity-plugin-phrase'
// Lista de tipos de esquema traduzíveis. Geralmente exportado de um arquivo de índice
// onde quer que você tenha seu esquema Sanity localizado
const ESQUEMAS_TRADUZÍVEIS = ['página', 'postagem', 'curso', 'aula', 'definição']
export default defineConfig({
schema: {
types: injectPhraseIntoSchema(TRANSLATABLE_SCHEMAS, PHRASE_CONFIG),
modelos: (anterior) =>
prev.filter((template) => !TRANSLATABLE_SCHEMAS.includes(template.id)),
},
plugins: [
// ...
phrasePlugin({
// Suas opções de configuração aqui
}),
],
})
Excluindo PTDs de listas de documentos
PTDs (Documentos de Tradução de Frases) são documentos temporários que não devem aparecer em listas normais de documentos dentro do Sanity Studio. A constante NOT_PTD fornece um filtro GROQ para esse propósito:
// sanity.config.ts
importar { NOT_PTD } de 'sanity-plugin-phrase/utils'
export default defineConfig({
// ... outra configuração
plugins: [
structureTool({
estrutura: (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 de PTDs
Ao usar o plug-in de internacionalização de documentos, o menu de tradução deve ser ocultado de PTDs. A isPtdId utilidade identifica documentos PTD:
import { isPtdId } from 'sanity-plugin-phrase/utils'
import { DocumentInternationalizationMenu } from '@sanity/document-internationalization'
// Use o mesmo array que translatableTypes do seu PHRASE_CONFIG
const TRANSLATABLE_TYPES = ['page', 'post', 'article']
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 TRANSLATABLE_TYPES.includes(schemaType) &&
documentId &&
!isPtdId(documentId)
? [...prev, DocumentInternationalizationMenu]
: prev
},
},
})
O plug-in não tem configuração na interface da Phrase, mas a Phrase 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
Crie 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
-
Job assigned
-
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
Configure o modelo 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.
-
Exclua dados específicos de localização, como o idioma de um determinado documento se estiver usando
@sanity/document-internationalization. -
Inclua 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. Substitua
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. Os 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 estão 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 de front-end como NextJS, Remix, SvelteKit ou Nuxt.
Acesse configure o manipulador com um padrão de Solicitação-Resposta via import {createRequestHandler} from backend ou use o manipulador interno diretamente via import {createInternalHandler} from . Certifique-se de que as solicitações CORS sejam tratadas corretamente, 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 configurado apiEndpoint em /api/phrase usando o Next.js Pages Router:
// app/api/phrase/route.ts
// Suporte a rotas da API do Next.js: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
import { PHRASE_CONFIG } from 'phraseConfig'
importar { createInternalHandler } from 'sanity-plugin-phrase/backend'
importar { writeToken } from '~/lib/sanity.api'
importar { cliente } from '~/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
}
se (
!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
// Rota API do Next.js: https://nextjs.org/docs/pages/building-your-application/routing/api-routes
import type { NextApiRequest, NextApiResponse } from 'next'
import { PHRASE_CONFIG } from 'phraseConfig'
importar { createInternalHandler } from 'sanity-plugin-phrase/backend'
importar { writeToken } from '~/lib/sanity.api'
importar { cliente } from '~/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
}
se (
!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 usa um padrão de adaptador para permitir configuração com base em como o conteúdo é estruturado e como deve ser traduzido.
Atualmente, o único adaptador disponível é , que é o usado pelo plug-in oficial 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 mudar a estrutura dos dados, ou se 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 no Sanity. Múltiplos 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. Grave documentos-alvo reais do conjunto de dados 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 a Phrase possa segmentar melhor o conteúdo das legendas:
importar { DataTransformer } de 'sanity-plugin-phrase'
const vttJsonTransformer: DataTransformer = {
codificar: {
array(arr) {
// Verifique se o array contém nós de legenda VTT
se (
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
},
},
decodificar: {
object(obj) {
se (!!obj && '_type' in obj && obj._type === 'encodedSubtitles') {
return decodeSubtitles(obj as EncodedSubtitles)
}
retornar indefinido
},
},
}
exportar const PHRASE_CONFIG = definirOpçõesDeFrase({
// ...
dataTransformers: [vttJsonTransformer],
})exportar const PHRASE_CONFIG = definirOpçõesDeFrase({
// ...
dataTransformers: [vttJsonTransformer],
})
const vttJsonTransformer: DataTransformer = {
codificar: {
array(arr) {
se (
arr.every(
(item) =>
typeof item === 'object' &&
!!item &&
'_type' in item &&
typeof item._type === 'string' &&
item._type.startsWith('vtt.'),
)
) {
return encodeSubtitles(arr as StoredSubtitleNode[])
}
retornar indefinido
},
},
decodificar: {
object(obj) {
se (!!obj && '_type' in obj && obj._type === 'encodedSubtitles') {
return decodeSubtitles(obj as EncodedSubtitles)
}
retornar indefinido
},
},
}
// Ignorando implementação
// Consulte /demo-nextjs/src/utils/vttJsonTransformer.ts para o código-fonte completo
declara função decodificarLegendas(
codificado: EncodedSubtitles,
): StoredSubtitleNode[]
declara função codificarLegendas(nós: 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 admin:
const PHRASE_CONFIG = definePhraseOptions({
// ...
isPhraseDashboardHidden: (context) => {
const isAdmin = (context.currentUser.roles || []).some(
(r) => r.nome === 'admin',
)
// Ocultar se não for um admin
return !isAdmin
},
})