Plugin poskytuje přístup k přeloženému obsahu Phrase přímo ze studia Sanity.
Podporovány jsou pouze překlady na úrovni dokumentu. Překlady na úrovni pole nejsou podporovány.
Funkce:
-
Náhledy v reálném čase
Překlady jsou synchronizovány, aby lingvisté a překladatelé mohli vidět změny v náhledech v reálném čase.
-
Inteligentní opětovné překlady
Plugin porovnává, jaký obsah se od posledního překladu změnil, a pouze tyto změny odesílá do Phrase.
-
Automatické překlady referencí
Při vydávání překladů mohou editoři zvolit také překlad dokumentů, na které se odkazuje, a plugin je automaticky propojí podle cílového jazyka.
-
Flexibilní schémata
Bez ohledu na strukturu se plugin přizpůsobí a zajistí, že konečný přeložený obsah odpovídá schématům Sanity.
-
Pracovní postupy Phrase
Pracovní postupy překladu v Phrase zůstávají stejné; není nutné opětovné školení nebo přeconfigurování operací.
Instalace se provádí v příkazovém řádku.
Předpokládá se, že generátor webových stránek a Sanity Studio jsou již nakonfigurovány. Pokud ne, použijte jednu z starter šablon poskytnutých Sanity.
Instalace
Přejděte do projektu obsahujícího instanci Sanity Studio a nainstalujte plugin:
npm install sanity-plugin-phrase # nebo pnpm, yarn, bun
Proměnné prostředí
Před konfigurací pluginu musí být nastaveny následující proměnné prostředí. Vytvořte soubor `.env` (nebo `.env.local` pro Next.js) v kořenovém adresáři projektu.
Příklady níže jsou uvedeny pro NextJS. Pro jiné frameworky se odkažte na jejich specifickou dokumentaci a vezměte na vědomí, že prefix `NEXT_PUBLIC_` může být nutné odstranit pro veřejné proměnné.
Důležité
Proměnné na straně serveru (SANITY_WRITE_TOKEN, PHRASE_USER_NAME, PHRASE_PASSWORD) by nikdy neměly být vystaveny klientovi. V Next.js jsou vystaveny pouze proměnné s prefixem NEXT_PUBLIC_ prohlížeči.
# Základní URL vaší stránky (používá se pro náhledové odkazy)
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
# URL, kde se nachází backendový handler pluginu
NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT="http://localhost:3000/api/phrase"
# Region datacentra Phrase ('eu' nebo 'us')
NEXT_PUBLIC_PHRASE_REGION="eu"
# Konfigurace projektu Sanity
NEXT_PUBLIC_SANITY_PROJECT_ID="vaše-id-projektu"
NEXT_PUBLIC_SANITY_DATASET="production"
# API token Sanity s oprávněními pro zápis (pouze na straně serveru)
SANITY_WRITE_TOKEN=""
# Přihlašovací údaje Phrase (pouze na straně serveru)
# Poznámka: API Phrase očekává pouze část uživatelského jména, NE celou e-mailovou adresu
PHRASE_USER_NAME="phraseUsername"
HESLO_FRAZE="tajneHeslo"
Konfigurace pluginu
Plugin je přidán do sanity.config.ts s požadovanými konfiguračními možnostmi:
// sanity.config.ts
import { defineConfig } from 'sanity'
import {
phrasePlugin,
definePhraseOptions,
documentInternationalizationAdapter,
} from 'sanity-plugin-phrase'
const PHRASE_CONFIG = definePhraseOptions({
// Povinné: i18n adaptér pro mezinárodní dokumentaci
i18nAdapter: documentInternationalizationAdapter(),
// Required: Typy dokumentů, které mohou být přeloženy
translatableTypes: ['page', 'post', 'article'],
// Required: Zdrojový jazyk (primární jazyk)
// To musí odpovídat jazyku definovanému ve vaší šabloně projektu Phrase
sourceLang: 'en',
// Required: Cílové jazyky, do kterých mohou uživatelé překládat
// Použijte stejné kódy jako vaše dokumenty Sanity
// Tento seznam musí odpovídat jazykům definovaným ve vaší šabloně projektu Phrase
podporované cílové jazyky: ['es', 'fr', 'de', 'pt'],
// Required: URL adresa vašeho backend API koncového bodu
apiEndpoint: process.env.NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT!,
// Required: Region datacentra Phrase ('eu' nebo 'us')
phraseRegion: process.env.NEXT_PUBLIC_PHRASE_REGION jako 'eu' | 'us',
// Required: Šablony projektů Phrase dostupné editorům
phraseTemplates: [
{
templateUid: 'YOUR_TEMPLATE_UID_HERE',
štítek: 'Výchozí šablona překladu',
},
],
// Required: Generovat náhledové URL pro lingvisty
getDocumentPreview: (doc, sanityClient) => {
const publishedId = doc._id.replace('drafts.', '')
return `${process.env.NEXT_PUBLIC_BASE_URL}/api/draft?id=${publishedId}`
},
// Volitelné nastavení
// Maximální hloubka pro překlad odkazovaných dokumentů (výchozí: 3)
maxReferencesDepth: 3,
// Allow translation of draft documents (default: false)
translateDrafts: false,
// Vlastní transformátory dat pro speciální typy obsahu
dataTransformers: [],
// Konfigurace protokolování pro ladění
logger: {
minimumLogLevel: 'info', // 'debug' | 'info' | 'warning' | 'error' | 'fatal'
},
// Skrýt Phrase hlavní panel na základě rolí uživatelů
isPhraseDashboardHidden: (context) =>
!(context.currentUser.roles || []).some((r) => r.name === 'admin'),
})
export default defineConfig({
// ... vaše stávající konfigurace
plugins: [
phrasePlugin(PHRASE_CONFIG),
// ... další pluginy
],
})// sanity.config.(js|ts)
import {
phrasePlugin,
documentInternationalizationAdapter,
} from 'sanity-plugin-phrase'
const PHRASE_CONFIG = definePhraseOptions({
/**
* I18n adaptér, který se má použít pro tento plugin.
* Bude zodpovědný za načítání a úpravu dokumentů pro každý cílový jazyk.
*
* Viz níže pro více informací o adaptérech.
*/
i18nAdapter: documentInternationalizationAdapter(),
/**
* Typy schémat Sanity, které může plugin překládat
*/
translatableTypes: ['page', 'post', 'course', 'lesson', 'definition'],
/**
* Kód jazyka všech jazyků, do kterých mohou uživatelé překládat.
* Měl by být stejný jako ten, který je uložen ve vašich dokumentech Sanity a používán vaším front-endem. Plugin to automaticky přeloží do formátu Phrase.
*/
supportedTargetLangs: ['cz', 'es', 'pt', 'fr', 'de', 'it', 'nl', 'pl', 'ru'],
/**
* Kód jazyka zdrojového jazyka, který bude přeložen.
* Měl by být stejný jako ten, který je uložen ve vašich dokumentech Sanity a používán vaším front-endem. Plugin to automaticky přeloží do formátu Phrase.
*/
sourceLang: 'en',
/**
* Jak je definováno v nastavení vašeho účtu Phrase
* Buď `eu` nebo `us`
*/
phraseRegion: 'us|eu',
/**
* URL vašeho nakonfigurovaného plugin backend API.
*
* **Poznámka:** postupujte podle kroků pro nastavení koncového bodu, uvedených níže
*/
apiEndpoint: 'https://my-site.com/api/phrase',
/**
* Používá se k přesměrování lingvistů z hlavního panelu Phrase na náhled jejich překladů na front-endu.
*/
getDocumentPreview: async (doc, sanityClient) => {
const publishedId = doc._id.replace('drafts.', '')
return `${process.env.NEXT_PUBLIC_FRONT_END_URL}/api/draft?publishedId=${publishedId}`
},
/**
* Šablony projektů Phrase, které mohou vaši editoři použít při žádosti o překlady.
*
* **Poznámka:** postupujte podle kroků pro nastavení šablon, uvedených níže
*/
phraseTemplates: [
{
templateUid: '1jYg0Pc1d8kAHUyM0tgdmt',
label: '[Sanity.io] Výchozí šablona',
},
],
/**
* @volitelné
* V případě, že chcete zobrazit nebo skrýt hlavní panel Phrase podle oprávnění uživatelů.
*
* Přijímá kontext s aktuálním uživatelským účtem a dokumentem a musí vrátit boolean.
*/
isPhraseDashboardHidden: (context) =>
!(context.currentUser.roles || []).some((r) => r.name === 'admin'),
})
export default defineConfig({
// ...
plugins: [
// ...
phrasePlugin(PHRASE_CONFIG),
],
})
Vložení schématu
Abychom pluginu řekli, které typy dokumentů mohou být přeloženy, předáme pole typů dokumentů funkci injectPhraseIntoSchema v souboru sanity.config.ts:
// sanity.config.ts
import { injectPhraseIntoSchema } from 'sanity-plugin-phrase'
// List of translatable schema types. Obvykle exportováno z indexového souboru
// kdekoli máte umístěno své schéma Sanity
const TRANSLATABLE_SCHEMAS = ['page', 'post', 'course', 'lesson', 'definition']
export default defineConfig({
schema: {
typy: injectPhraseIntoSchema(TRANSLATABLE_SCHEMAS, PHRASE_CONFIG),
šablony: (prev) =>
prev.filter((template) => !TRANSLATABLE_SCHEMAS.includes(template.id)),
},
plugins: [
// ...
phrasePlugin({
// Vaše možnosti konfigurace zde
}),
],
})
Vyloučení PTD z dokumentových seznamů
PTD (Dokumenty pro překlad frází) jsou dočasné dokumenty, které by se neměly objevovat v normálních dokumentových seznamech uvnitř Sanity Studio. Konstanta NOT_PTD poskytuje GROQ filtr pro tento účel:
// sanity.config.ts
import { NOT_PTD } from 'sanity-plugin-phrase/utils'
export default defineConfig({
// ... další konfigurace
plugins: [
structureTool({
struktura: (S) =>
S.list()
.title('Content')
.items([
S.listItem()
.title('Posts')
.schemaType('post')
.child(
S.documentList()
.title('Posts')
.filter(`_type == "post" && ${NOT_PTD}`),
),
// ... další položky
]),
}),
],
})
Skrýt překladatelské menu pro PTD
Při používání pluginu pro mezinárodní dokumentaci by mělo být překladatelské menu skryto pro PTD. Nástroj isPtdId identifikuje PTD dokumenty:
import { isPtdId } from 'sanity-plugin-phrase/utils'
import { DocumentInternationalizationMenu } from '@sanity/document-internationalization'
// Použijte stejné pole jako translatableTypes z vašeho PHRASE_CONFIG
const TRANSLATABLE_TYPES = ['stránka', 'příspěvek', 'článek']
export default defineConfig({
dokument: {
unstable_languageFilter: (prev, ctx) => {
const { schemaType, documentId } = ctx
// Zobrazit překladatelské menu pouze pro skutečné dokumenty, ne pro PTD
return TRANSLATABLE_TYPES.includes(schemaType) &&
documentId &&
!isPtdId(documentId)
? [...prev, DocumentInternationalizationMenu]
: prev
},
},
})
Plugin nemá žádnou konfiguraci v uživatelském rozhraní fráze, ale fráze musí být nakonfigurována pro odesílání webhookových oznámení na koncový bod API na pozadí. To umožňuje aktualizace v reálném čase, jak postupují překlady.
Vytvořit webhook
Vytvořte webhook s těmito nastaveními:
-
URL
URL koncového bodu API pluginu, jak je nakonfigurováno v možnosti .
-
Události:
-
Zakázky
-
Zakázka odstraněna
-
Job assigned
-
Termín dodání zakázky změněn
-
Cíl zakázky aktualizován
-
-
Projekty
-
Projekt odstraněn
-
Termín dodání projektu změněn
-
-
Jiné
-
Předpřeklad dokončen
-
-
To zajišťuje, že plugin je informován o jakýchkoli změnách v projektech fráze a může udržovat data Sanity synchronizovaná.
Nastavení šablony projektu
Nakonfigurujte frázi šablonu projektu s vlastnostmi požadovanými pro pracovní postupy a požadavky týmu. Jedna nebo více šablon může být nabídnuta k výběru při objednávání nového překladu. Šablony projektů fráze musí mít specifická nastavení importu JSON, aby plugin fungoval správně. Tato nastavení řídí, které pole jsou odesílána překladatelům a která jsou uchovávána jako metadata.
Import JSON souboru
Použijte regex k vyloučení specifických klíčů:
(^|.*\/) (_createdAt|_id|_rev|_type|_updatedAt|_ref|_key|_sanityRev|_sanityContext|_strengthenOnPublish|phraseMetadata|_spanMeta|_blockMeta|_diff|marks|VAŠE_VYLOUČENÉ_KLÍČE_TADY| (_createdAt|_id|_rev|_type|_updatedAt|_ref|_key|_sanityRev|_sanityContext|_strengthenOnPublish|phraseMetadata|_spanMeta|_blockMeta|_diff|marks|VAŠE_VYLOUČENÉ_KLÍČE_TADY) /.*)
Tento výraz zahrnuje duplicitní klíče záměrně, aby se zajistilo, že budou ignorovány parserem RegEx fráze. Zajistěte, aby byly správně duplikovány.
-
Vyloučit lokalizačně specifická data, jako je jazyk daného dokumentu, pokud používáte
@sanity/document-internationalization. -
Zahrnout jakékoli klíče specifické pro projekt, které nevyžadují překlad, jako je slug pro obsah používající stejnou cestu ve všech jazycích. Nahradit
YOUR_IGNORED_KEYS_HEREpipe-separovaným seznamem klíčů k ignorování. -
Kontextová poznámka:
/_sanityContext
Zdrojový jazyk
V současnosti tento plugin funguje na základě předpokladu, že má jediný zdrojový jazyk. Šablona projektu musí mít stejný zdroj jako ten, který je nakonfigurován v pluginu sourceLanguage.
Cílové jazyky
Ujistěte se, že jazyky vybrané v Phrase jsou v souladu s tím, co je v konfiguraci pluginu.
Toto je koncový bod, který plugin používá k komunikaci se Sanity Studio. Používá se k autentizaci k API Phrase, přijímání webhooků a uživatelských požadavků ze studia Sanity.
Vytvořte vlastní API koncový bod v projektu Sanity pro zpracování těchto požadavků. Jedním z nejjednodušších způsobů, jak to udělat, je použít serverless funkce prostřednictvím front-endových rámců jako NextJS, Remix, SvelteKit nebo Nuxt.
Přístup k nakonfigurování obsluhy s vzorem požadavek-odpověď prostřednictvím import {createRequestHandler} z backend nebo použijte interní obsluhu přímo prostřednictvím import {createInternalHandler} z . Ujistěte se, že požadavky CORS jsou správně zpracovány, studio a koncový bod mají různé původy.
Adresář aplikace NextJS v současnosti není podporován, protože nesprávně analyzuje backendovou obsluhu jako komponentu React klienta.
Tento příklad demonstruje vytvoření obsluhy trasy na nakonfigurované apiEndpoint cestě na /api/phrase pomocí Next.js Pages Router:
// app/api/phrase/route.ts
// Podpora API trasy Next.js: https://nextjs.org/docs/api-routes/introduction
importovat typ { NextApiRequest, NextApiResponse } z 'next'
importovat { PHRASE_CONFIG } z 'phraseConfig'
importovat { createInternalHandler } z 'sanity-plugin-phrase/backend'
importovat { writeToken } z '~/lib/sanity.api'
importovat { klient } z '~/lib/sanity.client'
stáhnout const maxDuration = 60
stáhnout const dynamic = 'force-dynamic'
const phraseHandler = createInternalHandler({
phraseCredentials: {
userName: process.env.PHRASE_USER_NAME || '',
password: process.env.PHRASE_PASSWORD || '',
},
sanityClient: klient.withConfig({ token: writeToken }),
pluginOptions: PHRASE_CONFIG,
})
stáhnout 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: 'Metoda není povolena' })
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
importovat typ { NextApiRequest, NextApiResponse } z 'next'
importovat { PHRASE_CONFIG } z 'phraseConfig'
importovat { createInternalHandler } z 'sanity-plugin-phrase/backend'
importovat { writeToken } z '~/lib/sanity.api'
importovat { klient } z '~/lib/sanity.client'
const phraseHandler = createInternalHandler({
phraseCredentials: {
userName: process.env.PHRASE_USER_NAME || '',
password: process.env.PHRASE_PASSWORD || '',
},
sanityClient: klient.withConfig({ token: writeToken }),
pluginOptions: PHRASE_CONFIG,
})
stáhnout 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: 'Metoda není povolena' })
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)
}
i18n adaptéry
Sanity nemá předepsaný přístup k internacionalizaci a existuje mnoho způsobů, jak ji implementovat. Tento plugin používá vzor adaptéru, aby umožnil konfiguraci na základě toho, jak je obsah strukturován a jak by měl být přeložen.
V současnosti je k dispozici pouze jeden adaptér, a to , který používá oficiální document-internationalization plugin (verze ^2.0.0). Podávejte problém, pokud je vyžadován konkrétní adaptér, nebo se odkažte na tento repository's package/src/adapters/document-internationalization.ts pro příklad, jak implementovat přizpůsobený.
Vlastní transformátory dat
Pokud je potřeba transformovat data před jejich odesláním do Phrase, použijte možnost . To je užitečné, pokud měníte strukturu dat, nebo pokud vylučujete určité pole z překladu.
Každý datový transformátor musí kódovat data před jejich odesláním do Phrase; a dekódovat je při jejich přijímání zpět, aby je transformoval před uložením do Sanity. Více transformátorů může být navrstveno a spuštěno sekvenčně.
Plugin nenabízí žádný způsob, jak testovat transformátory izolovaně, takže vývoj může být složitý. Uložte skutečné cílové dokumenty z datasetu Sanity do .JSON a použijte je jako testovací data pro každou funkci kódování/dekódování.
Příklad úpravy JSON-kódovaných VTT souborů na HTML, aby Phrase lépe segmentoval obsah titulků:
import { DataTransformer } from 'sanity-plugin-phrase'
const vttJsonTransformer: DataTransformer = {
kódovat: {
pole(arr) {
// Zkontrolujte, zda pole obsahuje VTT uzly titulků
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 // Vraťte undefined, abyste přeskočili transformaci
},
},
dekódovat: {
objekt(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 = {
kódovat: {
pole(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
},
},
dekódovat: {
objekt(obj) {
if (!!obj && '_type' in obj && obj._type === 'encodedSubtitles') {
return decodeSubtitles(obj as EncodedSubtitles)
}
return undefined
},
},
}
// Přeskočení implementace
// Odkaz na /demo-nextjs/src/utils/vttJsonTransformer.ts pro celý zdrojový kód
declare function decodeSubtitles(
encoded: EncodedSubtitles,
): StoredSubtitleNode[]
declare function encodeSubtitles(nodes: StoredSubtitleNode[]): EncodedSubtitles
Omezení přístupu editorů
Kteří editoři mohou mít přístup k hlavnímu panelu Phrase, lze omezit implementací možnosti i. Tato funkce je ekvivalentní té, která je předána vlastnosti hidden pole v Sanity. Přijímá kontext s aktuálním uživatelem a dokumentem a musí vrátit boolean.
Příklad omezení přístupu k hlavnímu panelu Phrase pro uživatele s rolí admin:
const PHRASE_CONFIG = definePhraseOptions({
// ...
isPhraseDashboardHidden: (context) => {
const isAdmin = (context.currentUser.roles || []).some(
(r) => r.name === 'správce',
)
// Skrýt, pokud nejste správce
return !isAdmin
},
})