หลุมพราง¶
เว็บไซต์นี้ถูกแปลเป็นสามสิบห้าภาษา และทุกภาษาล้วนผลิตขึ้นด้วยการหมุนลูปที่เอกสารชุดนี้สอน นั่นเป็นคลังข้อความขนาดเล็กเมื่อวัดด้วยมาตรฐานของวงการ แต่ก็ยังมากพอจะเหยียบกับดักเกือบทุกอันที่ทำให้ i18n ยากกว่าที่ตาเห็น
แต่ละหัวข้อด้านล่างคือสิ่งที่เกิดผิดพลาดขึ้นจริงที่นี่ ตอนนั้นมันมีหน้าตาอย่างไร และเส้นแบ่งระหว่างสิ่งที่ไลบรารีตรวจให้คุณกับสิ่งที่ยังคงเป็นดุลยพินิจของคุณอยู่ตรงไหน
การเปลี่ยนชื่อตัวแปรทำให้ต้องแปลประโยคใหม่¶
msgid คือคีย์ของแคตตาล็อก และชื่อที่ถูกแทรกค่าอยู่ ข้างใน นั้น การย้ายค่าคงที่ตัวหนึ่งไปยังขอบเขตของโมดูลแล้วเปลี่ยนเป็นตัวพิมพ์ใหญ่ตามที่สไตล์ของ Python เรียกร้อง — จาก author เป็น AUTHOR — เปลี่ยน Copyright © 2026 {author} · MIT License ให้กลายเป็นข้อความที่ไม่มีแคตตาล็อกใดเคยเห็นมาก่อน คำแปลของบรรทัดนั้นในทุกภาษาย่อมต้องวนกลับเข้าวงจร fuzzy อีกรอบ เพียงเพราะการเปลี่ยนชื่อที่ไม่ได้เปลี่ยนสิ่งใดที่ผู้อ่านมองเห็นเลย
ไลบรารีจะไม่ห้ามคุณ: ทั้งสองการสะกดเป็นชื่อตัวยึดตำแหน่งที่ถูกต้องทั้งคู่ สิ่งที่ไลบรารีทำคือทำให้ชื่อนั้น คุ้มค่า แก่การปกป้อง — ส่วนแทรกค่าต้องเป็นชื่อธรรมดา ดังนั้นสิ่งที่ไปอยู่ในคีย์ของแคตตาล็อกจึงเป็นคำที่นักแปลอ่านออก ไม่ใช่นิพจน์
กรณีตรงกันข้ามปลอดภัยโดยโครงสร้าง conversion และ format spec ไม่ใช่ส่วนหนึ่งของ msgid ดังนั้นการปรับ {amount:,.2f} ให้เป็น {amount:,.0f} จึงไม่เปลี่ยนคีย์ใดและไม่ทำให้คำแปลที่ไหนเป็นโมฆะเลย
nplurals=2 ไม่ได้แปลว่าต้องมีสตริงสองแบบที่ต่างกัน¶
ภาษาตุรกี ฮังการี เปอร์เซีย และเบงกาลี ต่างประกาศรูปพหูพจน์ไว้สองรูป และในทั้งสี่ภาษานั้น สองรูปของข้อความที่มีจำนวนกำกับเป็น สตริงเดียวกัน อย่างชอบธรรม — คำนามคงรูปเอกพจน์เมื่อตามหลังตัวเลข ดังนั้น {n} sayfa จึงถูกต้องทั้งกับหนึ่งหน้าและสิบหน้า ผู้รีวิวที่ "แก้" ความซ้ำนี้คือคนที่ทำให้คำแปลพัง
ความผิดพลาดในทิศตรงข้ามก็เกิดง่ายพอ ๆ กัน รูปที่สามของภาษาลัตเวียมีอยู่สำหรับศูนย์เท่านั้น รูปที่สองของภาษาสโลวีเนียคือทวิพจน์ (dual) สำหรับสองหน่วยพอดี ส่วนรูปสุดท้ายของภาษาโรมาเนียต้องมีคำว่า de ซึ่งสองรูปแรกต้องไม่มี การเติมช่องเหล่านั้นด้วยรูปเอกพจน์กับรูปพหูพจน์ให้ผลเป็นแคตตาล็อกที่ผิดเฉพาะกับจำนวนที่ไม่มีใครทดสอบ
ที่แย่กว่านั้น ลำดับ ของช่องไม่ได้สื่อความหมาย ภาษาเวลส์จัดดัชนีห้ารูปของตนโดยให้ msgstr[0] เป็นรูปทั่วไปและ msgstr[1] เป็นรูปเอกพจน์ การไล่เติมไปตามลำดับที่ดูเป็นธรรมชาติจะวางรูปเอกพจน์ไว้ในที่ที่ข้อความซึ่งไม่มีจำนวนกำกับทุกข้อความจะไปเจอ
ไลบรารีไม่รับเรื่องเหล่านี้มาไว้กับตัวเองเลยสักข้อ และนั่นแหละคือประเด็น: กฎพหูพจน์ของภาษาปลายทางอยู่ในเฮดเดอร์ของแคตตาล็อกภาษานั้นเอง และกฎยูเนียน/อินเตอร์เซกชันยอมให้คำแปลมีรูปมากกว่าหรือน้อยกว่าต้นทางก็ได้ สิ่งที่ไลบรารีตรวจคือสิ่งเดียวที่ตรวจได้โดยไม่ต้องรู้จักภาษา — ว่าทุกรูปยังคงรักษาตัวยึดตำแหน่งที่มันต้องการไว้ครบ
การที่สองรูปเหมือนกันเป๊ะนั้นมีเหตุผล¶
ภาษาไอริชมีรูปพหูพจน์ห้ารูป และในรายงานการ build ของเว็บไซต์นี้ หลายรูปในนั้นสะกดเหมือนกัน นั่นไม่ใช่ความพลาดจากการคัดลอกและวาง: leathanach ขึ้นต้นด้วย l และการกลายเสียงต้นคำสองแบบที่ตัวเลขของภาษาไอริชกระตุ้นให้เกิดนั้น ไม่มีแบบใดถูกเขียนออกมาบน l เลย แต่ละรูปยังคงทำงานจริง — ก้านคำสลับไปมาระหว่าง leathanach กับ leathanaigh และจำนวนที่เกินสิบจะย้อนกลับไปใช้รูปเอกพจน์ — เพียงแต่ไม่มีคำนามที่แปลว่า "หน้า" คำใดจะแสดงความต่างนั้นออกมา
การตรวจใด ๆ ที่ติดธงว่ารูปซ้ำกันเป็นเรื่องน่าสงสัย ย่อมติดธงภาษาไอริชที่ถูกต้องไปด้วย ผู้ที่รีวิวเรื่องนี้ได้มีเพียงมนุษย์ที่รู้ภาษานั้นเท่านั้น
ข้อความหนึ่งสอดคล้องกับจำนวนได้เพียงจำนวนเดียว¶
รายงานการ build ของเว็บไซต์นี้บอกว่าเรนเดอร์ไปกี่หน้าและใช้เวลานานเท่าใด การเขียนมันเป็น "Rendered {n} pages in {seconds} seconds" ดูไม่มีพิษภัย แต่แปลไม่ได้: gettext เลือกหนึ่งรูปจากหนึ่งจำนวน และจำนวนนั้นคือ n คำว่า seconds จะต้องสอดคล้องกับตัวเลขที่กลไกพหูพจน์ไม่มีวันได้เห็น
ทางแก้คือทำให้ปริมาณที่สองเป็นสัญลักษณ์หน่วยแทนที่จะเป็นคำ และตัวสัญลักษณ์หน่วยเองก็เป็นสิ่งที่ต้องแปลตามท้องถิ่นด้วย: แคตตาล็อกของเว็บไซต์นี้มีทั้ง s, с, ث, שנ׳ และ mp และการจัดพิมพ์ของภาษาฝรั่งเศส สเปน และสวีเดนต้องการช่องว่างก่อนสัญลักษณ์ในที่ที่ภาษาอังกฤษไม่ใส่ ไม่มีเรื่องใดในนั้นเป็นธุระของไลบรารี — แต่การสังเกตว่าข้อความหนึ่งต้องการความสอดคล้องถึง สอง ชุดนั้นเป็นธุระ และเครื่องมือเดียวสำหรับมันคือการเขียนข้อความนั้นเสียใหม่
การแก้ประโยคภาษาอังกฤษคือการแก้ไวยากรณ์ของภาษาอื่น¶
หน้าแรกเคยเขียนว่า "all ten language editions" การตัดตัวเลขออก — การแก้เพียงคำเดียวในภาษาอังกฤษ ที่ทำไปเพราะตัวเลขนั้นล้าสมัยอยู่เรื่อย — เปลี่ยนประธานพหูพจน์ให้กลายเป็นเอกพจน์ ภาษาสเปน อิตาลี โปรตุเกส รัสเซีย ยูเครน กรีก ดัตช์ และฮีบรู ต้องปรับกริยาให้สอดคล้องกันใหม่ทั้งหมด และหลายภาษาต้องเปลี่ยนรูป participle ด้วย
การแก้ต้นฉบับที่อ่านดูเป็นเรื่องเล็กในภาษาอังกฤษไม่ใช่เรื่องเล็กในปลายน้ำ การติดธง fuzzy ซึ่งเป็นสิ่งที่ pybabel update ทำ คือกลไกที่ให้โอกาสนักแปลแต่ละคนได้สังเกตเห็น
ความต่างที่มองไม่เห็นรอดชีวิตจากการคัดลอกและวางทุกครั้ง¶
คู่มืออ้างข้อความวินิจฉัยที่มี (nаme) อยู่ข้างใน — นั่นเป็นการ escape โดยตั้งใจ เพราะอักขระที่มันชี้ถึงคือ а แบบซีริลลิกที่ไม่มีผู้อ่านคนใดแยกออกจากตัวละตินได้ นักแปลของเว็บไซต์นี้เปลี่ยน escape นั้นให้กลายเป็นอักขระจริงไปแล้ว ห้าครั้งด้วยกัน ใน 5 ภาษาที่ต่างกัน และทุกครั้งก็ได้หน้าเว็บที่ดูถูกต้องแต่ผิด
อันนี้ไลบรารีจับได้จริง และมันคือเหตุผลที่ข้อความวินิจฉัยมีรูปทรงอย่างที่เป็นอยู่: ตัวยึดตำแหน่งที่ตัวอักษรปนกันหลายระบบการเขียนจะถูกรายงานสองครั้ง ครั้งหนึ่งแบบอ่านได้ อีกครั้งแบบ escape เพราะรูปแบบที่ escape แล้วคือการสะกดเดียวที่แยกทั้งสองออกจากกันได้ no-break space ที่อยู่ในวงเล็บปีกกาก็ถูกพิมพ์ด้วย code point ด้วยเหตุผลเดียวกัน ตัวตรวจแคตตาล็อกจะปฏิเสธข้อความนั้นก่อนที่มันจะถูกจัดส่งออกไปได้
ไม่ว่างเปล่าไม่ได้แปลว่าแปลแล้ว¶
แคตตาล็อกที่ตั้งโครงไว้ด้วยการคัดลอก msgid ลงใน msgstr ผ่านการตรวจแบบไร้เดียงสาได้ทุกข้อ: ไม่มีอะไรว่าง ไม่มีอะไรเป็น fuzzy เซตของข้อความตรงกันเป๊ะ เว็บไซต์นี้เคยมีอยู่ภาษาหนึ่งที่จัดส่งออกไปในสภาพนั้นอยู่หลายชั่วโมง อีกภาษาหนึ่งก็เช่นกัน โดยแปดหน้าของมันเป็นสำเนาที่เหมือนซอร์สภาษาอังกฤษทุกไบต์ — ซึ่งผ่านการตรวจที่เปรียบเทียบบล็อกโค้ดระหว่างสองไฟล์ เพราะมันคือไฟล์เดียวกัน
ทั้งสองอย่างไม่ใช่สิ่งที่ไลบรารีการแปลจะมองเห็น และทั้งสองอย่างตรวจได้ในราคาถูก แต่ไม่ใช่ด้วยการบังคับให้ทุกรายการต้องต่างจากต้นทาง: OK ชื่อผลิตภัณฑ์ ชื่อบุคคล อักษรย่อ และตัวระบุในโค้ด ล้วนแปลออกมาเป็นตัวมันเอง และการตรวจที่ห้ามสิ่งนั้นจะให้ผลบวกลวงไปตลอดกาล
จงวัดเป็น อัตรา แทน โดยดูทั้งแคตตาล็อกหรือทั้งหน้า แล้วส่งเฉพาะค่าผิดปกติไปให้มนุษย์ดู การทดสอบของเว็บไซต์นี้เองทำแบบนั้นเป๊ะ ๆ: มันเปรียบเทียบบรรทัดร้อยแก้วของแต่ละภาษากับซอร์สภาษาอังกฤษ แล้วล้มเหลวเมื่อเหมือนกันเกิน 25% ภาษาปลอมนั้นอยู่ที่ 87% ส่วนคำแปลของจริงทุกภาษาอยู่ระหว่าง 4% ถึง 8% ซึ่งคือหางเล็ก ๆ ของบรรทัดที่บังเอิญตรงกันโดยชอบธรรม เช่น URL และเอาต์พุตของโปรแกรมที่ยกมา ประชากรสองกลุ่มนี้ห่างกันมากพอจนค่าขีดแบ่งไม่จำเป็นต้องแม่นยำ
สิ่งที่ถูกแปลไม่ได้มีเพียงแคตตาล็อก¶
ความล้มเหลวสองอย่างที่นี่ไม่เกี่ยวกับ gettext เลย
การแปลหัวข้อทำให้แองเคอร์ (anchor) ที่ถูกสร้างจากหัวข้อนั้นเปลี่ยนไป ลิงก์ข้ามหน้าทุกลิงก์ที่ชี้เข้าหัวข้อดังกล่าวจึงพัง — อย่างเงียบ ๆ และเฉพาะในภาษานั้น เว็บไซต์นี้ตรึงแองเคอร์ภาษาอังกฤษไว้กับทุกหัวข้อ และมีการทดสอบที่ดึงรายการที่คาดหวังออกมาจากหน้าภาษาอังกฤษ
และตัวสร้างเว็บไซต์มาพร้อมคำแปลส่วนติดต่อผู้ใช้สำหรับหกสิบแปดภาษา ซึ่งไม่รวมภาษาสวาฮีลีและภาษาไอริช เมื่อไม่มีคำแปลนั้น การ build จะไม่ถอยกลับ (degrade) ไปเป็นภาษาอังกฤษ แต่การ include เทมเพลตจะล้มเหลว และภาษานั้นก็ build ไม่ได้เลย ไฟล์สองไฟล์ของรีโพซิทอรีนี้เองมีอยู่เพื่ออุดช่องว่างดังกล่าว
เครื่องมือของคุณเองก็มีบั๊ก¶
ขั้นตอน CI ที่เอกสารชุดนี้แนะนำไว้สำหรับจับแคตตาล็อกที่ล้าสมัย — pybabel update --check — ทำงานนั้นไม่ได้เลยกับโปรเจกต์ใดก็ตามที่ใช้ pgettext หรือ npgettext บน Babel 2.18.0 มันรายงานว่าแคตตาล็อกทุกอันที่มี msgctxt ล้าสมัย ทุกครั้งที่รัน การเปรียบเทียบวิ่งผ่าน Catalog.is_identical ซึ่งค้นหาข้อความแต่ละรายการด้วยคีย์ที่ข้อความนั้นถูกเก็บไว้ — และสำหรับข้อความที่มีคอนเท็กซ์ คีย์นั้นคือคู่ (id, context) ซึ่ง Catalog.get ไม่รับ การค้นหาจึงคืนค่าว่างเปล่า และแคตตาล็อกก็ไม่มีทางเปรียบเทียบแล้วเท่ากันได้:
>>> from babel.messages.catalog import Catalog
>>> c = Catalog(locale="ja")
>>> c.add("Guide", "ガイド", context="navigation")
<Message 'Guide' (flags: [])>
>>> c.is_identical(c)
False
เรื่องนี้ถูกพบที่นี่จากการลองใช้มันจริง ถูกรายงานไปยังต้นทาง และการตรวจที่ใช้แทนกันได้อยู่ในหน้าการนำขึ้นใช้งานจริง
บทเรียนทั่วไปคือบทเรียนที่ฟังแล้วอึดอัด: ด่านที่แดงตลอดเวลาแย่กว่าการไม่มีด่านเลย เพราะทีมจะปิดมันทิ้ง จงตรวจให้แน่ใจว่าการตรวจใน CI ของคุณผ่านได้จริง ก่อนจะไว้ใจให้มันทำหน้าที่กั้นด้วยการตก
ไลบรารีนี้มีไว้เพื่ออะไร สรุปในบรรทัดเดียว¶
เนื้อหาส่วนใหญ่ของหน้านี้คือดุลยพินิจที่ไม่มีเครื่องมือใดรับช่วงต่อได้ สิ่งที่เครื่องมือ ทำได้ คือรับประกันว่าคำแปลจะเปลี่ยนโครงสร้างของประโยคที่มันแปลไม่ได้ — ทำค่าหล่นหายไม่ได้ เสกค่าขึ้นมาใหม่ไม่ได้ เปลี่ยนรูปแบบการแสดงผลของค่าไม่ได้ และเอื้อมเข้าไปในอ็อบเจกต์ของคุณก็ไม่ได้ — และบอกเรื่องนั้นออกมาได้ด้วยประโยคเดียวที่คนซึ่งต้องเป็นผู้แก้นำไปลงมือทำต่อได้ นั่นคือทั้งหมดที่ไลบรารีนี้สัญญาไว้ และส่วนที่เหลือของเว็บไซต์นี้คือวิธีที่มันรักษาสัญญานั้น