برنامه نویسی

MDX با ترجمه 🐠

Summarize this content to 400 words in Persian Lang در سال‌های اخیر نیاز به قالبی آسان و در عین حال انعطاف‌پذیر برای محتوای ثابت ساده افزایش یافته است. در اصل، HTML برای آن در نظر گرفته شده بود – اما بیایید صادق باشیم: نه ساده است و نه انعطاف پذیر. پیشنهادهای جایگزین زیادی وجود دارد – تقریباً هر CMS با لهجه خاص خود ارائه می شود.

یکی از رقبای خوب برای محتوای ثابت که می تواند به خوبی خوانده شود و ارائه شود، Markdown است. با این حال، کاستی های بسیار کمی دارد. مهمتر از همه، Markdown به طور کامل مشخص نشده است – منجر به گویش های متعددی می شود که وجود دارد. علاوه بر این، بسیاری از چیزهای ضروری فقط به صورت پسوندهای غیر استاندارد در دسترس هستند. در نهایت، استفاده از اجزای سفارشی در Markdown امکان پذیر نیست.

اینجاست که MDX وارد عمل می شود. این در اصل ترکیبی از JSX / React و گویش مشخص Markdown است. اساساً، این اجازه می دهد تا متن هایی را بنویسید که به سادگی Markdown ساده هستند و همچنین اجزای پیچیده ای را که ممکن است در JSX کامل انجام داده باشید ایجاد کنید.

ساختار وب سایت

ما بر اساس ساختاری که در صفحات سریعتر مقاله اخیر منتشر شده خود با React و پست وبلاگ اصلی خود در سال 2019 تنظیم شده است. در این ساختار، همه صفحات را با استفاده از React برای هر صفحه به ترتیب نسبی مسیر خود قرار داده ایم:

اکنون دو چیز وجود دارد که می خواهیم به آنها رسیدگی کنیم:

نوشتن محتوا (و خواندن) را برای هر کارمند در smapiot بهبود بخشید
ساده سازی ترجمه ها؛ در حال حاضر ساختار نیاز به تکرار صفحات دارد (قرار دادن آنها با محتوای مختلف / ترجمه شده در پوشه های “de” و “en”).

برای اولی می‌خواهیم از MDX استفاده کنیم – برای دومی باید راه‌حلی ارائه کنیم که به ما امکان می‌دهد ساختاری مسطح داشته باشیم.

ادغام MDX

آوردن MDX به راه حل در واقع کاملاً ساده است. را @mdx-js/mdx بسته شامل همه چیز برای کامپایل و کار با فایل های MDX است. با این حال، از آنجایی که ما در حال حاضر از یک باندلر (Vite) استفاده می کنیم، می توانیم فقط به افزونه خاصی که برای آن باندلر وجود دارد، تکیه کنیم.

npm i @mdx-js/rollup –save-dev

چرا بسته ای به نام نصب می کنیم rollup اگر باندلر ما Vite باشد؟ به نظر می رسد که Vite در واقع فقط یک ابزار ساخت است – برای بسته بندی واقعی از دو چیز دیگر استفاده می شود:

esbuild برای بسته‌بندی/پردازش هر چیزی در بسته‌های شخص ثالث (و همچنین استفاده از آن، به عنوان مثال، در فایل پیکربندی)
جمع آوری برای بسته بندی/پردازش کد کاربر

از این رو پلاگین جمع آوری – که تمام چیزی است که ما نیاز داریم.

ادغام در ما vite.config.mjs فایل به شکل زیر است:

import codegen from ‘vite-plugin-codegen’;
import mdx from ‘@mdx-js/rollup’;
import { resolve } from ‘path’;

export default {
build: {
assetsInlineLimit: 0,
},
resolve: {
alias: {
‘@’: resolve(__dirname, ‘src’),
},
},
plugins: [
codegen(),
mdx(),
],
};

توجه داشته باشید که ما در حال حاضر هیچ گزینه ای ارائه نمی دهیم. این فقط کار می کند!

پس اینجا چه کار می توانیم بکنیم؟ یکی از صفحات قبلی ما را در نظر بگیرید، سلب مسئولیت قانونی (چه کسی صفحات قانونی را دوست ندارد؟!):

import * as React from ‘react’;
import { Page } from ‘@smapiot/components/lib/basic/Page’;

export const meta = {
title: ‘Legal Disclaimer’,
legal: ‘Legal Disclaimer’,
};

const title = ‘smapiot – Legal Disclaimer’;

export default () => (
<Page title={title}>
<section className=”container”>
<h1>Legal Disclaimer</h1>
<h3>Liability for Contents</h3>
<p>
The contents of our pages have been compiled with the greatest care. However, we cannot guarantee for accuracy,
completeness or topicality of the contents. As service providers, we are liable for own contents of these
websites according to sec. 7, paragraph 1 German Telemedia Act (TMG). However, according to sec. 8 to 10 German
Telemedia Act (TMG), service providers are not obligated to permanently monitor submitted or stored information
or to search for evidences that indicate illegal activities. Legal obligations to removing information or to
blocking the use of information remain unchallenged. In this case, liability is only possible at the time of
knowledge about a specific violation of law. Illegal contents will be removed immediately at the time we get
knowledge of them.
</p>
<h3>Liability for Links</h3>
<p>
Our web pages include links to external third party websites. We have no influence on the contents of those
external websites, therefore we cannot guarantee for those contents. Providers or administrators of linked
websites are always responsible for their own contents. The linked websites had been checked for possible
violations of law at the time of the establishment of the link. Illegal contents were not detected at the time
of the linking. A permanent monitoring of the contents of linked websites cannot be imposed without reasonable
indications that there has been a violation of law. Illegal links will be removed immediately at the time we get
knowledge of them.
</p>
<h3>Copyright</h3>
<p>
Contents and compilations published on these websites by the providers are subject to German copyright laws.
Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law
require a written permission of the author or originator. Downloads and copies of these websites are permitted
for private use only. The commercial use of our contents without permission of the originator is prohibited.
Copyright laws of third parties are respected as long as the contents on these websites do not originate from
the provider. Contributions of third parties on this site are indicated as such. However, if you notice any
violations of copyright law, please inform us. Such contents will be removed immediately.
</p>
</section>
</Page>
);

این خیلی بد نیست! کاری که اکنون می توانیم انجام دهیم این است که نام آن را از آن تغییر دهیم سلب مسئولیت.tsx به سلب مسئولیت.mdx و محتوای آن را به صورت زیر تغییر دهید:

export const meta = {
title: ‘Legal Disclaimer’,
legal: ‘Legal Disclaimer’,
};

<section className=”container”>
# Legal Disclaimer

### Liability for Contents

The contents of our pages have been compiled with the greatest care. However, we cannot guarantee for accuracy, completeness or topicality of the contents. As service providers, we are liable for own contents of these websites according to sec. 7, paragraph 1 German Telemedia Act (TMG). However, according to sec. 8 to 10 German Telemedia Act (TMG), service providers are not obligated to permanently monitor submitted or stored information or to search for evidences that indicate illegal activities. Legal obligations to removing information or to blocking the use of information remain unchallenged. In this case, liability is only possible at the time of knowledge about a specific violation of law. Illegal contents will be removed immediately at the time we get knowledge of them.

### Liability for Links

Our web pages include links to external third party websites. We have no influence on the contents of those external websites, therefore we cannot guarantee for those contents. Providers or administrators of linked websites are always responsible for their own contents. The linked websites had been checked for possible violations of law at the time of the establishment of the link. Illegal contents were not detected at the time of the linking. A permanent monitoring of the contents of linked websites cannot be imposed without reasonable indications that there has been a violation of law. Illegal links will be removed immediately at the time we get knowledge of them.

### Copyright

Contents and compilations published on these websites by the providers are subject to German copyright laws. Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law require a written permission of the author or originator. Downloads and copies of these websites are permitted for private use only. The commercial use of our contents without permission of the originator is prohibited. Copyright laws of third parties are respected as long as the contents on these websites do not originate from the provider. Contributions of third parties on this site are indicated as such. However, if you notice any violations of copyright law, please inform us. Such contents will be removed immediately.
</section>

خواندن بسیار آسان تر است! توجه داشته باشید که واردات غیر ضروری مانند react می تواند حذف شود، در حالی که جزء بسته بندی دیگ بخار Page اکنون به طور خودکار درج خواهد شد.

اما چیز دومی بود که ما به آن نیاز داشتیم، درست است؟ در مورد بهبودهای محلی سازی / ساختار صفحه چطور؟

بیایید ببینیم اینجا چه کاری می توانیم انجام دهیم!

ارائه ترجمه

چه می شود اگر رشته های محلی سازی را همه در یک سایدکار کوچک قرار دهیم؟ به عنوان مثال، برای فایلی مانند سلب مسئولیت.mdx ما همچنین یک فایل خواهیم داشت سلب مسئولیت.yml. در این فایل می‌توانیم ترجمه‌ها را با توجه به زبان‌ها قرار دهیم.

کل ساختار می تواند به این شکل باشد:

جایی که یک فایل فردی، به عنوان مثال، برای سلب مسئولیت.yml می تواند به صورت زیر نوشته شود:

de:
content$: |
# Haftungsausschluss

### Haftung für Inhalte

Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.

### Haftung für Links

Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.

### Urheberrecht

Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
en:
content$: |
# Legal Disclaimer

### Liability for Contents

The contents of our pages have been compiled with the greatest care. However, we cannot guarantee for accuracy, completeness or topicality of the contents. As service providers, we are liable for own contents of these websites according to sec. 7, paragraph 1 German Telemedia Act (TMG). However, according to sec. 8 to 10 German Telemedia Act (TMG), service providers are not obligated to permanently monitor submitted or stored information or to search for evidences that indicate illegal activities. Legal obligations to removing information or to blocking the use of information remain unchallenged. In this case, liability is only possible at the time of knowledge about a specific violation of law. Illegal contents will be removed immediately at the time we get knowledge of them.

### Liability for Links

Our web pages include links to external third party websites. We have no influence on the contents of those external websites, therefore we cannot guarantee for those contents. Providers or administrators of linked websites are always responsible for their own contents. The linked websites had been checked for possible violations of law at the time of the establishment of the link. Illegal contents were not detected at the time of the linking. A permanent monitoring of the contents of linked websites cannot be imposed without reasonable indications that there has been a violation of law. Illegal links will be removed immediately at the time we get knowledge of them.

### Copyright

Contents and compilations published on these websites by the providers are subject to German copyright laws. Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law require a written permission of the author or originator. Downloads and copies of these websites are permitted for private use only. The commercial use of our contents without permission of the originator is prohibited. Copyright laws of third parties are respected as long as the contents on these websites do not originate from the provider. Contributions of third parties on this site are indicated as such. However, if you notice any violations of copyright law, please inform us. Such contents will be removed immediately.

برای این فایل مجموعه ای از قراردادها وجود دارد:

کلیدهای سطح بالا باید زبان معتبر باشند (فقط در حال حاضر de و en)
کلیدها می توانند تو در تو باشند، به عنوان مثال، اشیا / آرایه ها مجاز هستند
وقتی یک کلید با یک ختم می شود $ امضا کنید که به عنوان Markdown تفسیر می شود (به طور خاص، MDX)

فایل MDX با چنین فایل سایدکار چگونه به نظر می رسد؟

export const meta = {
title: ‘Legal Disclaimer’,
legal: ‘Legal Disclaimer’,
};

<section className=”container”>
{locale.content$}
</section>

حالا این جذاب به نظر می رسد!

برای ادغام بومی سازی هنوز باید کاری انجام دهیم. استفاده از را ببینید locale در کد بالا اما چگونه می شود locale منظره را در فایل وارد کنید؟ بالاخره ما آن را نمی بینیم!

معلوم شد که می توانیم کد تولید شده را توسط MDX دستکاری کنیم. بیایید با افزودن یک افزونه سفارشی به ابزار، کمی ابزار را پیکربندی کنیم recmaPlugins گزینه این به ما اجازه می دهد تا کنترل کاملی بر درخت نحو انتزاعی تولید شده (AST) داشته باشیم، که سپس برای تولید کد واقعی نشان دهنده فایل MDX استفاده می شود.

import codegen from ‘vite-plugin-codegen’;
import mdx from ‘@mdx-js/rollup’;
import { resolve } from ‘path’;
import localize from ‘./src/tools/localize.mjs’;

export default {
build: {
assetsInlineLimit: 0,
},
resolve: {
alias: {
‘@’: resolve(__dirname, ‘src’),
},
},
plugins: [
codegen(),
mdx({
recmaPlugins: [localize],
}),
],
};

وارداتی localize تابع پلاگین واقعی است – که فقط عملکرد دیگری را ارائه می دهد – به اصطلاح ترانسفورماتور. ترانسفورماتور مسئول دستکاری AST است.

را transform تابع در قطعه بعدی، AST و فایل مجازی (یعنی نام، محتوا، …) فایل MDX را که در حال حاضر در حال مدیریت است، دریافت می کند.

بیایید قبل از اینکه به این کد بپردازیم، کد را ببینیم:

import { compileSync } from ‘@mdx-js/mdx’;
import { loadLocale } from ‘./localization.mjs’;

function fromMarkdown(content) {
let result = {};
compileSync(content, {
development: false,
recmaPlugins: [
() => (ast) => {
const [, fn] = ast.body;
result = {
type: ‘CallExpression’,
optional: false,
callee: {
…fn,
id: null,
},
arguments: [
{
type: ‘ObjectExpression’,
properties: [],
},
],
};
},
],
});

return result;
}

function getExpression(content, key) {
switch (typeof content) {
case ‘object’:
if (Array.isArray(content)) {
return {
type: ‘ArrayExpression’,
elements: content.map((item, i) => getExpression(item, `${i}`)),
};
}

return {
type: ‘ObjectExpression’,
properties: Object.entries(content).map(([name, value]) => ({
type: ‘Property’,
key: {
type: ‘Identifier’,
name,
},
value: getExpression(value, name),
kind: ‘init’,
})),
};
case ‘number’:
case ‘boolean’:
return {
type: ‘Literal’,
value: content,
};
case ‘string’:
if (key.endsWith(‘$’)) {
// uses potentially markdown
return fromMarkdown(content);
}

return {
type: ‘Literal’,
value: content,
};
case ‘undefined’:
default:
return {
type: ‘Literal’,
value: null,
};
}
}

async function transform(ast, vfile) {
const language = process.env.WEBSITE_LOCALE || ‘en’;

const source = vfile.history[0];
const locale = await loadLocale(source, language);

const idx = ast.body.findLastIndex((m) => m.type === ‘ImportDeclaration’);
const imprt = ast.body.find((node) => node.type === ‘ImportDeclaration’ && node.source.value === ‘react/jsx-runtime’);

const requiredImports = [
[‘_jsx’, ‘jsx’],
[‘_jsxs’, ‘jsxs’],
[‘_Fragment’, ‘Fragment’],
];

for (const requiredImport of requiredImports) {
const [alias, original] = requiredImport;

if (!imprt.specifiers.find((node) => node.type === ‘ImportSpecifier’ && node.imported.name === original)) {
imprt.specifiers.push({
type: ‘ImportSpecifier’,
imported: {
type: ‘Identifier’,
name: original,
},
local: {
type: ‘Identifier’,
name: alias,
},
});
}
}

ast.body.splice(idx + 1, 0, {
type: ‘VariableDeclaration’,
kind: ‘const’,
declarations: [
{
type: ‘VariableDeclarator’,
id: {
type: ‘Identifier’,
name: ‘locale’,
},
init: getExpression(locale, language),
},
],
});
}

const plugin = () => transform;

export default plugin;

به نظر پیچیده تر از آن چیزی است که باید باشد! در پایان، همه چیز به تغییر AST خلاصه می شود

بدست آوردن locale برای سند
وارد کردن نام بالقوه گمشده (برای پشتیبانی کامل از Markdown / محتوای تولید شده، در صورت وجود) در react/jsx-runtime واردات
درج کنید locale اعلامیه – اولین چیز بعد از واردات (از این طریق می توانید استفاده کنید locale تقریباً در همه جای سند
مقداردهی اولیه locale متغیر شیئی باشد که می شناسیم. به استثنای رشته های Markdown (پسوند با $): اینها به یک IIFE تبدیل می شوند که کد MDX اصلی را حفظ می کند

یک راه خوب برای دیدن کارهایی که انجام می دهیم را می توان با استفاده از زمین بازی MDX مشاهده کرد.

همانطور که می بینید کد کامپایل شده کمی عجیب است، اما می تواند به خوبی به فایل MDX اصلی ما نگاشت شود.

در مقابل، دیدگاهی که در واقع بیشتر به آن علاقه داریم، مرحله قبل از قرار گرفتن کد در آن حالت کامپایل شده است. این نمای “east” در زمین بازی است (یعنی AST صحنه ESTree).

چیزی که می خواهیم با افزونه خود به دست آوریم این است که شروع را دستکاری می کنیم. متأسفانه، به طور مستقیم در کد MDX ما باید از یک استفاده کنیم export برای آن، اما با دستکاری مستقیم AST ما در واقع به آن نیاز نداریم.

برای به دست آوردن locale متغیر / شی ما loadLocale عملکرد از یک ماژول اختصاصی

این ماژول انجام می دهد:

ترجمه های خاص محلی / فایل را بخوانید و تجزیه کنید
ترجمه های زبان خاص فایل محلی را دریافت کنید
ترجمه های جهانی را بخوانید و تجزیه کنید
ترجمه های زبان خاص فایل سراسری را دریافت کنید
ادغام با ترجمه های محلی که بالاتر در نظر گرفته می شوند

برای به دست آوردن ترجمه های خاص زبان، ما یک ترجمه پایه را انتخاب می کنیم (در مورد ما en) و از طریق شی “راه بروید”. زبان فعلی (بگذریم de) سپس یا ترجمه پایه را لغو می کند یا فقط از قطعه به دست آمده از ترجمه پایه استفاده می کند.

شما می توانید این فرآیند را به عنوان یک ادغام عمیق با ترجمه اصلی که ترجمه اصلی است در نظر بگیرید.

import { readFile } from ‘fs/promises’;
import { resolve } from ‘path’;
import { parse } from ‘yaml’;

async function getLocalization(path) {
try {
const content = await readFile(path, ‘utf8’);
return parse(content);
} catch (e) {
console.error(‘Error reading YAML file:’, e);
return {};
}
}

function mergeLocale(result, baseLocale, newLocale) {
if (!newLocale) {
Object.assign(result, baseLocale);
} else {
Object.entries(baseLocale).forEach(([name, value]) => {
const c = newLocale[name];

if (typeof value === ‘object’) {
mergeLocale((result[name] = {}), value, c || {});
} else if (typeof c === ‘string’ || c) {
result[name] = c;
} else {
result[name] = value;
}
});
}

return result;
}

function getLocale(locales, lang) {
const baseLocale = locales.en || {};

if (lang !== ‘en’ && lang in locales) {
const newLocale = locales[lang] || {};
return mergeLocale({}, baseLocale, newLocale);
}

return baseLocale;
}

export async function loadLocale(source, language) {
if (source.endsWith(‘.mdx’)) {
const globalFn = resolve(import.meta.dirname, ‘..’, ‘global.yml’);
const targetFn = source.replace(‘.mdx’, ‘.yml’);

const globals = await getLocalization(globalFn);
const locales = await getLocalization(targetFn);

return {
…getLocale(globals, language),
…getLocale(locales, language),
language,
};
}

return { language };
}

ترجمه های سراسری در پوشه بالا قرار می گیرند pages. نام فایل است global.yml.

برای global.yml ما فقط برخی از اصطلاحات ترجمه بسیار رایج را ذخیره کرده ایم. یک مثال:

de:
germany: Deutschland
en:
germany: Germany

زمانی که ترجمه‌های جدید می‌آیند، فقط می‌توانید یک فایل را تغییر دهید (یا فقط تغییر ساختار در یک مکان مستقل از تغییرات زبان) کار بزرگی است. برای ما این بسیار مهم بود و ساختار جدید نشان دهنده آن است.

نتیجه گیری

با رویکرد جدید، نوشتن صفحات بسیار آسان تر از قبل است. همچنین، از آنجایی که ما اکنون یک سیستم ترجمه ثابت داریم، نیازی به کپی کردن صفحات یا ارائه سیستم پیچیده برای اجزای میانی نداریم. همه اینها قبلاً در زمان کامپایل در ساختار داده شده انجام شده است.

در سال‌های اخیر نیاز به قالبی آسان و در عین حال انعطاف‌پذیر برای محتوای ثابت ساده افزایش یافته است. در اصل، HTML برای آن در نظر گرفته شده بود – اما بیایید صادق باشیم: نه ساده است و نه انعطاف پذیر. پیشنهادهای جایگزین زیادی وجود دارد – تقریباً هر CMS با لهجه خاص خود ارائه می شود.

یکی از رقبای خوب برای محتوای ثابت که می تواند به خوبی خوانده شود و ارائه شود، Markdown است. با این حال، کاستی های بسیار کمی دارد. مهمتر از همه، Markdown به طور کامل مشخص نشده است – منجر به گویش های متعددی می شود که وجود دارد. علاوه بر این، بسیاری از چیزهای ضروری فقط به صورت پسوندهای غیر استاندارد در دسترس هستند. در نهایت، استفاده از اجزای سفارشی در Markdown امکان پذیر نیست.

اینجاست که MDX وارد عمل می شود. این در اصل ترکیبی از JSX / React و گویش مشخص Markdown است. اساساً، این اجازه می دهد تا متن هایی را بنویسید که به سادگی Markdown ساده هستند و همچنین اجزای پیچیده ای را که ممکن است در JSX کامل انجام داده باشید ایجاد کنید.

ساختار وب سایت

ما بر اساس ساختاری که در صفحات سریعتر مقاله اخیر منتشر شده خود با React و پست وبلاگ اصلی خود در سال 2019 تنظیم شده است. در این ساختار، همه صفحات را با استفاده از React برای هر صفحه به ترتیب نسبی مسیر خود قرار داده ایم:

ساختار صفحات

اکنون دو چیز وجود دارد که می خواهیم به آنها رسیدگی کنیم:

  1. نوشتن محتوا (و خواندن) را برای هر کارمند در smapiot بهبود بخشید
  2. ساده سازی ترجمه ها؛ در حال حاضر ساختار نیاز به تکرار صفحات دارد (قرار دادن آنها با محتوای مختلف / ترجمه شده در پوشه های “de” و “en”).

برای اولی می‌خواهیم از MDX استفاده کنیم – برای دومی باید راه‌حلی ارائه کنیم که به ما امکان می‌دهد ساختاری مسطح داشته باشیم.

ادغام MDX

آوردن MDX به راه حل در واقع کاملاً ساده است. را @mdx-js/mdx بسته شامل همه چیز برای کامپایل و کار با فایل های MDX است. با این حال، از آنجایی که ما در حال حاضر از یک باندلر (Vite) استفاده می کنیم، می توانیم فقط به افزونه خاصی که برای آن باندلر وجود دارد، تکیه کنیم.

npm i @mdx-js/rollup --save-dev

چرا بسته ای به نام نصب می کنیم rollup اگر باندلر ما Vite باشد؟ به نظر می رسد که Vite در واقع فقط یک ابزار ساخت است – برای بسته بندی واقعی از دو چیز دیگر استفاده می شود:

  • esbuild برای بسته‌بندی/پردازش هر چیزی در بسته‌های شخص ثالث (و همچنین استفاده از آن، به عنوان مثال، در فایل پیکربندی)
  • جمع آوری برای بسته بندی/پردازش کد کاربر

از این رو پلاگین جمع آوری – که تمام چیزی است که ما نیاز داریم.

ادغام در ما vite.config.mjs فایل به شکل زیر است:

import codegen from 'vite-plugin-codegen';
import mdx from '@mdx-js/rollup';
import { resolve } from 'path';

export default {
  build: {
    assetsInlineLimit: 0,
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  plugins: [
    codegen(),
    mdx(),
  ],
};

توجه داشته باشید که ما در حال حاضر هیچ گزینه ای ارائه نمی دهیم. این فقط کار می کند!

پس اینجا چه کار می توانیم بکنیم؟ یکی از صفحات قبلی ما را در نظر بگیرید، سلب مسئولیت قانونی (چه کسی صفحات قانونی را دوست ندارد؟!):

import * as React from 'react';
import { Page } from '@smapiot/components/lib/basic/Page';

export const meta = {
  title: 'Legal Disclaimer',
  legal: 'Legal Disclaimer',
};

const title = 'smapiot - Legal Disclaimer';

export default () => (
  <Page title={title}>
    <section className="container">
      <h1>Legal Disclaimer</h1>
      <h3>Liability for Contents</h3>
      <p>
        The contents of our pages have been compiled with the greatest care. However, we cannot guarantee for accuracy,
        completeness or topicality of the contents. As service providers, we are liable for own contents of these
        websites according to sec. 7, paragraph 1 German Telemedia Act (TMG). However, according to sec. 8 to 10 German
        Telemedia Act (TMG), service providers are not obligated to permanently monitor submitted or stored information
        or to search for evidences that indicate illegal activities. Legal obligations to removing information or to
        blocking the use of information remain unchallenged. In this case, liability is only possible at the time of
        knowledge about a specific violation of law. Illegal contents will be removed immediately at the time we get
        knowledge of them.
      </p>
      <h3>Liability for Links</h3>
      <p>
        Our web pages include links to external third party websites. We have no influence on the contents of those
        external websites, therefore we cannot guarantee for those contents. Providers or administrators of linked
        websites are always responsible for their own contents. The linked websites had been checked for possible
        violations of law at the time of the establishment of the link. Illegal contents were not detected at the time
        of the linking. A permanent monitoring of the contents of linked websites cannot be imposed without reasonable
        indications that there has been a violation of law. Illegal links will be removed immediately at the time we get
        knowledge of them.
      </p>
      <h3>Copyright</h3>
      <p>
        Contents and compilations published on these websites by the providers are subject to German copyright laws.
        Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law
        require a written permission of the author or originator. Downloads and copies of these websites are permitted
        for private use only. The commercial use of our contents without permission of the originator is prohibited.
        Copyright laws of third parties are respected as long as the contents on these websites do not originate from
        the provider. Contributions of third parties on this site are indicated as such. However, if you notice any
        violations of copyright law, please inform us. Such contents will be removed immediately.
      </p>
    </section>
  </Page>
);

این خیلی بد نیست! کاری که اکنون می توانیم انجام دهیم این است که نام آن را از آن تغییر دهیم سلب مسئولیت.tsx به سلب مسئولیت.mdx و محتوای آن را به صورت زیر تغییر دهید:

export const meta = {
  title: 'Legal Disclaimer',
  legal: 'Legal Disclaimer',
};

<section className="container">
  # Legal Disclaimer

  ### Liability for Contents

  The contents of our pages have been compiled with the greatest care. However, we cannot guarantee for accuracy, completeness or topicality of the contents. As service providers, we are liable for own contents of these websites according to sec. 7, paragraph 1 German Telemedia Act (TMG). However, according to sec. 8 to 10 German Telemedia Act (TMG), service providers are not obligated to permanently monitor submitted or stored information or to search for evidences that indicate illegal activities. Legal obligations to removing information or to blocking the use of information remain unchallenged. In this case, liability is only possible at the time of knowledge about a specific violation of law. Illegal contents will be removed immediately at the time we get knowledge of them.

  ### Liability for Links

  Our web pages include links to external third party websites. We have no influence on the contents of those external websites, therefore we cannot guarantee for those contents. Providers or administrators of linked websites are always responsible for their own contents. The linked websites had been checked for possible violations of law at the time of the establishment of the link. Illegal contents were not detected at the time of the linking. A permanent monitoring of the contents of linked websites cannot be imposed without reasonable indications that there has been a violation of law. Illegal links will be removed immediately at the time we get knowledge of them.

  ### Copyright

  Contents and compilations published on these websites by the providers are subject to German copyright laws. Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law require a written permission of the author or originator. Downloads and copies of these websites are permitted for private use only. The commercial use of our contents without permission of the originator is prohibited. Copyright laws of third parties are respected as long as the contents on these websites do not originate from the provider. Contributions of third parties on this site are indicated as such. However, if you notice any violations of copyright law, please inform us. Such contents will be removed immediately.
</section>

خواندن بسیار آسان تر است! توجه داشته باشید که واردات غیر ضروری مانند react می تواند حذف شود، در حالی که جزء بسته بندی دیگ بخار Page اکنون به طور خودکار درج خواهد شد.

اما چیز دومی بود که ما به آن نیاز داشتیم، درست است؟ در مورد بهبودهای محلی سازی / ساختار صفحه چطور؟

بیایید ببینیم اینجا چه کاری می توانیم انجام دهیم!

ارائه ترجمه

چه می شود اگر رشته های محلی سازی را همه در یک سایدکار کوچک قرار دهیم؟ به عنوان مثال، برای فایلی مانند سلب مسئولیت.mdx ما همچنین یک فایل خواهیم داشت سلب مسئولیت.yml. در این فایل می‌توانیم ترجمه‌ها را با توجه به زبان‌ها قرار دهیم.

کل ساختار می تواند به این شکل باشد:

ساختار مجدد صفحات

جایی که یک فایل فردی، به عنوان مثال، برای سلب مسئولیت.yml می تواند به صورت زیر نوشته شود:

de:
  content$: |
    # Haftungsausschluss

    ### Haftung für Inhalte

    Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.

    ### Haftung für Links

    Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.

    ### Urheberrecht

    Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
en:
  content$: |
    # Legal Disclaimer

    ### Liability for Contents

    The contents of our pages have been compiled with the greatest care. However, we cannot guarantee for accuracy, completeness or topicality of the contents. As service providers, we are liable for own contents of these websites according to sec. 7, paragraph 1 German Telemedia Act (TMG). However, according to sec. 8 to 10 German Telemedia Act (TMG), service providers are not obligated to permanently monitor submitted or stored information or to search for evidences that indicate illegal activities. Legal obligations to removing information or to blocking the use of information remain unchallenged. In this case, liability is only possible at the time of knowledge about a specific violation of law. Illegal contents will be removed immediately at the time we get knowledge of them.

    ### Liability for Links

    Our web pages include links to external third party websites. We have no influence on the contents of those external websites, therefore we cannot guarantee for those contents. Providers or administrators of linked websites are always responsible for their own contents. The linked websites had been checked for possible violations of law at the time of the establishment of the link. Illegal contents were not detected at the time of the linking. A permanent monitoring of the contents of linked websites cannot be imposed without reasonable indications that there has been a violation of law. Illegal links will be removed immediately at the time we get knowledge of them.

    ### Copyright

    Contents and compilations published on these websites by the providers are subject to German copyright laws. Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law require a written permission of the author or originator. Downloads and copies of these websites are permitted for private use only. The commercial use of our contents without permission of the originator is prohibited. Copyright laws of third parties are respected as long as the contents on these websites do not originate from the provider. Contributions of third parties on this site are indicated as such. However, if you notice any violations of copyright law, please inform us. Such contents will be removed immediately.

برای این فایل مجموعه ای از قراردادها وجود دارد:

  • کلیدهای سطح بالا باید زبان معتبر باشند (فقط در حال حاضر de و en)
  • کلیدها می توانند تو در تو باشند، به عنوان مثال، اشیا / آرایه ها مجاز هستند
  • وقتی یک کلید با یک ختم می شود $ امضا کنید که به عنوان Markdown تفسیر می شود (به طور خاص، MDX)

فایل MDX با چنین فایل سایدکار چگونه به نظر می رسد؟

export const meta = {
  title: 'Legal Disclaimer',
  legal: 'Legal Disclaimer',
};

<section className="container">
  {locale.content$}
</section>

حالا این جذاب به نظر می رسد!

برای ادغام بومی سازی هنوز باید کاری انجام دهیم. استفاده از را ببینید locale در کد بالا اما چگونه می شود locale منظره را در فایل وارد کنید؟ بالاخره ما آن را نمی بینیم!

معلوم شد که می توانیم کد تولید شده را توسط MDX دستکاری کنیم. بیایید با افزودن یک افزونه سفارشی به ابزار، کمی ابزار را پیکربندی کنیم recmaPlugins گزینه این به ما اجازه می دهد تا کنترل کاملی بر درخت نحو انتزاعی تولید شده (AST) داشته باشیم، که سپس برای تولید کد واقعی نشان دهنده فایل MDX استفاده می شود.

import codegen from 'vite-plugin-codegen';
import mdx from '@mdx-js/rollup';
import { resolve } from 'path';
import localize from './src/tools/localize.mjs';

export default {
  build: {
    assetsInlineLimit: 0,
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  plugins: [
    codegen(),
    mdx({
      recmaPlugins: [localize],
    }),
  ],
};

وارداتی localize تابع پلاگین واقعی است – که فقط عملکرد دیگری را ارائه می دهد – به اصطلاح ترانسفورماتور. ترانسفورماتور مسئول دستکاری AST است.

را transform تابع در قطعه بعدی، AST و فایل مجازی (یعنی نام، محتوا، …) فایل MDX را که در حال حاضر در حال مدیریت است، دریافت می کند.

بیایید قبل از اینکه به این کد بپردازیم، کد را ببینیم:

import { compileSync } from '@mdx-js/mdx';
import { loadLocale } from './localization.mjs';

function fromMarkdown(content) {
  let result = {};
  compileSync(content, {
    development: false,
    recmaPlugins: [
      () => (ast) => {
        const [, fn] = ast.body;
        result = {
          type: 'CallExpression',
          optional: false,
          callee: {
            ...fn,
            id: null,
          },
          arguments: [
            {
              type: 'ObjectExpression',
              properties: [],
            },
          ],
        };
      },
    ],
  });

  return result;
}

function getExpression(content, key) {
  switch (typeof content) {
    case 'object':
      if (Array.isArray(content)) {
        return {
          type: 'ArrayExpression',
          elements: content.map((item, i) => getExpression(item, `${i}`)),
        };
      }

      return {
        type: 'ObjectExpression',
        properties: Object.entries(content).map(([name, value]) => ({
          type: 'Property',
          key: {
            type: 'Identifier',
            name,
          },
          value: getExpression(value, name),
          kind: 'init',
        })),
      };
    case 'number':
    case 'boolean':
      return {
        type: 'Literal',
        value: content,
      };
    case 'string':
      if (key.endsWith('$')) {
        // uses potentially markdown
        return fromMarkdown(content);
      }

      return {
        type: 'Literal',
        value: content,
      };
    case 'undefined':
    default:
      return {
        type: 'Literal',
        value: null,
      };
  }
}

async function transform(ast, vfile) {
  const language = process.env.WEBSITE_LOCALE || 'en';

  const source = vfile.history[0];
  const locale = await loadLocale(source, language);

  const idx = ast.body.findLastIndex((m) => m.type === 'ImportDeclaration');
  const imprt = ast.body.find((node) => node.type === 'ImportDeclaration' && node.source.value === 'react/jsx-runtime');

  const requiredImports = [
    ['_jsx', 'jsx'],
    ['_jsxs', 'jsxs'],
    ['_Fragment', 'Fragment'],
  ];

  for (const requiredImport of requiredImports) {
    const [alias, original] = requiredImport;

    if (!imprt.specifiers.find((node) => node.type === 'ImportSpecifier' && node.imported.name === original)) {
      imprt.specifiers.push({
        type: 'ImportSpecifier',
        imported: {
          type: 'Identifier',
          name: original,
        },
        local: {
          type: 'Identifier',
          name: alias,
        },
      });
    }
  }

  ast.body.splice(idx + 1, 0, {
    type: 'VariableDeclaration',
    kind: 'const',
    declarations: [
      {
        type: 'VariableDeclarator',
        id: {
          type: 'Identifier',
          name: 'locale',
        },
        init: getExpression(locale, language),
      },
    ],
  });
}

const plugin = () => transform;

export default plugin;

به نظر پیچیده تر از آن چیزی است که باید باشد! در پایان، همه چیز به تغییر AST خلاصه می شود

  • بدست آوردن locale برای سند
  • وارد کردن نام بالقوه گمشده (برای پشتیبانی کامل از Markdown / محتوای تولید شده، در صورت وجود) در react/jsx-runtime واردات
  • درج کنید locale اعلامیه – اولین چیز بعد از واردات (از این طریق می توانید استفاده کنید locale تقریباً در همه جای سند
  • مقداردهی اولیه locale متغیر شیئی باشد که می شناسیم. به استثنای رشته های Markdown (پسوند با $): اینها به یک IIFE تبدیل می شوند که کد MDX اصلی را حفظ می کند

یک راه خوب برای دیدن کارهایی که انجام می دهیم را می توان با استفاده از زمین بازی MDX مشاهده کرد.

کد کامپایل شده

همانطور که می بینید کد کامپایل شده کمی عجیب است، اما می تواند به خوبی به فایل MDX اصلی ما نگاشت شود.

در مقابل، دیدگاهی که در واقع بیشتر به آن علاقه داریم، مرحله قبل از قرار گرفتن کد در آن حالت کامپایل شده است. این نمای “east” در زمین بازی است (یعنی AST صحنه ESTree).

نمایندگی ESTree

چیزی که می خواهیم با افزونه خود به دست آوریم این است که شروع را دستکاری می کنیم. متأسفانه، به طور مستقیم در کد MDX ما باید از یک استفاده کنیم export برای آن، اما با دستکاری مستقیم AST ما در واقع به آن نیاز نداریم.

مفهوم کاری که می خواهیم انجام دهیم

برای به دست آوردن locale متغیر / شی ما loadLocale عملکرد از یک ماژول اختصاصی

این ماژول انجام می دهد:

  1. ترجمه های خاص محلی / فایل را بخوانید و تجزیه کنید
  2. ترجمه های زبان خاص فایل محلی را دریافت کنید
  3. ترجمه های جهانی را بخوانید و تجزیه کنید
  4. ترجمه های زبان خاص فایل سراسری را دریافت کنید
  5. ادغام با ترجمه های محلی که بالاتر در نظر گرفته می شوند

برای به دست آوردن ترجمه های خاص زبان، ما یک ترجمه پایه را انتخاب می کنیم (در مورد ما en) و از طریق شی “راه بروید”. زبان فعلی (بگذریم de) سپس یا ترجمه پایه را لغو می کند یا فقط از قطعه به دست آمده از ترجمه پایه استفاده می کند.

شما می توانید این فرآیند را به عنوان یک ادغام عمیق با ترجمه اصلی که ترجمه اصلی است در نظر بگیرید.

import { readFile } from 'fs/promises';
import { resolve } from 'path';
import { parse } from 'yaml';

async function getLocalization(path) {
  try {
    const content = await readFile(path, 'utf8');
    return parse(content);
  } catch (e) {
    console.error('Error reading YAML file:', e);
    return {};
  }
}

function mergeLocale(result, baseLocale, newLocale) {
  if (!newLocale) {
    Object.assign(result, baseLocale);
  } else {
    Object.entries(baseLocale).forEach(([name, value]) => {
      const c = newLocale[name];

      if (typeof value === 'object') {
        mergeLocale((result[name] = {}), value, c || {});
      } else if (typeof c === 'string' || c) {
        result[name] = c;
      } else {
        result[name] = value;
      }
    });
  }

  return result;
}

function getLocale(locales, lang) {
  const baseLocale = locales.en || {};

  if (lang !== 'en' && lang in locales) {
    const newLocale = locales[lang] || {};
    return mergeLocale({}, baseLocale, newLocale);
  }

  return baseLocale;
}

export async function loadLocale(source, language) {
  if (source.endsWith('.mdx')) {
    const globalFn = resolve(import.meta.dirname, '..', 'global.yml');
    const targetFn = source.replace('.mdx', '.yml');

    const globals = await getLocalization(globalFn);
    const locales = await getLocalization(targetFn);

    return {
      ...getLocale(globals, language),
      ...getLocale(locales, language),
      language,
    };
  }

  return { language };
}

ترجمه های سراسری در پوشه بالا قرار می گیرند pages. نام فایل است global.yml.

برای global.yml ما فقط برخی از اصطلاحات ترجمه بسیار رایج را ذخیره کرده ایم. یک مثال:

de:
  germany: Deutschland
en:
  germany: Germany

زمانی که ترجمه‌های جدید می‌آیند، فقط می‌توانید یک فایل را تغییر دهید (یا فقط تغییر ساختار در یک مکان مستقل از تغییرات زبان) کار بزرگی است. برای ما این بسیار مهم بود و ساختار جدید نشان دهنده آن است.

نتیجه گیری

با رویکرد جدید، نوشتن صفحات بسیار آسان تر از قبل است. همچنین، از آنجایی که ما اکنون یک سیستم ترجمه ثابت داریم، نیازی به کپی کردن صفحات یا ارائه سیستم پیچیده برای اجزای میانی نداریم. همه اینها قبلاً در زمان کامپایل در ساختار داده شده انجام شده است.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا