Tento plugin poskytuje přístup k přeloženému obsahu Phrase přímo ze Sanity studia.
Podporovány jsou pouze překlady na úrovni dokumentu. Překlady na úrovni polí nejsou podporovány.
Funkce:
-
Náhledy v reálném čase
Překlady jsou udržovány v synchronizaci, aby lingvisté a překladatelé viděli změny v náhledu v reálném čase.
-
Chytré opětovné překlady
Plugin porovnává, jaký obsah se od posledního překladu změnil, a do Phrase odesílá pouze tyto změny.
-
Automatický překlad referencí
Při zadávání překladů mohou editoři zvolit také překlad dokumentů, na které aktuální dokument odkazuje, a plugin je automaticky propojí podle cílového jazyka.
-
Flexibilní schémata
Bez ohledu na strukturu se jí plugin přizpůsobí a zajistí, aby byl výsledný přeložený obsah v souladu se schématy Sanity.
-
Pracovní postupy Phrase
Pracovní postupy překladu ve Phrase zůstávají stejné; přeškolení nebo překonfigurování operací není vyžadováno.
Instalace se provádí na 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 ze šablon Starter poskytnutých společností Sanity.
Instalace
Přejděte do projekt obsahující instanci Sanity Studio a nainstalujte plugin:
npm install sanity-plugin-phrase # nebo pnpm, yarn, bun
Proměnné prostředí
Před konfigurací plugin je nutné nastavit následující proměnné prostředí. V kořenovém adresáři projekt vytvořit soubor `.env` (nebo `.env.local` pro Next.js).
Níže uvedené příklady jsou pro NextJS. Pro ostatní frameworky se podívejte do jejich specifické dokumentace a vezměte na vědomí, že u veřejných proměnných může být nutné odstranit předponu `NEXT_PUBLIC_`.
Důležité
Proměnné na straně server (SANITY_WRITE_TOKEN, PHRASE_USER_NAME, PHRASE_PASSWORD) by nikdy neměly být vystaveny klient. V Next.js jsou prohlížeči vystaveny pouze proměnné s předponou NEXT_PUBLIC_.
# Základní URL vašeho webu (používá se pro odkazy na náhled)
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
# URL, kde bude umístěn backendový obslužný program plugin
NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT="http://localhost:3000/api/phrase"
# Oblast datového centra Phrase ('eu' nebo 'us')
NEXT_PUBLIC_PHRASE_REGION="eu"
# Konfigurace projekt Sanity
NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
NEXT_PUBLIC_SANITY_DATASET="production"
# API token Sanity s oprávněním k zápisu (pouze server)
SANITY_WRITE_TOKEN=""
# přihlašovací údaje Phrase (pouze server)
# Poznámka: API Phrase očekává pouze část uživatelského jména, NIKOLI celou e-mail adresu
PHRASE_USER_NAME="phraseUsername"
PHRASE_PASSWORD="secretPassword"
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({
// Vyžadováno: adaptér i18n pro internacionalizaci dokumentů
i18nAdapter: documentInternationalizationAdapter(),
// Vyžadováno: Typy dokumentů, které lze přeložit
translatableTypes: ['page', 'post', 'article'],
// Vyžadováno: Zdrojový jazyk (primární jazyk)
// Musí odpovídat jazyku definovanému v šabloně vašeho projektu Phrase
sourceLang: 'en',
// Vyžadováno: Cílové jazyky, do kterých mohou uživatelé překládat
// Použijte stejné kódy jako u vašich dokumentů Sanity
// Tento seznam musí odpovídat jazykům definovaným v šabloně vašeho projektu Phrase
supportedTargetLangs: ['es', 'fr', 'de', 'pt'],
// Vyžadováno: URL koncového bodu vašeho backendového API
apiEndpoint: process.env.NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT!,
// Vyžadováno: Oblast datového centra Phrase ('eu' nebo 'us')
phraseRegion: process.env.NEXT_PUBLIC_PHRASE_REGION as 'eu' | 'us',
// Vyžadováno: Šablony projektů Phrase dostupné pro editory
phraseTemplates: [
{
templateUid: 'YOUR_TEMPLATE_UID_HERE',
label: 'Výchozí překladová šablona',
},
],
// Vyžadováno: Generovat náhledové adresy URL pro lingvisty
getDocumentPreview: (doc, sanityClient) => {
const publishedId = doc._id.Nahradit('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,
// Povolit překlad konceptů dokumentů (výchozí: false)
translateDrafts: false,
// Vlastní transformátory dat pro speciální typy obsah
dataTransformers: [],
// Konfigurace protokolování pro ladění
logger: {
minimumLogLevel: 'info', // 'debug' | 'info' | 'warning' | 'error' | 'fatal'
},
// Skrýt hlavní panel Phrase na základě rolí uživatele
isPhraseDashboardHidden: (context) =>
!(context.currentUser.roles || []).some((r) => r.name === 'správce'),
})
export default defineConfig({
// ... vaše stávající konfigurace
plugins: [
phrasePlugin(PHRASE_CONFIG),
// ... other plugins
],
})// sanity.config.(js|ts)
import {
phrasePlugin,
documentInternationalizationAdapter,
} from 'sanity-plugin-phrase'
const PHRASE_CONFIG = definePhraseOptions({
/**
* Adaptér i18n, který se má pro tento plugin použít.
* Bude zodpovědný za načítání a úpravu dokumentů pro každý cílový jazyk.
*
* Více informací o adaptérech naleznete níže.
*/
i18nAdapter: documentInternationalizationAdapter(),
/**
* Typy schémat Sanity, které může plugin přeložit
*/
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 jej automaticky převede 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 jej automaticky převede do formátu Phrase.
*/
sourceLang: 'en',
/**
* Jak je definováno v nastavení vašeho uživatelského účtu Phrase
* Buď `eu` nebo `us`
*/
phraseRegion: 'us|eu',
/**
* URL adresa k vašemu nakonfigurovanému backend API pluginu.
*
* **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ů ve front-endu.
*/
getDocumentPreview: async (doc, sanityClient) => {
const publishedId = doc._id.Nahradit('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 uživatelských oprávnění.
*
* Přijímá kontext s aktuálním uživatelem a dokumentem a musí vrátit logickou hodnotu.
*/
isPhraseDashboardHidden: (context) =>
!(context.currentUser.roles || []).some((r) => r.name === 'správce'),
})
export default defineConfig({
// ...
plugins: [
// ...
phrasePlugin(PHRASE_CONFIG),
],
})
Injekce schématu
Aby bylo možné pluginu sdělit, které typy dokumentů lze přeložit, předejte pole typů dokumentů funkci injectPhraseIntoSchema v souboru sanity.config.ts:
// sanity.config.ts
import { injectPhraseIntoSchema } from 'sanity-plugin-phrase'
// Seznam typů schémat, které představují překládaný text. 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: {
types: injectPhraseIntoSchema(TRANSLATABLE_SCHEMAS, PHRASE_CONFIG),
templates: (prev) =>
prev.filtrovat((šablona) => !TRANSLATABLE_SCHEMAS.includes(šablona.ID)),
},
plugins: [
// ...
phrasePlugin({
// Zde jsou vaše konfigurační možnosti
}),
],
})
Vyloučení PTD ze seznamů dokumentů
PTD (Phrase Translation Documents) jsou dočasné dokumenty, které by se neměly zobrazovat v běžných seznamech dokumentů v rámci Sanity Studio. Konstanta NOT_PTD poskytuje pro tento účel filtr GROQ:
// sanity.config.ts
import { NOT_PTD } from 'sanity-plugin-phrase/utils'
export default defineConfig({
// ... další konfigurace
plugins: [
structureTool({
structure: (S) =>
S.list()
.title('obsah')
.items([
S.listItem()
.title('Posts')
.schemaType('post')
.child(
S.documentList()
.title('Posts')
.filtrovat(`_type == \"post\" && ${NOT_PTD}`),
),
// ... další položky
]),
}),
],
})
Skrytí nabídky překladu z vedlejších
Při použití plugin dokument-internationalization by měla být nabídka překladu před vedlejšími skryta. Nástroj isPtdId identifikuje dokument dokument:
import { isPtdId } from 'sanity-plugin-phrase/utils'
import { DocumentInternationalizationMenu } from '@sanity/document-internationalization'
// Použít stejné pole jako translatableTypes z vašeho PHRASE_CONFIG
const TRANSLATABLE_TYPES = ['page', 'post', 'article']
export default defineConfig({
dokument: {
unstable_languageFilter: (prev, ctx) => {
const { schemaType, documentId } = ctx
// Zobrazit nabídku překladu pouze pro skutečné dokumenty, nikoli pro vedlejší
return TRANSLATABLE_TYPES.includes(schemaType) &&
documentId &&
!isPtdId(documentId)"}
? [...prev, DocumentInternationalizationMenu]
: předchozí
},
},
})
Plugin nemá v uživatelském rozhraní Phrase žádnou konfiguraci, ale Phrase musí být nakonfigurován tak, aby odesílal webhook oznámení na koncový bod API backendu. To umožňuje aktualizace v reálném čase v průběhu překladů.
vytvořit webhook
vytvořit webhook s tímto nastavení:
-
URL
URL adresa 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 je plugin informován o všech změnách v projektech Phrase a může udržovat data Sanity synchronizovaná.
Nastavení šablony projektů
Nakonfigurujte šablonu projektů Phrase s vlastnostmi požadovanými pro pracovní postupy a požadavky Team. Při objednávání nového překladu lze nabídnout jednu nebo více šablon, ze kterých lze vybírat. Šablony projektů Phrase musí mít specifické nastavení importu JSON, aby plugin fungoval správně. Tato nastavení určují, která pole se odesílají překladatelům a která jsou zachována jako metadata.
Import souboru JSON
Použijte regulární výraz k Vyloučit specifické klíče:
(^|.*\/) (_createdAt|_id|_rev|_type|_updatedAt|_ref|_key|_sanityRev|_sanityContext|_strengthenOnPublish|phraseMetadata|_spanMeta|_blockMeta|_diff|marks|YOUR_IGNORED_KEYS_HERE| (_createdAt|_id|_rev|_type|_updatedAt|_ref|_key|_sanityRev|_sanityContext|_strengthenOnPublish|phraseMetadata|_spanMeta|_blockMeta|_diff|marks|YOUR_IGNORED_KEYS_HERE) /.*)
Tento výraz obsahuje duplicitní klíče záměrně, aby bylo zajištěno, že je analyzátor regulárních výrazů Phrase ignoruje. Zajistěte, aby byly správně duplikovány.
-
Vyloučit data specifická pro lokalizaci, jako je jazyk daného dokumentu, pokud používáte
@sanity/document-internationalization. -
Zahrnout všechny klíče specifické pro projekt, které nevyžadují překlad, jako je slug pro obsah používající stejnou cestu napříč všemi jazyky. Nahradit
YOUR_IGNORED_KEYS_HEREseznamem klíčů oddělených svislítkem, které chcete ignorovat. -
Kontextová poznámka:
/_sanityContext
Zdrojový jazyk
V současné době tento plugin funguje na předpokladu, že má jeden zdrojový jazyk. Šablona projektu musí mít stejný zdroj jako ten, který je nakonfigurován v sourceLanguage pluginu.
Cílové jazyky
Zajistěte, aby jazyky vybrané ve Phrase byly synchronizovány s tím, co je v konfiguraci pluginu.
Toto je koncový bod, který plugin používá ke komunikaci se Sanity Studiem. Používá se k autentizaci k API Phrase, přijímání webhooků a požadavků uživatelů ze Sanity studia.
Vytvořte vlastní koncový bod API v projektu Sanity pro zpracování těchto požadavků. Jedním z nejjednodušších způsobů, jak toho dosáhnout, je použít serverless funkce prostřednictvím front-end frameworků, jako jsou NextJS, Remix, SvelteKit nebo Nuxt.
Přístup ke konfiguraci obslužné rutiny pomocí vzoru Request-Response prostřednictvím import {createRequestHandler} from nebo použijte interní obslužnou rutinu přímo prostřednictvím import {createInternalHandler} from . Zajistěte, aby byly požadavky CORS zpracovány správně, studio a koncový bod mají různé původy.
Adresář aplikací NextJS není v současné době podporován, protože nesprávně analyzuje obslužnou rutinu backendu jako klientskou komponentu React.
Tento příklad ukazuje vytvoření obslužné rutiny trasy na nakonfigurované cestě apiEndpoint na /api/phrase pomocí Next.js Pages Router:
// app/api/phrase/route.ts
// Podpora API route v Next.js: 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'
import { klient } from '~/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('přístup-Control-Allow-Origin', '*')
res.setHeader('přístup-Control-Allow-Methods', 'POST, OPTIONS')
res.setHeader('přístup-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.stav(405).json({ error: 'Method not allowed' })
return
}
const phraseRes = await phraseHandler(
req.method.toUpperCase() === 'POST' ? req.body : req.dotaz,
)
const resBody = await phraseRes.json().catch(() => {})
Array.from(phraseRes.headers.entries()).forEach((value: [řetězec, 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'
import { klient } from '~/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('přístup-Control-Allow-Origin', '*')
res.setHeader('přístup-Control-Allow-Methods', 'POST, OPTIONS')
res.setHeader('přístup-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.stav(405).json({ error: 'Method not allowed' })
return
}
const phraseRes = await phraseHandler(
req.method.toUpperCase() === 'POST' ? req.body : req.dotaz,
)
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)
}
adaptéry i18n
Sanity nemá žádný předepsaný přístup k internacionalizaci a existuje mnoho způsobů, jak ji implementovat. Tento plugin používá vzor adaptéru, který umožňuje konfiguraci na základě toho, jak je obsah strukturován a jak by měl být přeložen.
V současné době je k dispozici pouze adaptér , který používá oficiální plugin document-internationalization od Sanity (verze ^2.0.0). Pokud je vyžadován konkrétní adaptér, nahlaste problém nebo se podívejte do tohoto úložiště package/src/adapters/document-internationalization.ts, kde najdete příklad, jak implementovat vlastní.
Vlastní transformátory dat
Pokud je vyžadována transformace dat před jejich odesláním do Phrase, použijte možnost . To je užitečné při změně struktury dat nebo při vyloučení určitých polí z překladu.
Každý transformátor dat musí data před odesláním do Phrase zakódovat a při jejich přijetí zpět dekódovat, aby je mohl transformovat před uložením do Sanity. Více transformátorů lze vrstvit a spouštět postupně.
Plugin nenabízí žádný způsob, jak testovat transformátory izolovaně, takže vývoj může být složitý. Uložit skutečné cílové dokumenty ze sady dat Sanity do .JSON a použít je jako testovací data pro každou funkci encode/decode.
Příklad úpravy JSON-kódovaných souborů VTT na HTML, aby Phrase mohl lépe segmentovat obsah titulků:
import { DataTransformer } from 'sanity-plugin-phrase'
const vttJsonTransformer: DataTransformer = {
encode: {
array(arr) {
// Zkontrolovat, zda pole obsahuje uzly titulků 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 // Vrátit undefined pro přeskočení transformace
},
},
decode: {
object(obj) {
if (!!obj && '_type' in obj && obj._type === 'encodedSubtitles') {
return decodeSubtitles(obj as EncodedSubtitles)
}
return undefined
},
},
}
stáhnout const PHRASE_CONFIG = definePhraseOptions({
// ...
dataTransformers: [vttJsonTransformer],
})stáhnout 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
},
},
}
// Přeskakování implementace
// Viz /demo-nextjs/src/utils/vttJsonTransformer.ts pro úplný zdrojový kód
declare function decodeSubtitles(
encoded: EncodedSubtitles,
): StoredSubtitleNode[]
declare function encodeSubtitles(nodes: StoredSubtitleNode[]): EncodedSubtitles
Omezení přístupu editora
Které editory mohou mít přístup k hlavní panel Phrase lze omezit implementací možnosti . Tato funkce je ekvivalentní funkci předané vlastnosti hidden pole v Sanity. Přijímá kontext s aktuálním uživatel a dokument a musí vrátit boolean.
Příklad omezení přístup k hlavní panel Phrase pro uživatel s role správce:
const PHRASE_CONFIG = definePhraseOptions({
// ...
isPhraseDashboardHidden: (context) => {
const isAdmin = (context.currentUser.roles || []).some(
(r) => r.name === 'správce',
)
// Skrýt, pokud není správce
return !isAdmin
},
})