การใช้ msgctxt: การเพิ่มบริบทให้กับการแปล Gettext

คุณแปลคำภาษาอังกฤษว่า "Book" เป็นภาษาเยอรมัน และนักแปลของคุณก็ส่งคำว่า "Buch" กลับมาอย่างมั่นใจ สามสัปดาห์ต่อมา มีรายงานข้อผิดพลาดเข้ามา: ปุ่ม "Book a table" บนแบบฟอร์มการจองของคุณตอนนี้อ่านว่า "Buch a table" ซึ่งเป็นคำนาม ไม่ใช่คำกริยา คำศัพท์ถูกต้อง แต่ขาดบริบท นี่เป็นหนึ่งในความล้มเหลวที่พบบ่อยที่สุดในการแปลซอฟต์แวร์ และวิธีแก้ปัญหานี้มีอยู่ใน Gettext มานานหลายทศวรรษแล้ว นั่นคือ msgctxt
หากคุณเคยส่งงานแปลที่สตริงภาษาอังกฤษเดี่ยวๆ ถูกต้องในที่หนึ่ง แต่กลับไร้ความหมายในอีกที่หนึ่ง นั่นหมายความว่าคุณได้เจอเข้ากับปัญหาความกำกวม คำต้นฉบับเดียวกันสามารถแปลได้แตกต่างกันไปขึ้นอยู่กับบริบทที่ปรากฏ หากไม่มี msgctxt นักแปล (ไม่ว่าจะเป็นมนุษย์หรือ AI) ก็ไม่สามารถแยกแยะกรณีเหล่านี้ได้ เพราะสิ่งที่พวกเขาเห็นมีเพียงสตริงเปล่าๆ อย่าง msgid "Book" คู่มือนี้จะอธิบายว่า msgctxt ทำงานอย่างไรใน Gettext, วิธีการทำเครื่องหมายในซอร์สโค้ดของคุณด้วย _x() และ _ex(), วิธีที่มันปรากฏในไฟล์ .po ของคุณ และเหตุใดไปป์ไลน์การแปลที่เข้าใจบริบทจึงเป็นวิธีที่น่าเชื่อถือเพียงวิธีเดียวที่จะทำให้สตริงเหล่านี้ถูกต้องในวงกว้าง
ปัญหาความกำกวม: หนึ่งคำ หลายความหมาย
ภาษามนุษย์เต็มไปด้วยคำที่รวมตัวกันเป็นโทเค็นภาษาอังกฤษเดียว แต่สามารถแยกออกเป็นหลายคำในภาษาอื่น นักแปลจะเห็นเพียงสตริงต้นฉบับเท่านั้น ดังนั้นเมื่อองค์ประกอบ UI สองส่วนที่ไม่มีความสัมพันธ์กันใช้คำภาษาอังกฤษเดียวกัน พวกมันก็จะใช้ msgid เดียวกัน - และ Gettext ก็จะรวมพวกมันเป็นรายการเดียว
คำที่แยกความหมายเมื่อแปลข้ามภาษา
ลองพิจารณาตัวอย่างคลาสสิกสามประการ:
- "Book" - คำนาม (วัตถุบนชั้นวาง) เทียบกับคำกริยา ("Book a flight" - จองเที่ยวบิน) ภาษาเยอรมัน: Buch เทียบกับ buchen.
- "Post" - เผยแพร่เนื้อหา เทียบกับ ส่งจดหมาย ภาษาฝรั่งเศส: publier เทียบกับ courrier.
- "Order" - ลำดับ/การจัดเรียง เทียบกับ การซื้อ ภาษาสเปน: orden เทียบกับ pedido.
ในภาษาอังกฤษ โค้ดของคุณจะใช้สตริงตัวอักษรเดียวกันในทั้งสองตำแหน่ง:
// Somewhere in a library catalog screen
echo __( 'Book', 'mytextdomain' );
// Somewhere in a reservation form button
echo __( 'Book', 'mytextdomain' );
สตริงที่เหมือนกันยุบรวมเป็นรายการเดียวได้อย่างไร
เมื่อคุณรัน wp i18n make-pot หรือ xgettext การเรียกใช้ทั้งสองจะยุบรวมเป็นรายการเดียวในไฟล์ .po:
#: catalog.php:42 reservation-form.php:88
msgid "Book"
msgstr ""
มี msgstr เพียงรายการเดียวที่ต้องกรอก ไม่ว่านักแปลจะเลือกอะไร หนึ่งในสองหน้าจอก็จะผิด นักแปลไม่สามารถแก้ไขปัญหานี้ได้แม้ว่าพวกเขาจะเข้าใจปัญหา เพราะรูปแบบนั้นไม่อนุญาตให้พวกเขาระบุคำแปลสองรายการสำหรับ msgid เดียวกัน ความกำกวมนี้ฝังอยู่ในข้อมูล
msgctxt คืออะไรและช่วยแยกแยะความกำกวมได้อย่างไร?
msgctxt คือสตริงบริบทที่แนบมากับรายการแปล คำตอบสั้นๆ คือ มันเพิ่มคีย์ตัวที่สองควบคู่ไปกับ msgid ดังนั้น Gettext จึงถือว่า (context, msgid) เป็นคู่ที่ไม่ซ้ำกัน สองรายการที่มี msgid เดียวกัน แต่มี msgctxt ที่แตกต่างกัน จะกลายเป็นสองสตริงที่แยกจากกันและสามารถแปลได้อย่างอิสระ
ในโมเดลการค้นหาของ Gettext โดยปกติแล้วการแปลจะใช้เพียงสตริงต้นฉบับเป็นคีย์เท่านั้น แต่ด้วย msgctxt คีย์จะกลายเป็นการรวมกันของบริบทและสตริงต้นฉบับ นั่นคือกลไกทั้งหมด และเป็นเหตุผลว่าทำไมมันถึงทำงานได้อย่างราบรื่น: คุณไม่ได้เปลี่ยนข้อความที่แสดง แต่เปลี่ยนเฉพาะคีย์การค้นหาภายในเท่านั้น
บรรทัด msgctxt ในไฟล์ .po
ในไฟล์ .po บริบทจะปรากฏในบรรทัดของตัวเอง เหนือ msgid โดยตรง:
#: catalog.php:42
msgctxt "noun"
msgid "Book"
msgstr "Buch"
#: reservation-form.php:88
msgctxt "verb"
msgid "Book"
msgstr "Reservieren"
ตอนนี้มีสองรายการ พวกมันมีค่า msgid ที่เหมือนกัน แต่มีค่า msgctxt ที่แตกต่างกัน ดังนั้นแต่ละรายการจึงมี msgstr ของตัวเอง แคตตาล็อกจะแสดงผลเป็น Buch; ปุ่มจองจะแสดงผลเป็น Reservieren รันไทม์จะเลือกอันที่ถูกต้อง เพราะโค้ดของคุณบอกว่าควรใช้บริบทใด
เปรียบเทียบกับเวอร์ชันที่มีรายการเดียวที่ผิดพลาดข้างต้น ความแตกต่างไม่ใช่คุณภาพของการแปล แต่เป็นว่าโมเดลข้อมูลอนุญาตให้มีคำตอบที่ถูกต้องหรือไม่
การทำเครื่องหมายในโค้ดของคุณ: _x() และ _ex()
ในการส่ง msgctxt ไปยังเทมเพลต .po ของคุณ คุณต้องเรียกใช้ฟังก์ชันการแปลที่เข้าใจบริบท แทนที่จะเป็นฟังก์ชันธรรมดา ใน WordPress ฟังก์ชันเหล่านี้คือ _x() และ _ex() ซึ่งเป็นคู่แฝดที่แสดงผลออกไป; ใน Gettext ดิบ ฟังก์ชันเหล่านี้จะแมปกับ pgettext()
การเลือกระหว่าง _x() และ _ex()
ฟังก์ชัน __() แบบธรรมดาจะรับสตริงและ text domain ส่วนเวอร์ชันที่มีบริบทจะแทรกบริบทเป็นอาร์กิวเมนต์ที่สอง:
// Without context - ambiguous
__( 'Book', 'mytextdomain' );
// With context - the second argument is the msgctxt
_x( 'Book', 'noun', 'mytextdomain' ); // returns the translated string
_ex( 'Book', 'verb', 'mytextdomain' ); // echoes it directly
// Real-world disambiguation
_x( 'Post', 'verb: publish content', 'mytextdomain' );
_x( 'Post', 'noun: mail item', 'mytextdomain' );
_x( 'Order', 'sequence or sorting', 'mytextdomain' );
_x( 'Order', 'customer purchase', 'mytextdomain' );
อาร์กิวเมนต์ที่สองคือป้ายกำกับบริบท มันจะไม่แสดงให้ผู้ใช้ของคุณเห็นเลย มีอยู่เพื่อแยกแยะความกำกวมเท่านั้น ทั้งสำหรับการค้นหาของ Gettext และสำหรับผู้ที่ทำการแปล (หรือเครื่องมือใดๆ ที่ทำการแปล) เขียนบริบทให้สั้นและเป็นคำใบ้ที่อธิบายได้ เช่น 'noun', 'verb', 'button label', 'admin menu' บริบทที่คลุมเครือเช่น '1' นั้นถูกต้องทางเทคนิค แต่ไม่มีประโยชน์สำหรับนักแปล
เมื่อคุณสร้างเทมเพลตขึ้นมาใหม่ ตัวแยกข้อความจะจดจำการเรียกใช้เหล่านี้และปล่อยบรรทัด msgctxt เครื่องมืออย่าง Poedit และโปรแกรมแก้ไข PO อื่นๆ จะจัดกลุ่มรายการตามบริบทอย่างเห็นได้ชัด ทำให้นักแปลที่เป็นมนุษย์เห็นได้ทันทีว่า "Book (คำนาม)" และ "Book (คำกริยา)" เป็นงานสองอย่างที่แตกต่างกัน หากคุณยังคงกำลังตั้งค่าเครื่องมือสกัดข้อมูลหรือกำลังหาวิธีที่ตัวแปรโต้ตอบกับสตริงเหล่านี้ คู่มือของเราเกี่ยวกับการ แปลไฟล์ PO โดยไม่ทำให้ตัวแปรโค้ดเสียหาย ครอบคลุมขั้นตอนการทำงานโดยละเอียด
ทำไมนักแปลมือใหม่ – และเครื่องมือ AI จำนวนมาก – ถึงแปลผิดพลาดเรื่องนี้
นี่คือส่วนที่ไม่สบายใจ การเพิ่ม msgctxt ลงในแหล่งที่มาของคุณเป็นสิ่งจำเป็นแต่ยังไม่เพียงพอ บริบทจะช่วยได้ก็ต่อเมื่อผู้ที่ทำการแปลอ่านมันจริงๆ
โหมดความล้มเหลวจากการละทิ้งบริบท
สคริปต์การแปลแบบแบตช์ที่ไม่ซับซ้อนจะทำสิ่งนี้: มันจะวนซ้ำผ่านรายการต่างๆ ดึง msgid แต่ละรายการ ส่งไปยัง API การแปล และเขียนผลลัพธ์ลงใน msgstr มันไม่เคยมองที่บรรทัด msgctxt ดังนั้นแม้ว่าคุณจะเขียน _x( 'Book', 'noun' ) และ _x( 'Book', 'verb' ) อย่างระมัดระวัง สคริปต์ก็จะส่งคำขอที่เหมือนกันสองรายการ – "translate Book" – และวางคำตอบเดียวกันลงในทั้งสองรายการ ความพยายามในการทำเครื่องหมายของคุณทั้งหมดจะถูกทิ้งในขั้นตอนสุดท้าย
เครื่องมือแปล AI ทั่วไปจำนวนมากมีจุดบอดเดียวกัน พวกมันถูกสร้างมาเพื่อแปลข้อความ และ msgctxt เป็นเมตาดาตา ไม่ใช่ข้อความ หากเครื่องมือทำให้ไฟล์ .po ของคุณกลายเป็นรายการสตริงก่อนที่จะส่งไปยังโมเดล บริบทก็จะไปไม่ถึงพร้อมท์ของโมเดล โมเดลซึ่งขาดสัญญาณใดๆ จะใช้ความหมายที่พบบ่อยที่สุดทางสถิติของคำเป็นค่าเริ่มต้น – โดยปกติจะเป็นคำนามสำหรับ "Book", ความหมายของการเผยแพร่สำหรับ "Post" – และแปลกรณีส่วนน้อยผิดพลาดโดยไม่แสดงข้อผิดพลาด คุณจะไม่เห็นข้อผิดพลาด คุณจะเห็นรายงานข้อผิดพลาดจากผู้ใช้ชาวเยอรมันในอีกหลายสัปดาห์ต่อมา
บริบทและคำพหูพจน์มีสาเหตุหลักเดียวกัน
นี่คือจุดที่การจัดการคำพหูพจน์และบริบทมาบรรจบกัน เพราะทั้งสองอย่างขึ้นอยู่กับว่าเครื่องมือเข้าใจโครงสร้างของ Gettext แทนที่จะถือว่าไฟล์เป็นข้อความธรรมดา หากสตริงของคุณเกี่ยวข้องกับรูปแบบตามจำนวนด้วย ความเข้าใจในโครงสร้างเดียวกันนี้ก็มีความสำคัญ – เราจะอธิบายรายละเอียดใน การทำความเข้าใจคำพหูพจน์ใน Gettext
ไปป์ไลน์ที่เข้าใจบริบทใช้ msgctxt อย่างไร
ไปป์ไลน์การแปลที่เคารพ msgctxt จะทำตรงกันข้ามกับสคริปต์แบบธรรมดา มันจะแยกวิเคราะห์ไฟล์ .po เป็นข้อมูลที่มีโครงสร้าง เก็บแต่ละบริบทของรายการไว้กับสตริงต้นฉบับ และส่งบริบทนั้นไปยัง AI เป็นส่วนหนึ่งของพร้อมท์ – เพื่อให้โมเดลรู้ว่ากำลังแปล "Book" โดยเฉพาะในฐานะคำกริยา ไม่ใช่ในเชิงนามธรรม
บริบทในฐานะสัญญาณชั้นหนึ่ง
นี่คือแนวทางที่ SimplePoTranslate ใช้ในการแก้ปัญหานี้ AI ที่เข้าใจบริบทของ SimplePoTranslate อ่านบรรทัด msgctxt เป็นสัญญาณชั้นหนึ่ง: เมื่อสองรายการมี msgid ร่วมกัน แต่แตกต่างกันในบริบท พวกมันจะถูกแปลเป็นสตริงที่แตกต่างกันตามจริง และคำใบ้บริบทจะแจ้งการเลือกคำของโมเดล ผลลัพธ์คือ "Book (คำนาม)" จะกลับมาเป็น Buch ในขณะที่ "Book (คำกริยา)" จะกลับมาเป็น buchen หรือ reservieren - โดยอัตโนมัติ โดยที่คุณไม่ต้องแก้ไขคำที่กำกวมทุกคำด้วยตนเอง
ที่สำคัญไม่แพ้กัน ไปป์ไลน์ยังคงรักษาบรรทัด msgctxt ไว้ในผลลัพธ์ เครื่องมือที่อ่อนแอกว่าอาจตัดบริบทออกในระหว่างการวนซ้ำ ทำให้สองรายการของคุณยุบกลับมารวมกันเป็นหนึ่งอย่างเงียบๆ และนำความกำกวมกลับมาอีกครั้งในการรวมครั้งต่อไป การรองรับ Gettext เต็มรูปแบบ หมายความว่าบริบทจะยังคงอยู่ผ่านการแปล การคอมไพล์ .mo และการนำเข้าซ้ำในภายหลัง – ดังนั้นการแยกแยะความกำกวมของคุณจึงคงทน ไม่ใช่การแก้ไขด้วยตนเองเพียงครั้งเดียว เมื่อรวมกับการ ล็อกไวยากรณ์ (Syntax Locking) สำหรับตัวยึดตำแหน่งภายในสตริงเหล่านั้น คุณจะได้คำแปลที่ถูกต้องทั้งในด้านบริบทและโครงสร้างที่สมบูรณ์
คุณยังคงเป็นเจ้าของในการตัดสินใจทำเครื่องหมาย – มีเพียงคุณเท่านั้นที่รู้ว่า "Book" ที่กำหนดเป็นคำกริยา แต่เมื่อคุณใส่คำอธิบายประกอบในแหล่งที่มาของคุณด้วย _x() และ _ex() แล้ว ไปป์ไลน์ที่เข้าใจบริบทจะเปลี่ยนคำอธิบายประกอบนั้นให้เป็นการแปลที่ถูกต้องในทุกภาษาเป้าหมาย โดยไม่ต้องดูแลสตริงทีละสตริง
บทสรุป
ปัญหาความกำกวม – หนึ่งคำภาษาอังกฤษ หลายความหมาย – ไม่ใช่เรื่องแปลกที่คุณจะมองข้ามได้ แต่มันเป็นคุณสมบัติเชิงโครงสร้างของวิธีการที่ Gettext จัดเก็บสตริง วิธีแก้ปัญหาคือ msgctxt: ทำเครื่องหมายสตริงที่กำกวมในแหล่งที่มาของคุณด้วย _x() และ _ex() ให้ป้ายกำกับบริบทที่ชัดเจนแก่แต่ละส่วนที่ปรากฏ และให้ Gettext ใช้คู่ (context, msgid) เป็นคีย์ในการแปล ส่วนนั้นขึ้นอยู่กับคุณ และใช้เวลาเพียงไม่กี่นาที
ส่วนที่ยากกว่าคือการทำให้แน่ใจว่าขั้นตอนการแปลของคุณให้ความสำคัญกับบริบทนั้นจริง ๆ แทนที่จะทิ้งมันไป สคริปต์แบบธรรมดาและเครื่องมือ AI ที่แปลเฉพาะข้อความจะทิ้ง msgctxt และนำข้อผิดพลาดเดิมที่คุณพยายามป้องกันกลับมาอีกครั้ง ไปป์ไลน์ที่เข้าใจบริบทจะอ่านบริบท แปลแต่ละรายการที่แยกแยะความกำกวมได้อย่างถูกต้อง และรักษามันไว้ตลอดการคอมไพล์และการนำเข้าซ้ำ
พร้อมที่จะหยุดส่งงานแปลที่ผิดพลาดเพราะขาดบริบทแล้วหรือยัง? ลองใช้ SimplePoTranslate ฟรี — ไม่ต้องใช้บัตรเครดิต ระดับฟรีสามารถจัดการไฟล์
.poและ.potจริง พร้อมการรองรับบริบท msgctxt อย่างเต็มรูปแบบ ดังนั้นสตริงที่แยกแยะความกำกวมของคุณจะได้รับการแปลอย่างถูกต้องตั้งแต่ครั้งแรก