วิธีสร้างแอปหลายภาษา: การสาธิตด้วย PHP และ Gettext

เผยแพร่แล้ว: 2022-03-11

ไม่ว่าคุณจะกำลังสร้างเว็บไซต์หรือเว็บแอปพลิเคชันเต็มรูปแบบ การทำให้เข้าถึงได้กว้างขึ้นสำหรับผู้ชมในวงกว้าง มักต้องการให้มีให้บริการในภาษาและท้องถิ่นต่างๆ

ความแตกต่างพื้นฐานระหว่างภาษาของมนุษย์ส่วนใหญ่ทำให้สิ่งนี้เป็นเรื่องง่าย ความแตกต่างในกฎไวยากรณ์ ความแตกต่างของภาษา รูปแบบวันที่ และอื่นๆ รวมกันเพื่อทำให้การแปลเป็นภาษาท้องถิ่นเป็นความท้าทายที่น่ากลัวและไม่เหมือนใคร

ลองพิจารณาตัวอย่างง่ายๆ นี้

กฎการพหูพจน์ในภาษาอังกฤษค่อนข้างตรงไปตรงมา: คุณสามารถมีคำในรูปเอกพจน์หรือรูปพหูพจน์ของคำได้

ในภาษาอื่น เช่น ภาษาสลาฟ มีรูปพหูพจน์สองรูปนอกเหนือจากรูปเอกพจน์ คุณอาจพบภาษาที่มีรูปแบบพหูพจน์ทั้งหมดสี่ ห้า หรือหกรูปแบบ เช่น ในภาษาสโลวีเนีย ไอริช หรืออาหรับ

วิธีจัดระเบียบโค้ดของคุณ และวิธีออกแบบส่วนประกอบและอินเทอร์เฟซของคุณ มีบทบาทสำคัญในการกำหนดว่าคุณจะแปลแอปพลิเคชันของคุณเป็นภาษาท้องถิ่นได้ง่ายเพียงใด

Internationalization (i18n) ของ codebase ของคุณ ช่วยให้แน่ใจว่าสามารถปรับให้เข้ากับภาษาหรือภูมิภาคต่างๆ ได้อย่างง่ายดาย การทำให้เป็นสากลมักจะทำครั้งเดียว โดยเฉพาะอย่างยิ่งในช่วงเริ่มต้นของโครงการเพื่อหลีกเลี่ยงการเปลี่ยนแปลงอย่างมากในซอร์สโค้ดลงที่ถนน

วิธีสร้างแอปหลายภาษา: การสาธิตด้วย PHP และ Gettext

เมื่อ codebase ของคุณถูกทำให้เป็นสากลแล้ว การโลคัล ไลซ์เซชั่น (l10n) จะกลายเป็นเรื่องของการแปลเนื้อหาของแอปพลิเคชันของคุณเป็นภาษา/สถานที่เฉพาะ

จำเป็นต้องดำเนินการโลคัลไลเซชันทุกครั้งที่ต้องรองรับภาษาหรือภูมิภาคใหม่ นอกจากนี้ เมื่อใดก็ตามที่ส่วนหนึ่งของอินเทอร์เฟซ (ที่มีข้อความ) ได้รับการอัปเดต เนื้อหาใหม่จะพร้อมใช้งาน ซึ่งจะต้องแปลเป็นภาษาท้องถิ่น (เช่น แปล) เป็นภาษาท้องถิ่นที่รองรับทั้งหมด

ในบทความนี้ เราจะเรียนรู้วิธีทำให้ซอฟต์แวร์เป็นภาษาท้องถิ่นและแปลเป็นภาษาท้องถิ่นด้วย PHP เราจะดำเนินการตามตัวเลือกการใช้งานต่างๆ และเครื่องมือต่างๆ ที่พร้อมใช้งานเพื่อให้กระบวนการนี้ง่ายขึ้น

เครื่องมือสำหรับความเป็นสากล

วิธีที่ง่ายที่สุดในการทำให้ซอฟต์แวร์ PHP เป็นสากลคือการใช้ไฟล์อาร์เรย์ อาร์เรย์จะถูกเติมด้วยสตริงที่แปลแล้ว ซึ่งสามารถค้นหาได้จากภายในเทมเพลต:

 <h1><?=$TRANS['title_about_page']?></h1>

อย่างไรก็ตาม นี่เป็นวิธีที่แทบจะไม่แนะนำสำหรับโครงการที่จริงจัง เนื่องจากจะก่อให้เกิดปัญหาในการบำรุงรักษาตามมาอย่างแน่นอน ปัญหาบางอย่างอาจปรากฏขึ้นในช่วงเริ่มต้น เช่น การขาดการสนับสนุนการแก้ไขตัวแปรหรือการทำให้คำนามเป็นพหูพจน์ เป็นต้น

หนึ่งในเครื่องมือสุดคลาสสิก (มักใช้เป็นข้อมูลอ้างอิงสำหรับ i18n และ l10n) คือเครื่องมือ Unix ที่เรียกว่า Gettext

แม้ว่าจะย้อนหลังไปถึงปี 1995 แต่ก็ยังเป็นเครื่องมือที่ครอบคลุมสำหรับการแปลซอฟต์แวร์ที่ใช้งานง่ายเช่นกัน แม้ว่าการเริ่มต้นใช้งานค่อนข้างง่าย แต่ก็ยังมีเครื่องมือสนับสนุนที่ทรงพลัง

Gettext คือสิ่งที่เราจะใช้ในโพสต์นี้ เราจะนำเสนอแอปพลิเคชัน GUI ที่ยอดเยี่ยมที่สามารถใช้เพื่ออัปเดตไฟล์ต้นทาง l10n ของคุณได้อย่างง่ายดาย ดังนั้นจึงไม่จำเป็นต้องจัดการกับบรรทัดคำสั่ง

ห้องสมุดเพื่อทำให้สิ่งต่าง ๆ เป็นเรื่องง่าย

เว็บเฟรมเวิร์กและไลบรารี PHP หลักที่รองรับ Gettext

มีเว็บเฟรมเวิร์กและไลบรารี PHP หลักๆ ที่สนับสนุน Gettext และการใช้งาน i18n อื่นๆ บางตัวติดตั้งได้ง่ายกว่ารุ่นอื่นๆ หรือรองรับคุณสมบัติเพิ่มเติมหรือรองรับรูปแบบไฟล์ i18n ที่แตกต่างกัน แม้ว่าในเอกสารนี้ เราจะเน้นที่เครื่องมือที่มาพร้อมกับแกน PHP ต่อไปนี้คือรายการอื่นๆ ที่ควรค่าแก่การกล่าวถึง:

  • oscarotero/Gettext: รองรับ Gettext ด้วยอินเทอร์เฟซเชิงวัตถุ รวมถึงฟังก์ชันตัวช่วยที่ได้รับการปรับปรุง ตัวแยกไฟล์ที่ทรงพลังสำหรับรูปแบบไฟล์ต่างๆ (บางรูปแบบไม่รองรับโดยคำสั่ง gettext ) ยังสามารถส่งออกไปยังรูปแบบต่างๆ นอกเหนือจากไฟล์ .mo/.po ได้ ซึ่งจะมีประโยชน์หากคุณต้องการรวมไฟล์การแปลของคุณเข้ากับส่วนอื่นๆ ของระบบ เช่น อินเทอร์เฟซ JavaScript

  • symfony/translation: รองรับรูปแบบต่างๆ มากมาย แต่แนะนำให้ใช้ verbose XLIFF ไม่รวมฟังก์ชันตัวช่วยหรือตัวแยกในตัว แต่รองรับตัวยึดตำแหน่งโดยใช้ strtr() ภายใน

  • zend/i18n: รองรับไฟล์อาร์เรย์และ INI หรือรูปแบบ Gettext ใช้ชั้นแคชเพื่อหลีกเลี่ยงความจำเป็นในการอ่านระบบไฟล์ทุกครั้ง รวมถึงตัวช่วยดูและตัวกรองอินพุตที่ทราบถึงสถานที่และตัวตรวจสอบความถูกต้อง อย่างไรก็ตาม ไม่มีตัวแยกข้อความ

กรอบงานอื่น ๆ ยังรวมถึงโมดูล i18n แต่ไม่มีให้บริการนอกฐานรหัส:

  • Laravel: รองรับไฟล์อาร์เรย์พื้นฐาน ไม่มีตัวแยกอัตโนมัติ แต่มีตัวช่วย @lang สำหรับไฟล์เทมเพลต

  • Yii: รองรับการแปลอาร์เรย์ Gettext และฐานข้อมูล และรวมตัวแยกข้อความ สนับสนุนโดยส่วนขยาย Intl มีให้ตั้งแต่ PHP 5.3 และอิงตามโครงการ ICU ซึ่งช่วยให้ Yii สามารถเรียกใช้การแทนที่ที่มีประสิทธิภาพ เช่น การสะกดตัวเลข การจัดรูปแบบวันที่ เวลา ช่วงเวลา สกุลเงิน และลำดับ

หากคุณตัดสินใจเลือกไลบรารี่ที่ไม่มีตัวแยก คุณอาจต้องการใช้รูปแบบ Gettext เพื่อให้คุณสามารถใช้ Gettext toolchain ดั้งเดิม (รวมถึง Poedit) ตามที่อธิบายไว้ในส่วนที่เหลือของบท

การติดตั้ง Gettext

คุณอาจต้องติดตั้ง Gettext และไลบรารี PHP ที่เกี่ยวข้องโดยใช้ตัวจัดการแพ็คเกจ เช่น apt-get หรือ yum หลังจากติดตั้งแล้ว ให้เปิดใช้งานโดยเพิ่ม extension=gettext.so (Linux/Unix) หรือ extension=php_gettext.dll (Windows) ลงใน php.ini ของคุณ

ที่นี่เราจะใช้ Poedit เพื่อสร้างไฟล์การแปลด้วย คุณอาจจะพบมันในตัวจัดการแพ็คเกจของระบบของคุณ มีให้สำหรับ Unix, Mac และ Windows และสามารถดาวน์โหลดได้ฟรีบนเว็บไซต์เช่นกัน

ประเภทของไฟล์ Gettext

มีไฟล์สามประเภทที่คุณมักจะจัดการขณะทำงานกับ Gettext

ไฟล์หลักคือไฟล์ PO (Portable Object) และ MO (Machine Object) ไฟล์แรกคือรายการ "วัตถุที่แปล" ที่อ่านได้ และไฟล์ที่สองคือไบนารีที่เกี่ยวข้อง (จะแปลโดย Gettext เมื่อทำการแปลเป็นภาษาท้องถิ่น) นอกจากนี้ยังมีไฟล์ POT (เทมเพลต PO) ที่มีคีย์ที่มีอยู่ทั้งหมดจากไฟล์ต้นทางของคุณ และสามารถใช้เป็นแนวทางในการสร้างและอัปเดตไฟล์ PO ทั้งหมด

ไฟล์เทมเพลตไม่จำเป็น ขึ้นอยู่กับเครื่องมือที่คุณใช้ทำ l10n คุณจะใช้ได้ดีกับไฟล์ PO/MO เท่านั้น คุณจะมีไฟล์ PO/MO หนึ่งคู่ต่อภาษาและภูมิภาค แต่จะมีเพียง POT เดียวต่อโดเมน

การแยกโดเมน

มีบางกรณีในโครงการขนาดใหญ่ที่คุณอาจต้องแยกการแปลเมื่อคำเดียวกันสื่อความหมายที่แตกต่างกันในบริบทที่แตกต่างกัน

ในกรณีเหล่านี้ คุณจะต้องแยกออกเป็น "โดเมน" ต่างๆ ซึ่งโดยทั่วไปจะตั้งชื่อกลุ่มของไฟล์ POT/PO/MO โดยที่ชื่อไฟล์คือ โดเมนการแปล ดังกล่าว

โครงการขนาดเล็กและขนาดกลางมักจะใช้เพียงโดเมนเดียวเท่านั้น ชื่อของมันคือโดยพลการ แต่เราจะใช้ "หลัก" สำหรับตัวอย่างโค้ดของเรา

ในโครงการ Symfony ตัวอย่างเช่น โดเมนใช้เพื่อแยกการแปลสำหรับข้อความตรวจสอบความถูกต้อง

รหัสสถานที่

โลแคลเป็นเพียงโค้ดที่ระบุเวอร์ชันหนึ่งของภาษา มีการกำหนดตามข้อกำหนด ISO 639-1 และ ISO 3166-1 alpha-2: อักษรตัวพิมพ์เล็กสองตัวสำหรับภาษา ตามด้วยขีดล่างและอักษรตัวพิมพ์ใหญ่สองตัวที่ระบุรหัสประเทศหรือรหัสภูมิภาค

สำหรับภาษาที่หายากจะใช้ตัวอักษรสามตัว

สำหรับผู้พูดบางคน ส่วนของประเทศอาจดูเหมือนซ้ำซาก อันที่จริง บางภาษามีภาษาถิ่นในประเทศต่างๆ เช่น เยอรมันออสเตรีย (de_AT) หรือโปรตุเกสแบบบราซิล (pt_BR) ส่วนที่สองใช้เพื่อแยกความแตกต่างระหว่างภาษาถิ่น - เมื่อไม่ปรากฏ จะใช้เป็นภาษา "ทั่วไป" หรือ "ลูกผสม"

โครงสร้างไดเรกทอรี

ในการใช้ Gettext เราจะต้องยึดตามโครงสร้างเฉพาะของโฟลเดอร์

ขั้นแรก คุณจะต้องเลือกรูทตามอำเภอใจสำหรับไฟล์ l10n ของคุณในที่เก็บซอร์สของคุณ ภายในนั้น คุณจะมีโฟลเดอร์สำหรับแต่ละสถานที่ที่จำเป็น และโฟลเดอร์ "LC_MESSAGES" แบบตายตัวที่จะบรรจุคู่ PO/MO ของคุณทั้งหมด

โฟลเดอร์ LC_MESSAGES

แบบฟอร์มพหูพจน์

ดังที่เราได้กล่าวไว้ในบทนำ ภาษาต่างๆ อาจมีกฎการใช้พหูพจน์ต่างกัน อย่างไรก็ตาม Gettext ช่วยเราแก้ปัญหานี้ได้

เมื่อสร้างไฟล์ .po ใหม่ คุณจะต้องประกาศกฎการทำให้เป็นพหูพจน์สำหรับภาษานั้น และส่วนที่แปลเป็นพหูพจน์จะมีรูปแบบที่แตกต่างกันสำหรับกฎแต่ละข้อ

เมื่อเรียกใช้ Gettext ด้วยรหัส คุณจะต้องระบุตัวเลขที่เกี่ยวข้องกับประโยค (เช่น สำหรับวลี "คุณมีข้อความ n ข้อความ" คุณจะต้องระบุค่าของ n) และรูปแบบจะออกมาถูกต้อง ที่จะใช้ - แม้กระทั่งการใช้การแทนที่สตริงหากจำเป็น

กฎพหูพจน์ประกอบด้วยจำนวนกฎที่จำเป็นสำหรับการทดสอบบูลีนสำหรับแต่ละกฎ (อาจละเว้นการทดสอบได้ไม่เกินหนึ่งกฎ) ตัวอย่างเช่น:

  • ภาษาญี่ปุ่น: nplurals=1; plural=0; nplurals=1; plural=0; - กฎข้อเดียว: ไม่มีรูปพหูพจน์

  • ภาษาอังกฤษ: nplurals=2; plural=(n != 1); nplurals=2; plural=(n != 1); - กฎสองข้อ: ใช้รูปพหูพจน์เฉพาะเมื่อ n ไม่ใช่ 1 มิฉะนั้นให้ใช้รูปเอกพจน์

  • โปรตุเกสแบบบราซิล: nplurals=2; plural=(n > 1); nplurals=2; plural=(n > 1); - กฎสองข้อ ใช้รูปพหูพจน์เมื่อ n มากกว่า 1 เท่านั้น มิฉะนั้นให้ใช้รูปเอกพจน์

สำหรับคำอธิบายที่ลึกซึ้งยิ่งขึ้น มีบทแนะนำ LingoHub ที่ให้ข้อมูลออนไลน์

Gettext จะกำหนดกฎที่จะใช้โดยยึดตามหมายเลขที่ให้มา และจะใช้สตริงเวอร์ชันที่แปลเป็นภาษาท้องถิ่นที่ถูกต้อง สำหรับสตริงที่ต้องการการจัดการพหูพจน์ คุณจะต้องรวมประโยคที่แตกต่างกันสำหรับกฎพหูพจน์แต่ละกฎในไฟล์ .po

ตัวอย่างการใช้งาน

หลังจากทั้งหมดทฤษฎีนั้น มาปฏิบัติกันเล็กน้อย นี่คือข้อความที่ตัดตอนมาของไฟล์ .po (อย่าเพิ่งกังวลเกี่ยวกับไวยากรณ์มากเกินไป แต่ให้เข้าใจเนื้อหาโดยรวม):

 msgid "" msgstr "" "Language: pt_BR\n" "Content-Type: text/plain; charset=UTF-8\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" msgid "We're now translating some strings" msgstr "Nos estamos traduzindo algumas strings agora" msgid "Hello %1$s! Your last visit was on %2$s" msgstr "Ola %1$s! Sua ultima visita foi em %2$s" msgid "Only one unread message" msgid_plural "%d unread messages" msgstr[0] "So uma mensagem nao lida" msgstr[1] "%d mensagens nao lidas"

ส่วนแรกทำงานเหมือนส่วนหัว โดยที่ msgid และ msgstr ว่างไว้

มันอธิบายการเข้ารหัสไฟล์ รูปพหูพจน์ และอื่นๆ สองสามอย่าง ส่วนที่สองแปลสตริงอย่างง่ายจากภาษาอังกฤษเป็นภาษาโปรตุเกสแบบบราซิล และส่วนที่สามทำแบบเดียวกัน แต่ใช้ประโยชน์จากการแทนที่สตริงจาก sprintf ทำให้การแปลมีชื่อผู้ใช้และวันที่เข้าชม

ส่วนสุดท้ายคือตัวอย่างรูปแบบพหูพจน์ โดยแสดงเวอร์ชันเอกพจน์และพหูพจน์เป็น msgid ในภาษาอังกฤษ และคำแปลที่เกี่ยวข้องกันเป็น msgstr 0 และ 1 (ตามตัวเลขที่กำหนดโดยกฎพหูพจน์)

มีการใช้การแทนที่สตริงด้วยเช่นกัน ดังนั้นตัวเลขสามารถเห็นได้โดยตรงในประโยคโดยใช้ %d รูปพหูพจน์มักจะมีสอง msgid (เอกพจน์และพหูพจน์) ดังนั้นจึงขอแนะนำว่าอย่าใช้ภาษาที่ซับซ้อนเป็นแหล่งที่มาของการแปล

คีย์การแปลเป็นภาษาท้องถิ่น

อย่างที่คุณอาจสังเกตเห็น เราใช้ประโยคภาษาอังกฤษจริงเป็นรหัสต้นทาง msgid นั้นเหมือนกับที่ใช้ในไฟล์ .po ทั้งหมดของคุณ ซึ่งหมายความว่าภาษาอื่นๆ จะมีรูปแบบเดียวกันและฟิลด์ msgid เดียวกัน แต่บรรทัด msgstr ที่แปลแล้ว

เมื่อพูดถึงคีย์การแปล มีสองแนวทาง "ปรัชญา" มาตรฐานที่นี่:

1. msgstr เป็นประโยคจริง

ข้อดีหลักของวิธีนี้คือ:

  • หากมีบางส่วนของซอฟต์แวร์ที่ไม่ได้แปลในภาษาใดๆ คีย์ที่แสดงอยู่จะยังคงมีความหมายอยู่บ้าง ตัวอย่างเช่น หากคุณรู้วิธีการแปลจากภาษาอังกฤษเป็นภาษาสเปนแต่ต้องการความช่วยเหลือในการแปลภาษาฝรั่งเศส คุณอาจเผยแพร่หน้าใหม่ที่มีประโยคภาษาฝรั่งเศสหายไป และบางส่วนของเว็บไซต์จะแสดงเป็นภาษาอังกฤษแทน

  • นักแปลจะเข้าใจสิ่งที่เกิดขึ้นได้ง่ายขึ้นมาก และทำการแปลที่เหมาะสมตาม msgid

  • มันให้ l10n "ฟรี" แก่คุณสำหรับหนึ่งภาษา - ภาษาต้นทาง

ในทางกลับกัน ข้อเสียหลักคือ หากคุณต้องการเปลี่ยนข้อความจริง คุณต้องแทนที่ msgid เดียวกันในไฟล์ภาษาต่างๆ

2. msgstr เป็นคีย์ที่มีโครงสร้างไม่ซ้ำกัน

สิ่งนี้จะอธิบายบทบาทของประโยคในแอปพลิเคชันในลักษณะที่มีโครงสร้าง รวมถึงเทมเพลตหรือส่วนที่เป็นที่ตั้งของสตริงแทนที่จะเป็นเนื้อหา

นี่เป็นวิธีที่ยอดเยี่ยมในการจัดระเบียบโค้ด โดยแยกเนื้อหาข้อความออกจากตรรกะของเทมเพลต อย่างไรก็ตาม นั่นอาจสร้างปัญหาให้กับนักแปลที่อาจพลาดบริบท

จำเป็นต้องใช้ไฟล์ภาษาต้นฉบับเป็นพื้นฐานสำหรับการแปลอื่นๆ ตัวอย่างเช่น นักพัฒนาซอฟต์แวร์ควรมีไฟล์ "en.po" ซึ่งนักแปลจะอ่านเพื่อทำความเข้าใจว่าจะเขียนอะไรใน "fr.po"

การแปลที่หายไปจะแสดงปุ่มที่ไม่มีความหมายบนหน้าจอ (“top_menu.welcome” แทนที่จะเป็น “สวัสดี ผู้ใช้!” บนหน้าภาษาฝรั่งเศสที่ไม่ได้แปลดังกล่าว)

เป็นเรื่องที่ดีเพราะจะทำให้การแปลต้องเสร็จก่อนเผยแพร่ แต่ไม่ดีเพราะปัญหาการแปลจะแย่มากในอินเทอร์เฟซ อย่างไรก็ตาม ไลบรารีบางแห่งมีตัวเลือกในการระบุภาษาที่กำหนดเป็น "ทางเลือก" ซึ่งมีพฤติกรรมคล้ายกับแนวทางอื่น

คู่มือ Gettext ให้ความสำคัญกับแนวทางแรก เนื่องจากโดยทั่วไปแล้วจะง่ายกว่าสำหรับนักแปลและผู้ใช้ในกรณีที่เกิดปัญหา นั่นคือแนวทางที่เราจะใช้ที่นี่เช่นกัน

อย่างไรก็ตาม ควรสังเกตว่าเอกสารของ Symfony รองรับการแปลตามคำสำคัญ เพื่อให้สามารถเปลี่ยนแปลงการแปลทั้งหมดได้อย่างอิสระโดยไม่ส่งผลกระทบต่อเทมเพลตเช่นกัน

การใช้งานในชีวิตประจำวัน

ในแอปพลิเคชันทั่วไป คุณจะใช้ฟังก์ชัน Gettext บางอย่างขณะเขียนข้อความคงที่ในหน้าของคุณ

ประโยคเหล่านั้นจะปรากฏในไฟล์ .po รับการแปล คอมไพล์เป็นไฟล์ .mo แล้วใช้โดย Gettext เมื่อแสดงอินเทอร์เฟซจริง ให้มาเชื่อมโยงสิ่งที่เราได้พูดคุยกันในตัวอย่างทีละขั้นตอน:

1. ไฟล์เทมเพลตตัวอย่าง รวมถึงการเรียก gettext ที่แตกต่างกัน

 <?php include 'i18n_setup.php' ?> <div> <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1> <!-- code indented this way only for legibility → <?php if ($unread): ?> <h2> <?=sprintf( ngettext('Only one unread message', '%d unread messages', $unread), $unread )?> </h2> <?php endif ?> </div> <h1><?=gettext('Introduction')?></h1> <p><?=gettext('We\'re now translating some strings')?></p>
  • gettext() เพียงแค่แปล msgid เป็น msgstr ที่สอดคล้องกันสำหรับภาษาที่กำหนด นอกจากนี้ยังมีฟังก์ชันชวเลข _() ที่ทำงานในลักษณะเดียวกัน

  • ngettext() ทำเช่นเดียวกัน แต่มีกฎพหูพจน์

  • นอกจากนี้ยังมี dgettext() และ dngettext() ที่ให้คุณแทนที่โดเมนสำหรับการโทรครั้งเดียว (เพิ่มเติมเกี่ยวกับการกำหนดค่าโดเมนในตัวอย่างถัดไป)

2. ไฟล์ติดตั้งตัวอย่าง (i18n_setup.php ตามที่ใช้ด้านบน) เลือกสถานที่ที่ถูกต้องและกำหนดค่า Gettext

การใช้ Gettext เกี่ยวข้องกับโค้ดสำเร็จรูป แต่ส่วนใหญ่เกี่ยวกับการกำหนดค่าไดเร็กทอรีโลแคลและการเลือกพารามิเตอร์ที่เหมาะสม (โลแคลและโดเมน)

 <?php /** * Verifies if the given $locale is supported in the project * @param string $locale * @return bool */ function valid($locale) { return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es'); } //setting the source/default locale, for informational purposes $lang = 'en_US'; if (isset($_GET['lang']) && valid($_GET['lang'])) { // the locale can be changed through the query-string $lang = $_GET['lang']; //you should sanitize this! setcookie('lang', $lang); //it's stored in a cookie so it can be reused } elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) { // if the cookie is present instead, let's just keep it $lang = $_COOKIE['lang']; //you should sanitize this! } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // default: look for the languages the browser says the user accepts $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ['-' => '_']); }); foreach ($langs as $browser_lang) { if (valid($browser_lang)) { $lang = $browser_lang; break; } } } // here we define the global system locale given the found language putenv("LANG=$lang"); // this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instance setlocale(LC_ALL, $lang); // this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mo bindtextdomain('main', '../locales'); // indicates in what encoding the file should be read bind_textdomain_codeset('main', 'UTF-8'); // if your application has additional domains, as cited before, you should bind them here as well bindtextdomain('forum', '../locales'); bind_textdomain_codeset('forum', 'UTF-8'); // here we indicate the default domain the gettext() calls will respond to textdomain('main'); // this would look for the string in forum.mo instead of main.mo // echo dgettext('forum', 'Welcome back!'); ?>

3.เตรียมงานแปลรอบแรก

ข้อดีอย่างหนึ่งที่ Gettext มีเหนือแพ็คเกจ i18n เฟรมเวิร์กแบบกำหนดเองคือรูปแบบไฟล์ที่กว้างขวางและมีประสิทธิภาพ

บางทีคุณอาจกำลังคิดว่า “โอ้ มนุษย์ นั่นค่อนข้างยากที่จะเข้าใจและแก้ไขด้วยมือ อาร์เรย์ที่เรียบง่ายจะง่ายกว่า!” อย่าพลาด แอปพลิเคชันอย่าง Poedit พร้อมให้ความช่วยเหลือ - มากมาย คุณสามารถรับโปรแกรมได้จากเว็บไซต์ของพวกเขา ซึ่งฟรีและใช้ได้กับทุกแพลตฟอร์ม มันเป็นเครื่องมือที่ค่อนข้างง่ายในการทำความคุ้นเคย และเป็นเครื่องมือที่ทรงพลังมากในเวลาเดียวกัน - โดยใช้ฟีเจอร์ทั้งหมดที่ Gettext มีให้ เราจะทำงานที่นี่กับเวอร์ชันล่าสุด Poedit 1.8

ดูภายใน Poedit

ในการรันครั้งแรก คุณควรเลือก “ไฟล์ > ใหม่…” จากเมนู คุณจะถูกถามถึงภาษา เลือก/กรองภาษาที่คุณต้องการแปล หรือใช้รูปแบบที่เรากล่าวถึงก่อนหน้านี้ เช่น en_US หรือ pt_BR

การเลือกภาษา

ตอนนี้บันทึกไฟล์โดยใช้โครงสร้างไดเร็กทอรีที่เรากล่าวถึงเช่นกัน จากนั้น คุณควรคลิก “แยกจากแหล่งที่มา” และที่นี่ คุณจะกำหนดการตั้งค่าต่างๆ สำหรับงานการแยกและการแปล คุณจะพบสิ่งเหล่านี้ได้ในภายหลังผ่าน “แคตตาล็อก > คุณสมบัติ”:

  • พาธต้นทาง: รวมโฟลเดอร์ทั้งหมดจากโปรเจ็กต์ที่เรียก gettext() (และพี่น้อง) ซึ่งมักจะเป็นโฟลเดอร์เทมเพลต/มุมมองของคุณ นี่เป็นการตั้งค่าบังคับเท่านั้น

  • คุณสมบัติการแปล:

    • ชื่อโครงการและเวอร์ชัน ที่อยู่อีเมลของทีมและทีม: ข้อมูลที่เป็นประโยชน์ที่อยู่ในส่วนหัวของไฟล์ .po
    • รูปพหูพจน์: นี่คือกฎที่เรากล่าวถึงก่อนหน้านี้ คุณสามารถปล่อยให้มันมีตัวเลือกเริ่มต้นเป็นส่วนใหญ่ เนื่องจาก Poedit ได้รวมฐานข้อมูลที่มีประโยชน์ของกฎพหูพจน์สำหรับหลายภาษาแล้ว
    • ชุดอักขระ : UTF-8 โดยเฉพาะ
    • ชุดอักขระซอร์สโค้ด: ชุดอักขระที่ใช้โดย codebase ของคุณ - อาจเป็น UTF-8 เช่นกันใช่ไหม
  • คีย์เวิร์ดที่มา: ซอฟต์แวร์พื้นฐานรู้ว่า gettext() และการเรียกใช้ฟังก์ชันที่คล้ายคลึงกันนั้นมีลักษณะอย่างไรในภาษาการเขียนโปรแกรมหลายภาษา แต่คุณสามารถสร้างฟังก์ชันการแปลของคุณเองได้เช่นกัน ที่นี่คุณจะเพิ่มวิธีการอื่นๆ เหล่านั้น ซึ่งจะกล่าวถึงในภายหลังในส่วน "เคล็ดลับ"

หลังจากตั้งค่าคุณสมบัติเหล่านั้นแล้ว Poedit จะเรียกใช้การสแกนผ่านไฟล์ต้นทางของคุณเพื่อค้นหาการเรียกการแปลเป็นภาษาท้องถิ่นทั้งหมด หลังจากการสแกนทุกครั้ง Poedit จะแสดงสรุปสิ่งที่พบและสิ่งที่ถูกลบออกจากไฟล์ต้นทาง รายการใหม่จะว่างเปล่าในตารางการแปล ทำให้คุณสามารถป้อนเวอร์ชันที่แปลของสตริงเหล่านั้นได้ บันทึกและไฟล์ .mo จะถูกคอมไพล์ (อีกครั้ง) ลงในโฟลเดอร์เดียวกัน และ ด่วน! โครงการของคุณเป็นสากล!

โครงการที่เป็นสากล

Poedit ยังสามารถแนะนำการแปลทั่วไปจากเว็บและจากไฟล์ก่อนหน้า มันสะดวกมาก คุณแค่ต้องตรวจสอบว่ามันสมเหตุสมผลหรือไม่ และยอมรับมัน หากคุณไม่แน่ใจเกี่ยวกับการแปล คุณสามารถทำเครื่องหมายว่า Fuzzy และคำแปลจะแสดงเป็นสีเหลือง รายการสีน้ำเงินคือรายการที่ไม่มีการแปล

4. การแปลสตริง

ดังที่คุณอาจสังเกตเห็น สตริงที่แปลเป็นภาษาท้องถิ่นมีสองประเภทหลัก: สตริงธรรมดาและสตริงที่มีรูปพหูพจน์

คนธรรมดามีเพียงสองกล่อง: ต้นทางและสตริงที่แปลแล้ว ไม่สามารถแก้ไขสตริงต้นทางได้ เนื่องจาก Gettext/Poedit ไม่มีความสามารถในการแก้ไขไฟล์ต้นทางของคุณ แต่คุณจะต้องเปลี่ยนแหล่งที่มาและสแกนไฟล์อีกครั้ง ( เคล็ดลับ: หากคุณคลิกขวาที่บรรทัดการแปล ระบบจะแสดงคำใบ้พร้อมไฟล์ต้นฉบับและบรรทัดที่ใช้สตริงนั้น)

สตริงรูปแบบพหูพจน์ประกอบด้วยสองกล่องเพื่อแสดงสตริงต้นทางสองสตริง และแท็บเพื่อให้คุณสามารถกำหนดค่าฟอร์มสุดท้ายที่แตกต่างกันได้

การกำหนดค่าแบบฟอร์มขั้นสุดท้าย

ตัวอย่างสตริงที่มีรูปพหูพจน์บน Poedit แสดงแท็บการแปลสำหรับแต่ละอัน

เมื่อใดก็ตามที่คุณเปลี่ยนไฟล์ซอร์สโค้ดและจำเป็นต้องอัปเดตการแปล เพียงกดรีเฟรช แล้ว Poedit จะสแกนโค้ดอีกครั้ง ลบรายการที่ไม่มีอยู่ รวมรายการที่เปลี่ยนแปลงและเพิ่มรายการใหม่

Poedit อาจพยายามเดาคำแปลบางฉบับโดยอิงจากคำแปลอื่นๆ ที่คุณทำ การเดาเหล่านั้นและรายการที่เปลี่ยนแปลงจะได้รับเครื่องหมาย "คลุมเครือ" ซึ่งระบุว่าต้องตรวจสอบ โดยแสดงเป็นสีเหลืองในรายการ

นอกจากนี้ยังมีประโยชน์หากคุณมีทีมแปลและมีคนพยายามเขียนสิ่งที่พวกเขาไม่แน่ใจ เพียงทำเครื่องหมายว่าคลุมเครือแล้วคนอื่นจะตรวจสอบในภายหลัง

สุดท้าย ขอแนะนำให้ทำเครื่องหมายที่ “ดู > รายการที่ไม่ได้แปลก่อน” เนื่องจากจะช่วยให้คุณไม่ลืมรายการใดๆ จากเมนูนั้น คุณยังสามารถเปิดส่วนต่างๆ ของ UI ที่อนุญาตให้คุณทิ้งข้อมูลเชิงบริบทไว้สำหรับนักแปลหากจำเป็น

Tips & Tricks

เว็บเซิร์ฟเวอร์อาจลงเอยด้วยการแคชไฟล์ .mo ของคุณ

หากคุณใช้งาน PHP เป็นโมดูลบน Apache (mod_php) คุณอาจประสบปัญหากับการแคชไฟล์ .mo ซึ่งเกิดขึ้นในครั้งแรกที่มีการอ่าน จากนั้นหากต้องการอัปเดต คุณอาจต้องรีสตาร์ทเซิร์ฟเวอร์

ใน Nginx และ PHP5 มักใช้เวลาเพียงสองสามครั้งในการรีเฟรชหน้าเพื่อรีเฟรชแคชการแปล และใน PHP7 แทบไม่มีความจำเป็น

ไลบรารีมีฟังก์ชันตัวช่วยเพื่อให้โค้ดโลคัลไลเซชันสั้น

ตามที่หลายคนต้องการ มันง่ายกว่าที่จะใช้ _() แทน gettext() ไลบรารี i18n แบบกำหนดเองจำนวนมากจากเฟรมเวิร์กใช้สิ่งที่คล้ายกับ t() เช่นกัน เพื่อทำให้โค้ดที่แปลสั้นลง อย่างไรก็ตาม นั่นเป็นฟังก์ชันเดียวที่ใช้ทางลัด

คุณอาจต้องการเพิ่มโครงการอื่น ๆ เช่น __() หรือ _n() สำหรับ ngettext() หรืออาจเป็น _r( _r() แฟนซีที่จะเข้าร่วมการโทร gettext() และ sprintf() ไลบรารีอื่น ๆ เช่น Gettext ของ oscarotero ยังมีฟังก์ชันตัวช่วยเช่นนี้

ในกรณีเหล่านี้ คุณจะต้องสั่งโปรแกรมอรรถประโยชน์ Gettext เกี่ยวกับวิธีแยกสตริงออกจากฟังก์ชันใหม่เหล่านั้น อย่ากลัวเลย มันง่ายมาก เป็นเพียงฟิลด์ในไฟล์ .po หรือหน้าจอการตั้งค่าใน Poedit (ในตัวแก้ไข ตัวเลือกนั้นอยู่ใน “แคตตาล็อก > คุณสมบัติ > คีย์เวิร์ดแหล่งที่มา”)

ข้อควรจำ: Gettext รู้ฟังก์ชันเริ่มต้นสำหรับหลายภาษาอยู่แล้ว ดังนั้นอย่ากังวลหากรายการนั้นดูเหมือนว่างเปล่า คุณต้องระบุข้อกำหนดของฟังก์ชันใหม่ในรายการตามรูปแบบเฉพาะนี้:

  • หากคุณสร้างบางอย่างเช่น t() ที่ส่งคืนการแปลสำหรับสตริง คุณสามารถระบุเป็น t ได้ Gettext จะรู้ว่าอาร์กิวเมนต์ของฟังก์ชันเดียวคือสตริงที่จะแปล

  • หากฟังก์ชันมีอาร์กิวเมนต์มากกว่าหนึ่งอาร์กิวเมนต์ คุณสามารถระบุได้ว่าสตริงแรกคือสตริงใด และรูปแบบพหูพจน์ด้วย หากจำเป็น ตัวอย่างเช่น หากฟังก์ชันลายเซ็นของเราคือ __('one user', '%d users', $number) ข้อมูลจำเพาะจะเป็น __:1,2 ซึ่งหมายความว่ารูปแบบแรกเป็นอาร์กิวเมนต์แรก และรูปแบบที่สองคือ อาร์กิวเมนต์ที่สอง หากหมายเลขของคุณมาเป็นอาร์กิวเมนต์แรกแทน ข้อมูลจำเพาะจะเป็น __:2,3 ซึ่งระบุว่ารูปแบบแรกเป็นอาร์กิวเมนต์ที่สอง เป็นต้น

หลังจากรวมกฎใหม่เหล่านั้นในไฟล์ .po แล้ว การสแกนใหม่จะนำสตริงใหม่ของคุณมาให้คุณได้อย่างง่ายดายเหมือนเมื่อก่อน

ทำให้แอป PHP ของคุณมีหลายภาษาด้วย Gettext

Gettext เป็นเครื่องมือที่ทรงพลังมากสำหรับการทำให้โครงการ PHP ของคุณเป็นสากล นอกเหนือจากความยืดหยุ่นที่ช่วยให้รองรับภาษามนุษย์จำนวนมากแล้ว การรองรับภาษาโปรแกรมมากกว่า 20 ภาษายังช่วยให้คุณถ่ายทอดความรู้ของคุณเกี่ยวกับการใช้ PHP ไปยังภาษาอื่นๆ เช่น Python, Java หรือ C# ได้อย่างง่ายดาย

นอกจากนี้ Poedit ยังช่วยให้เส้นทางระหว่างโค้ดและสตริงที่แปลราบรื่นขึ้น ทำให้กระบวนการตรงไปตรงมาและง่ายต่อการติดตามมากขึ้น นอกจากนี้ยังสามารถปรับปรุงความพยายามในการแปลที่ใช้ร่วมกันด้วยการรวม Crowdin

เมื่อใดก็ตามที่เป็นไปได้ ให้พิจารณาภาษาอื่นๆ ที่ผู้ใช้ของคุณอาจพูดได้ นี่เป็นสิ่งสำคัญส่วนใหญ่สำหรับโครงการที่ไม่ใช่ภาษาอังกฤษ: คุณสามารถเพิ่มการเข้าถึงของผู้ใช้ได้หากคุณเผยแพร่เป็นภาษาอังกฤษและภาษาแม่ของคุณ

แน่นอนว่า ไม่ใช่ทุกโครงการที่ต้องการความเป็นสากล แต่การเริ่มต้น i18n ในช่วงเริ่มต้นของโครงการนั้นง่ายกว่ามาก แม้ว่าจะไม่จำเป็นในตอนแรก มากกว่าที่จะทำในภายหลังตามถนนหากมันกลายเป็นข้อกำหนดในเวลาต่อมา และด้วยเครื่องมืออย่าง Gettext และ Poedit ก็ง่ายกว่าที่เคย

ที่เกี่ยวข้อง: บทนำสู่ PHP 7: มีอะไรใหม่และมีอะไรหายไป