Tento plugin poskytuje přístup k Phrase přeložený obsah přímo ze Sanity studia.
Podporovány jsou pouze překlady na úrovni dokument. Překlady na úrovni polí nejsou podporovány.
Funkce:
-
Náhled v reálném čase
Překlady jsou udržovány v synchronizaci, aby lingvisté a překladatelé mohli vidět změny v náhled 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ý jazyk.
-
Flexibilní schémata
Bez ohledu na strukturu se jí plugin přizpůsobí a zajistí, aby byl výsledný obsah v souladu se schématy Sanity.
-
Phrase pracovní postupy
Pracovní postupy překladu v 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 z Starter šablon 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 Další.js).
Níže uvedené příklady jsou pro DalšíJS. 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é jméno, NIKOLI celou e-mail adresu
PHRASE_USER_NAME="phraseUsername"
PHRASE_PASSWORD="secretPassword"
Konfigurace plugin
Tento 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 internacionalizaci dokumentů
i18nAdapter: documentInternationalizationAdapter(),
// Povinné: Typy dokumentů, které lze přeložit
translatableTypes: ['page', 'post', 'article'],
// Povinné: Zdroj jazyk (primární jazyk)
// Toto musí odpovídat jazyk definovanému ve vaší šablona projektu Phrase
sourceLang: 'en',
// Povinné: Cíl jazyky, do kterých mohou uživatelé překládat
// Použít stejné kódy jako vaše dokumenty Sanity
// Tento seznam musí odpovídat jazyky definovaným ve vaší šablona projektu Phrase
supportedTargetLangs: ['es', 'fr', 'de', 'pt'],
// Povinné: Adresa URL koncového bodu vašeho backend API
apiEndpoint: process.env.NEXT_PUBLIC_PHRASE_PLUGIN_API_ENDPOINT!,
// Povinné: Oblast datového centra Phrase ('eu' nebo 'us')
phraseRegion: process.env.NEXT_PUBLIC_PHRASE_REGION as 'eu' | 'us',
// Povinné: Šablony projekt Phrase dostupné pro editory
phraseTemplates: [
{
templateUid: 'YOUR_TEMPLATE_UID_HERE',
label: 'Výchozí šablona překladu',
},
],
// Povinné: Generovat adresy URL náhledů 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živatelů
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, které jsou uvedeny 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, které jsou uvedeny 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é jsou překládaný text. Obvykle exportováno z indexového souboru
// kdekoli máte umístěno své Sanity Schema
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 seznamy dokumentů uvnitř Sanity Studio. Konstanta NOT_PTD poskytuje pro tento účel GROQ filtrovat:
// 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}`),
),
// ... ostatní položky
]),
}),
],
})
Skrytí nabídky překladu z vedlejších dokumentů
Při použití pluginu document-internationalization by měla být nabídka překladu pro vedlejší dokumenty skryta. Nástroj isPtdId identifikuje dokumenty vedlejší:
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ší dokumenty
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
Adresa URL ke koncovému 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í šablon projektů
Nakonfigurujte šablonu(y) projektu 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 výběr z jedné nebo více šablon. Šablony projektů Phrase musí mít specifické nastavení importu JSON, aby plugin správně fungoval. Tato nastavení určují, která pole jsou odesílána překladatelům a která jsou zachována jako metadata.
import souboru JSON
Použít 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 zvolené ve Phrase byly synchronizovány s tím, co je v konfiguraci pluginu.
Toto je endpoint, 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í API endpoint 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 handleru s modelem Request-Response prostřednictvím import {createRequestHandler} from nebo použít interní handler přímo prostřednictvím import {createInternalHandler} from . Zajistěte, aby byly požadavky CORS zpracovány správně, studio a endpoint mají různé původy.
Adresář app v NextJS není v současné době podporován, protože nesprávně analyzuje backend handler jako klientskou komponentu React.
Tento příklad ukazuje vytvoření Route Handleru na nakonfigurované cestě apiEndpoint na /api/phrase pomocí Next.js Pages Router:
// app/api/phrase/route.ts
// Další.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'
import { klient } from '~/lib/sanity.klient'
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.klient'
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)
}
i18n adaptéry
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ý je používán oficiálním document-internationalization plugin od Sanity (verze ^2.0.0). Pokud je vyžadován konkrétní adaptér, založte problém, nebo se podívejte do tohoto repository's package/src/adapters/document-internationalization.ts pro 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 . Toto 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 zakódovat před odesláním do Phrase; a dekódovat je při jejich přijetí zpět, aby je transformoval před uložením do Sanity. Více transformátorů lze vrstvit a spouštět postupně.
Tento 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 kódování/dekódování.
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 === 'řetězec' &&
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
},
},
}
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 === 'řetězec' &&
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řístup editoru
Které editor může mít přístup k hlavní panel Phrase lze omezit implementací možnosti i. Tato funkce je ekvivalentní funkci předané vlastnosti hidden pole v Sanity. Přijímá kontext s aktuální 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
},
})