10 ข้อผิดพลาด i18n ที่พบบ่อยและวิธีหลีกเลี่ยง
เรียนรู้ข้อผิดพลาดด้าน i18n ที่พบบ่อยที่สุดที่นักพัฒนาทำ และวิธีแก้ไข ปรับปรุงคุณภาพการโลคาลายส์ของแอปของคุณด้วยแนวทางที่ดีที่สุดเหล่านี้.
i18n ดูเรียบง่าย — จนกว่าคุณจะพบว่าผู้ใช้ภาษาเยอรมันเห็นปุ่มตัดขาด, ผู้ใช้ภาษาญี่ปุ่นได้ส่วนประโยคที่ถูกตัด, และผู้ใช้อาราบิกเห็นเลย์เอาต์ที่เสียหาย นี่ไม่ใช่กรณีขอบเขต แต่เป็นผลลัพธ์ที่คาดการณ์ได้ของข้อผิดพลาดทั่วไปที่ทีมพัฒนาส่วนใหญ่ทำเมื่อเริ่ม localization ในคู่มือฉบับนี้เราจะผ่าน 10 ข้อผิดพลาด i18n ที่พบบ่อยที่สุด อธิบายว่าทำไมพวกเขาถึงเกิด และจะแสดงให้คุณเห็นทีละขั้นตอนว่าจะลบข้อผิดพลาดแต่ละข้ออย่างไร ไม่ว่าคุณจะสร้างแอปใหม่หรือปรับปรุงโปรเจ็กต์เดิม — หลีกเลี่ยงกับดักเหล่านี้จะช่วยคุณประหยัดสัปดาห์ของ debugging
ข้อผิดพลาด #1: สตริงที่ฮาร์ดโค้ด
ข้อผิดพลาด i18n พื้นฐานคือการใส่ strings ด้วยมือ (hardcoding) เกิดขึ้นเพราะนักพัฒนามุ่งเน้นที่ทำให้ฟีเจอร์ใช้งานได้ก่อน แล้ววางแผนว่าเดี๋ยวค่อยทำความสะอาด แต่แล้วความสะอาดก็ติดขัดมักจะไม่เกิดขึ้นและมี strings หลายพันอยู่ในหลายไฟล์
['ติดตั้ง i18n framework ของคุณก่อนเขียน UI สายแรก', 'ใช้ปลั๊กอิน Linter เพื่อค้นหาข้อความ hardcoded ใน templates และ components', 'ทำให้ keys za tafsiri ziwe descriptive na zishikiane na feature au page']
ตั้งค่าเฟรมเวิร์ก i18n ของคุณก่อนเขียนบรรทัด UI แรก
ใช้ปลั๊กอินลินต์เพื่อตรวจหาสตริงที่ฝังในเทมเพลตและคอมโพเนนต์
ให้กุญแจการแปลมีคำอธิบายชัดเจนและจัดเรียงตามฟีเจอร์หรือหน้า
สถานการณ์ทั่วไป
นักพัฒนาซอฟต์แวร์เขียนป้ายปุ่มไว้ภายใน JSX โดยตรง: <button>Submit Order</button>.
แอปถูกปล่อยเป็นภาษาอังกฤษและทำงานได้อย่างถูกต้อง หกเดือนต่อมา บริษัทขยายไปยังเยอรมนี
ทีมทดสอบ localization พบมากกว่า 2,000 strings ที่ hardcoded การปรับปรุงจะใช้เวลา 3 สัปดาห์และทำให้เกิดบั๊ก 47
ทำไมถึงเป็นปัญหา
ในฐานข้อมูลโค้ดที่พัฒนาแล้วStrings ที่ hardcode อาจมีเป็นพันๆ ไฟล์ การแยกออกภายหลังจะต้องแก้ไขทุกไฟล์ ทุกส่วน และเสี่ยงต่อการเกิด regressions ทุกที่
Strings ที่ถูก hardcode อยู่ในโค้ดต้นฉบับ, เทมเพลต หรือส่วนประกอบใดๆ และไม่สามารถดึงออก, แปล หรือเปลี่ยนแปลงขณะรันไทม์ได้โดยไม่แก้โค้ด
ผู้ใช้งานใน locale ที่ไม่ใช่ภาษาอังกฤษจะเห็นองค์ประกอบ UI ที่ไม่ได้รับการแปลผสมกับข้อมูลที่แปลแล้ว ซึ่งทำให้ภาพรวมดูสับสนและไม่เป็นมืออาชีพ
วิธีแก้ไข
ย้ายข้อความที่มองเห็นโดยผู้ใช้ทั้งหมดไปยังไฟล์ทรัพยากร
มั่นใจว่าเริ่มต้นกรอบงานแปล (เช่น next-intl, react-i18next, vue-i18n) ก่อนเขียนคอมโพเนนต์แรกของคุณ.
สร้างโครงสร้างไฟล์ทรัพยากร (เช่น messages/de.json) และอ้างอิง strings ทั้งหมดด้วยคีย์การแปลอย่าง t('checkout.submitButton').
เพิ่มกฎ Linting หรือ Pre-Commit Hook ที่ระบุ literal ของสตริงอย่างไม่ถูกต้องในส่วน UI
ข้อผิดพลาด #10: แปลทุกอย่างตามตัวอักษร
ไม่เนื้อหาทั้งหมดควรถูกแปล ชื่อแบรนด์ ชื่อบริษัททางกฎหมาย คำศัพท์ทางเทคนิค และชื่อผลิตภัณฑ์บางชนิดควรคงในภาษาเดิม การแปลมากเกินไปอาจก่อให้เกิดปัญหาทางกฎหมาย ความสอดคล้องของตรา และความสับสนของผู้ใช้งาน
["ดูแลพจนานุกรม 'ไม่แปล' และแบ่งปันกับนักแปลทุกราย", 'ใช้ส่วนที่ถูกล็อคหรื namespaces แยกสำหรับชื่อแบรนด์และศัพท์ทางกฎหมาย', 'จัดทำบันทึกบริบทเสมอสำหรับสตริงที่มีความหมายหลายทาง เพื่อป้องกันการแปลที่ผิด']
รักษาพจนานุกรม 'ไม่แปล' และแบ่งปันให้กับนักแปลทุกคน
ใช้ส่วนที่ถูกล็อกหรือ namespaces แยกสำหรับคำที่เกี่ยวกับแบรนด์และข้อกำหนดทางกฎหมาย
โปรดระบุคำอธิบายบริบทเสมอสำหรับสตริงที่มีหลายความหมาย เพื่อป้องกันการแปลผิด
สถานการณ์ทั่วไป
ไฟล์การแปลประกอบด้วยชื่อบริษัท 'CloudForge Inc.' และคำศัพท์ทางเทคนิค 'OAuth 2.0 Token' ในรูปแบบสตริงที่สามารถแปลได้ทั่วไป
ผู้แปลภาษาสเปนแปล 'CloudForge' เป็น 'ForjaNube' และ 'OAuth 2.0 Token' เป็น 'ficha OAuth 2.0'.
ผลลัพธ์: ผู้ใช้จะไม่ค้นหาบริษัทนี้ด้วยชื่อจริงของมัน และนักพัฒนาที่อ่านเอกสารภาษาสเปนจะสับสนกับศัพท์เทคนิคที่แปลออกมาไม่รู้จัก
ทำไมถึงเป็นปัญหา
ชื่อแบรนด์ที่แปลผิดหนึ่งชื่อในเอกสารทางกฎหมายอาจทำให้สัญญาเป็นโมฆะ การแก้ไขการแปลมากเกินไปจำเป็นต้องตรวจสอบคำทุกคำในทุกภาษา — เป็นงานที่ต้องใช้หลายสัปดาห์
หากข้อความทั้งหมดถูกส่งไปแปลโดยไม่มีบริบท นักแปลอาจแปลชื่อแบรนด์ ('Apple' → 'Apfel'), คำศัพท์ทางกฎหมาย ('GmbH' → 'LLC') หรือคำศัพท์ทางเทคนิคที่ควรคงไว้ในภาษาอังกฤษ
ผู้ใช้ไม่พบผลิตภัณฑ์ภายใต้ชื่อแบรนด์ที่พวกเขาคุ้นเคย เอกสารทางกฎหมายอ้างถึงบริษัทที่ผิด และเอกสารทางเทคนิคจะเข้าใจยากหากคำเช่น 'API Endpoint' ถูกแปล
วิธีแก้ไข
ทำเครื่องหมายเนื้อหาที่ไม่สามารถแปลได้อย่างชัดเจนและจัดทำบันทึกบริบทสำหรับนักแปล
สร้างพจนานุกรม 'ไม่แปล' ที่ระบุชื่อแบรนด์ ชื่อผลิตภัณฑ์ บุคคลตามกฎหมาย และศัพท์เฉพาะที่ต้องคงไว้โดยไม่เปลี่ยน
Tumia namespace tofauti au kunci maalum kwa yaliyomo yasiyotafsiriwa. Zana nyingi za i18n zinaunga mkono vipande vilivyofungwa ambavyo watafsaji hawawezi kuhariri.
เพิ่มคอมเมนต์/คำอธิบายสำหรับนักแปลในไฟล์การแปลของคุณ เพื่ออธิบายบริบท: 'นี่คือชื่อแบรนด์ — ไม่แปล' หรือ 'ศัพท์เฉพาะ — เก็บไว้เป็นภาษาอังกฤษ'
ข้อผิดพลาด #2: การต่อสตริง
การรวมคำเป็นประโยคจากชิ้นส่วนทำให้ความหมายในภาษาอังกฤษดูสมเหตุสมผล แต่ในภาษาอื่นไม่เป็นเช่นนั้น โครงสร้างประโยคและคำศัพท์ต่างกันมากระหว่างภาษา ทำให้การผูกข้อความเป็นประโยคไม่สามารถแปลได้
['อย่าสร้างประโยคโดยการเชื่อมต่อชิ้นส่วนที่แปลแล้ว', 'ใช้ตัวแปรแทนที่ชื่อ ('{name}') แทนตำแหน่ง ({0}) เพื่อความชัดเจน', 'ให้คอมเมนต์บริบทสำหรับนักแปลเพื่ออธิบายว่าตัวแปรแต่ละตัวประกอบด้วยอะไร']
ไม่ควรสร้างประโยคโดยการผูกข้อความที่แปลมา
ใช้ตัวแปรแทนที่ชื่อ ('{name}') แทนตำแหน่ง ({0}) เพื่อความชัดเจน
ให้คำอธิบายบริบทแก่ผู้แปล ซึ่งอธิบายว่าแต่ละ placeholder ประกอบด้วยอะไร
สถานการณ์ทั่วไป
นักพัฒนากำลังเขียน: 'You have ' + count + ' items in your ' + cartType + ' cart' — ทำงานได้อย่างสมบูรณ์ในภาษาอังกฤษ
ผู้แปลภาษาเยอรมันได้รับสามชิ้นส่วนแยกจากกันและไม่สามารถสร้างประโยคที่ถูกต้องทางไวยากรณ์ได้ เพราะลำดับคำต้องเปลี่ยนแปลง
ผลลัพธ์: ผู้ใช้งานชาวเยอรมันเห็น 'Sie haben 5 Artikel in Ihrem Warenkorb Standard' — ราบรื่นน้อยและไม่เป็นมืออาชีพ
ทำไมถึงเป็นปัญหา
แต่ละสตริงที่เชื่อมต่อกันเป็นระเบิดเวลาในตัวเอง เมื่อมี 20 ภาษาและ 50 สตริงที่เชื่อมกัน จะมีข้อผิดพลาดด้านไวยากรณ์ประมาณ 1,000 จุดที่ต้องแก้ด้วยมือ
การรวมข้อความอย่างเช่น 'ยินดีต้อนรับ ' + ชื่อผู้ใช้ + ', คุณมี ' + จำนวน + ' ข้อความใหม่' ต้องการลำดับคำเฉพาะ นักแปลจะได้รับชิ้นส่วนที่ไม่มีบริบท
ผู้ใช้เห็นประโยคที่ผิดไวยากรณ์ ในภาษาเยอรมัน กริยามักอยู่ท้ายประโยค ในภาษาอาหรับ โครงสร้างทั้งหมดกลับด้าน ผลลัพธ์อ่านเหมือนไร้สาระ
วิธีแก้ไข
ใช้ ICU MessageFormat ซึ่งรองรับกฎ CLDR แบบครบถ้วน
แทนที่การเชื่อมต่อด้วยคีย์ข้อความเดียวพร้อมตัวแปรแทนที่: 'cart.summary': 'คุณมี {count} รายการในรถเข็น '{cartType}''
ผู้แปลจัดหารูปแบบพหุลที่จำเป็นทั้งหมดสำหรับภาษา เขียน Polish: '{count, plural, one {# plik} few {# pliki} many {# plików} other {# pliku}}'.
นักแปลสามารถจัดเรียงใหม่ได้อิสระ: 'ในรถเข็น '{cartType}' ของคุณมี {count} รายการ' — ภาษาเยอรมันที่ถูกต้องตามไวยากรณ์
ข้อผิดพลาด #3: ละเลยการพหูพจน์
ภาษาอังกฤษมีสองรูปพหูพจน์: เอกพจน์และพหูพจน์ นักพัฒนามักคิดว่าทุกภาษาทำงานเหมือนกัน แต่ไม่ใช่ ภาษาโปแลนด์มี 4 รูปแบบ ภาษาอาหรับมี 6 และแม้ภาษาฝรั่งเศสก็มีการจัดการศูนย์แตกต่างจากอังกฤษ
['ใช้ ICU MessageFormat ตลอดเวลา หรือห้องสมุดที่เทียบเท่าสำหรับข้อความที่นับได้', 'อย่าพัฒนาโลจิกพหูพจน์เอง — เชื่อถือกฎ CLDR', 'ทดสอบการผันพหูพจน์ด้วยค่าตัวอย่าง 0, 1, 2, 5, 21 และ 100 เพื่อครอบคลุมทุกประเภท']
ใช้งาน ICU MessageFormat ตลอดเวลา หรือไลบรารีที่เทียบเท่าสำหรับเนื้อหาที่นับได้
อย่าคิดตรรกะรูปพหูพจน์เองเด็ดขาด — เชื่อกฎ CLDR
ทดสอบรูปพหูพจน์ด้วยค่าที่แตกต่างกัน เช่น 0, 1, 2, 5, 21 และ 100 เพื่อครอบคลุมทุกประเภท
สถานการณ์ทั่วไป
นักพัฒนากำลังเขียน: count + (count === 1 ? ' Datei' : ' Dateien') — ภาษาเยอรมันถูกต้อง
ผู้แปลต้องการ 4 รูปแบบ: 1 plik, 2-4 pliki, 5-21 plików, 22-24 pliki. เฉพาะ ternary ง่ายไม่สามารถถ่ายทอดได้
ผลลัพธ์: ผู้ใช้งานภาษาพลินิสท์เห็น '5 pliki' (รูปแบบผิด) แทน '5 plików' (รูปแบบถูกต้อง), และแอปดูเหมือนพัง
ทำไมเรื่องนี้ถึงเป็นปัญหา
ทุกคำนามที่นับได้ในแอปของคุณต้องมีการจัดการพหูพจน์ เมื่อมี 50 สตริงแบบนี้และ 20 ภาษา จะมี 1,000 กฎพหูพจน์ที่จัดการด้วยตนเองยาก
การตรวจสอบ if/else ง่ายๆ (count === 1 ? 'Datei' : 'Dateien') ใช้ได้กับเยอรมันและอังกฤษเท่านั้น CLDR กำหนดหกประเภทการพหุพจน์: zero, one, two, few, many และ other ภาษาแต่ละภาษาจะใช้ subset ต่างกัน
ผู้ใช้เห็นข้อความพหูพจน์ผิดพลาด เช่น '1 ข่าว' หรือรูปแบบพหูพจน์ที่ผิดทั้งหมด ในบริบททางการทำให้ความน่าเชื่อถือเสียหาย
วิธีแก้
ใช้ ICU MessageFormat ซึ่งรองรับกฎการพหุพจน์ของ CLDR ได้ครบถ้วน
กำหนดข้อความด้วย ICU ซินทAX: 'fileCount': '{count, plural, one {# Datei} other {# Dateien}}'.
ผู้แปลจัดหารูปแบบพหุลทั้งหมดที่จำเป็นสำหรับภาษาของพวกเขา ตัวอย่าง Polish: '{count, plural, one {# plik} few {# pliki} many {# plików} other {# pliku}}'.
ไลบรารี i18n ของคุณจะเลือกฟอร์มที่ถูกต้องโดยอัตโนมัติระหว่างรันไทม์ ตามกฎ CLDR ของ Locale ที่ใช้งานอยู่.
ข้อผิดพลาด #4: ความกว้างของส่วน UI ที่คงที่
นักออกแบบสร้างเลย์เอาต์ที่ละเอียดเป็นพิกเซลเป็นภาษาอังกฤษ และนักพัฒนาก็นำไปใช้งานด้วยความกว้างคงที่ แต่ข้อความที่แปลอาจยาวหรืสั้นมาก ภาษาเยอรมันจะยาวกว่าภาษาอังกฤษประมาณ 30% ในขณะที่ภาษาจีนอาจสั้นลงถึง 50%
['ไม่ควรใช้ความกว้างพิกเซลคงที่สำหรับองค์ประกอบที่มีข้อความที่แปลได้', 'วางแผนการขยายข้อความอย่างน้อย 40% เป็นพื้นฐาน — บางภาษาจะขยายมากกว่า', 'ใช้ CSS Flexbox หรือ Grid สำหรับเลย์เอาท์ที่ปรับตามความยาวของเนื้อหา']
ห้ามกำหนดความกว้างพิกเซลคงที่สำหรับองค์ประกอบที่มีข้อความที่แปลได้
ตั้งค่าการขยายข้อความเป็น 40% เป็นค่าพื้นฐาน — บางภาษาอาจขยายมากขึ้น
ใช้ CSS Flexbox หรือ Grid สำหรับเค้าโครงที่ปรับให้เข้ากับความยาวเนื้อหาที่แตกต่างกัน
สถานการณ์ทั่วไป
นักออกแบบสร้างแถบนำทางที่มี 5 ปุ่ม แต่ละปุ่มกว้าง 100 px — ดูดีบนภาษาอังกฤษ
การแปลภาษาเยอรมัน: 'Settings' เปลี่ยเป็น 'Einstellungen' (13 เทียบกับ 8 ตัวอักษร), 'Submit' เปลี่ยเป็น 'Absenden' (8 เทียบกับ 6 ตัวอักษร). แถบนำทางล้นออก.
ผลลัพธ์: บนอุปกรณ์มือถือ ปุ่มจะซ้อนทับกันหรือตัดข้อความ ทำให้การนำทางใช้งานไม่ได้สำหรับผู้ใช้ชาวเยอรมัน
ทำไมเรื่องนี้ถึงเป็นปัญหา
ทุกองค์ประกอบที่มีความกว้างคงที่เป็นจุดแตกหัก แอปทั่วไปมีปุ่มหลายร้อยปุ่ม ป้าย และการ์ดทั้งหมดต้องรองรับการขยายของข้อความ
คอนเทนเนอร์ที่มีความกว้างคงที่ (width: 120px) และปุ่มขนาดคงที่จะตัดออกหรือล้นเมื่อข้อความขยายออก CSS overflow: hidden จะซ่อนเนื้อหาอย่างเงียบๆ ในขณะที่ overflow: visible จะทำให้การจัดวางเสียหาย
ผู้ใช้เห็นป้ายชื่อถูกตัดทอนเช่น 'Einstellu...' แทน 'Einstellungen', หรือปุ่มทับซ้อนองค์ประกอบที่อยู่ติดกัน การดำเนินการที่สำคัญจะอ่านไม่ได้หรือติดคลิกไม่ได้
วิธีแก้
ออกแบบและนำเลย์เอาต์ยืดหยุ่นที่ปรับตามความยาวของเนื้อหาสำหรับทุก locale.
แทนที่ความกว้างคงที่ด้วย min-width, max-width และการจัดการแบบยืดหยุ่น ใช้ CSS Grid หรือ Flexbox เพื่อแจกสรรพื้นที่แบบไดนามิค
ตั้งค่าตัวคอนเทนต์ข้อความให้ขึ้นบรรทัด: ใช้ overflow-wrap: break-word และหลีกเลี่ยง white-space: nowrap สำหรับเนื้อหาที่สามารถแปลได้
ทดสอบ UI ของคุณด้วยการจำลองพหุภาษาแบบปลอมที่ยืดข้อความทั้งหมด 40% เพื่อจำลอง Worst Case — ก่อนที่คุณจะส่งข้อความไปยังนักแปล
ข้อผิดพลาด #5: การจัดรูปแบบวันที่และตัวเลข
ข้อมูลและตัวเลขดูเหมือนสากล แต่ 01/02/2025 หมายถึงวันที่ 2 มกราคมในสหรัฐอเมริกา และวันที่ 1 กุมภาพันธ์ในยุโรป เครื่องหมายจุลภาคและจุดทศนิยมเปลี่ยนความหมายของตัวเลข: 1,000.50 (สหรัฐอเมริกา) เทียบกับ 1.000,50 (เยอรมนี) การทำผิดพลาดทำให้เกิดความสับสน ข้อมูลผิดพลาด และความไม่ไว้วางใจ
['ไม่ฟอร์แมตข้อมูลหรือตัวเลขด้วยเทมเพลตสตริงด้วยตนเอง—ใช้ API ของ Intl เสมอ', 'เก็บข้อมูลทั้งหมดใน ISO 8601 และสกุลเงินในหน่วยที่เล็กที่สุด (เซนต์) ภายใน', 'ทดสอบด้วย Locales ที่ใช้สัญลักษณ์ทศนิยมต่างกัน ลำดับวันที่และระบบปฏิทินที่แตกต่างกัน']
อย่ากำหนดฟอร์แมตข้อมูลหรือจำนวนด้วยเทมเพลตสตริงด้วยตัวเอง — ใช้ Intl-APIs ตลอด
เก็บข้อมูลทั้งหมดใน ISO 8601 และสกุลเงินในหน่วยที่เล็กที่สุด (เซนต์) ภายในระบบ
ทดสอบกับ Locale ที่ใช้ตัวคั่นทศนิยมต่างกัน ลำดับวันที่และระบบปฏิทินที่แตกต่าง
สถานการณ์ทั่วไป
นักพัฒนารูปแบบวันที่เป็น MM/DD/YYYY และราคายัง $1,234.50 — ถูกต้องสำหรับผู้ใช้ชาวอเมริกัน.
ผู้ใช้ชาวเยอรมันเห็น 03/04/2025 และตีความว่าเป็น 3 เมษายน แทน 4 มีนาคม — การนัดหมายที่พลาดหรือการจองที่ผิดพลาด
ผลลัพธ์: ผู้ใช้จองเที่ยวบินในวันที่ไม่ถูกต้องและสงสัยเกี่ยวกับรูปแบบราคา ตั๋วสนับสนุนเพิ่มขึ้น 15% ในตลาดเยอรมนี
ทำไมเรื่องนี้ถึงเป็นปัญหา
รูปแบบวันที่และตัวเลขมีผลกับการแสดงข้อมูลทุกส่วนในแอปพลิเคชัน: ตาราง กราฟ แบบฟอร์ม ใบแจ้งหนี้ รายงาน การแก้ไขระดับโลกครอบคลุมหลายร้อยกรณี
สตริงฟอร์แมทที่ฮาร์ดโค้ด เช่น toLocaleDateString('en-US') หรือการจัดรูปแบบด้วย Template-Literals ละเว้น Locale ที่แท้จริงของผู้ใช้งาน แม้ Locale ถูกต้องแต่ระบบปฏิทินที่ผิด (Gregorian vs Hijri) ก็ทำให้เกิดปัญหา
ผู้ใช้อ่านข้อมูลผิดและป้อนข้อมูลในรูปแบบที่ผิด ผู้ใช้ในยุโรปที่เห็น 03/04/2025 อาจตีความว่านี่คือ 3 เมษายน แทน 4 มีนาคม — การนัดหมายที่พลาดหรือการจองที่ผิดพลาด
วิธีแก้ปัญหานี้
ใช้ API ของ Intl ที่มีอยู่ในระบบหรือไลบรารีที่รองรับ locale สำหรับวันที่ เวลา จำนวน และสกุลเงินทั้งหมด
แทนที่การจัดรูปแบบด้วยตนเองด้วย Intl.DateTimeFormat(locale) สำหรับวันที่ และ Intl.NumberFormat(locale, { style: 'currency', currency }) สำหรับราคา
เก็บข้อมูลในรูปแบบ ISO 8601 (YYYY-MM-DD) ภายในและฟอร์แมตเพื่อการแสดงผลด้วย Locale ของผู้ใช้
ทดสอบด้วย Locale ที่ใช้เครื่องหมายทศนิยมต่างกัน ลำดับวันที่แตกต่างกัน และระบบปฏิทินที่ต่างกัน
ข้อผิดพลาด #6: ลืมภาษา RTL
ภาษาเขียนจากขวาไปซ้าย (RTL) เช่น ภาษาอาหรับ ภาษาเฮบรู และเปอร์เซีย ถูกใช้งานโดยผู้คนมากกว่า 500 milyonคน อย่างไรก็ตาม แอปส่วนใหญ่ถูกออกแบบสำหรับการวางแนวจากซ้ายไปขวา (LTR) RTL-support ไม่ได้หมายถึงการกลับข้อความ — ทั้ง UI ต้องสะท้อน
['ตั้งแต่วันแรก ใช้คุณสมบัติ CSS ที่มีตรรกะเท่านั้น (inline-start/end, block-start/end)', 'ทดสอบแอปของคุณด้วย RTL (dir=rtl) บน HTML-Element ในทุกการทบทวนสปรินต์', 'สร้างรายการตรวจสอบ RTL สำหรับการนำทาง ไอคอน ฟอร์ม และตัวบ่งชี้ความคืบหน้า']
ตั้งแต่วันแรก ให้ใช้คุณสมบัติ CSS ที่สอดคล้องกับทิศทางตามภาษา (inline-start/end, block-start/end) เท่านั้น
ทดสอบแอปของคุณด้วย rtl บนองค์ประกอบ HTML ในทุกการทบทวนสปรินต์
สร้างรายการตรวจสอบ RTL สำหรับการนำทาง ไอคอน แบบฟอร์ม และการแสดงความก้าวหน้า
สถานการณ์ทั่วไป
นักพัฒนาซอฟต์แวร์ใช้ margin-left: 16px และ text-align: left ตลอดทั้งแอปพลิเคชัน — ปฏิบัติการ LTR มาตรฐาน
แอปเริ่มทำงานในซาอุดีอาระเบีย ปุ่มย้อนกลับชี้ไปข้างหน้า ช่องด้านข้างปรากฏที่ด้านที่ผิด และข้อมูลตัวเลขเรียงลำดับไม่ถูกต้อง
ผลลัพธ์: ผู้ใช้อาหรับออกจากแอปภายใน 30 วินาที ทีมต้องการ 4 สัปดาห์ในการ refactor CSS ด่วนเพื่อแก้ปัญหา
ทำไมเป็นปัญหา
การรองรับ RTL เกี่ยวข้องกับส่วนประกอบแต่ละส่วนของแอปพลิเคชัน การเพิ่ม RTL ภายหลังมักต้องเขียน CSS ใหม่ประมาณ 30-50% ของกฎทั้งหมด และตรวจสอบไอคอนและการออกแบบทุกส่วน
คุณสมบัติ CSS เช่น margin-left, padding-right, text-align: left และ float: left กำหนดทิศทางไว้ล่วงหน้า ไอคอนที่มีความหมายทางทิศทาง (ลูกศร, แถบความคืบหน้า) ชี้ไปในทิศทางที่ไม่ถูกต้อง ค่าขอบโค้ง (border-radius) ก็สะท้อนไปด้วย
ผู้ใช้ชาวอาหรับเห็นการนำทางในมุมที่ผิด กระบวนการความก้าวหน้าดำเนินไปทางทิศกลับ และข้อความที่ทับซ้อนกับส่วน UI แอปดูแปลกและใช้งานยาก
วิธีแก้ปัญหานี้
ใช้คุณสมบัติ CSS ที่มีตรรกะและทดสอบการออกแบบ RTLตั้งแต่ต้น
Tüm fiziksel CSS Özelliklerini mantıksal karşılıklarıyla değiştirin: margin-left → margin-inline-start, padding-right → padding-inline-end, text-align: left → text-align: start.
ตั้งค่า dir บน HTML-Root-Element ตาม Locale ที่ใช้งาน ใช้ pseudo-class CSS :dir(rtl) สำหรับ override RTL
ตรวจสอบไอคอนทั้งหมดว่ามีทิศทางหรือไม่ เปลี่ยนไอคอนที่มีทิศทางไปเป็นเวอร์ชันสะท้อนกลับ หรือใช้ CSS transform: scaleX(-1) ในบริบท RTL
ข้อผิดพลาด #7: รูปภาพที่มีข้อความ
การใส่ข้อความลงในภาพ — ไม่ว่าจะอยู่ใน Hero-Banner ปุ่ม อินโฟกราฟิก หรือภาพหน้าจอ — เป็นฝันร้ายในการ localization ภาพที่มีข้อความจะต้องสร้างใหม่สำหรับแต่ละภาษา ซึ่งจะทำให้ต้นทุนการออกแบบสูงขึ้นและปล่อยล่าช้า
['อย่านำข้อความที่สามารถแปลได้ไปฝังตรงๆ ในภาพราสเตอร์ (PNG, JPG)', 'ใช้ overlays ของข้อความ CSS บนภาพพื้นหลังสำหรับ Hero-Banner และ CTA', 'อัตโนมัติการสร้างภาพหน้าจอสำหรับ App Store รายการและหน้าเว็บไซต์การตลาด']
อย่าฝังข้อความที่สามารถแปลได้โดยตรงลงในภาพแรสเตอร์ (PNG, JPG)
ใช้ CSS Text Overlay บนภาพพื้นหลังสำหรับ Hero Banner และ CTAs
ทำให้การสร้างภาพหน้าจอสำหรับ App Store และหน้า Marketing อัตโนมัติ
กรณีทั่วไป
นักออกแบบสร้างแบนเนอร์โปรโมชั่นที่มีข้อความ 'Start Your Free Trial' ฝังอยู่ในภาพโดยตรง
ทีมท้องถิ่นแปลข้อความ UI ทั้งหมด แต่แบนเนอร์บนหน้าเยอรมันยังคงแสดงข้อความภาษาอังกฤษ
ผลลัพธ์: หน้าแลนด์ดิ้งของเยอรมันมีแบนเนอร์ภาษาอังกฤษที่ทำให้สับสน การสร้างแบนเนอร์ที่แปลเป็น 15 ภาษาใช้เวลาประมาณ 3 วันของงานออกแบบ และทำให้การเปิดตัวช้าลง
ทำไมเป็นปัญหา
หน้าเชิงการตลาดทั่วไปมีภาพที่มีข้อความ 5-10 ภาพ สำหรับ 15 ภาษา จะมีภาพที่แตกต่างกัน 75-150 แบบที่ต้องสร้าง ดูแล และอัปเดตเมื่อมีการออกแบบใหม่.
ข้อความที่ฝังอยู่ในภาพ (PNG, JPG, SVG ที่มีข้อความแนบอยู่) ไม่สามารถดึงออกได้ด้วยเครื่องมือแปล. เวอร์ชันที่แปลแล้วต้องให้ดีไซน์เนอร์แก้ไขไฟล์ต้นฉบับด้วยมือ, ส่งออก, และอัปโหลด
ผู้ใช้เห็นภาพที่มีข้อความในภาษาต่างประเทศ หรือยิ่งไปกว่านั้นคือการผสมผสานระหว่าง UI ที่แปลแล้วกับภาพที่ยังไม่แปล ซึ่งดูไม่สอดคล้องและทำให้ความไว้วางใจในแบรนด์ลดลง
วิธีแก้ปัญหานี้
แยกข้อความออกจากภาพด้วย CSS overlays, SVG ที่มีองค์ประกอบข้อความที่สามารถแปลได้ หรือการสร้างภาพด้วยการสร้างภาพแบบไดนามิก
ใช้ CSS เพื่อวางข้อความที่สามารถแปลได้เหนือภาพพื้นหลัง: วางชั้นข้อความด้วยตำแหน่ง absolute เหนือคอนเทนเนอร์ภาพ
สำหรับอินโฟกราฟิกหรือตัวอย่างภาพ diagram ให้ใช้ SVG ที่มีองค์ประกอบข้อความ <text> ที่อ้างถึงคีย์การแปล แทนการฝังข้อความเปล่า
สำหรับภาพหน้าจอแอปในสื่อการตลาด ให้สร้างภาพหน้าจออัตโนมัติด้วยเครื่องมืออย่าง Fastlane (Mobile) หรือ Playwright (Web) ซึ่งจะถ่ายภาพหน้าจอในแต่ละ Locale
ข้อผิดพลาด #8: การขาดการแปลที่ยังไม่ได้รับการจัดการ
การแปลระหว่างการพัฒนามักไม่ครบถ้วน ฟีเจอร์ใหม่ๆ เพิ่มสตริงมากกว่าที่นักแปลจะทันแปลได้ โดยไม่มีระบบ fallback ที่เหมาะสม การขาดการแปลอาจทำให้ UI ล้มเหลว หรือส่วนประกอบ UIว่างเปล่า หรือคีย์การแปลดิบที่ผู้ใช้เห็น
['ตรวจสอบให้แน่ใจเสมอว่ามีภาษา fallback อย่างน้อยหนึ่งภาษาในการตั้งค่า i18n ของคุณ', 'บันทึกคีย์การแปลที่ขาดหายไปในระบบมอนิเตอร์ของคุณเพื่อการติดตาม', 'กำหนดระดับการครอบคลุมการแปลขั้นต่ำก่อนที่ Locale จะเปิดใช้งาน']
กำหนดภาษา fallback อย่างน้อยหนึ่งภาษาในตั้งค่าการ i18n ของคุณ
บันทึกคีย์ที่ยังไม่มีการแปลไปยังระบบเฝ้าติดตามของคุณเพื่อให้ติดตามได้
กำหนดระดับการครอบคลุมการแปลขั้นต่ำ ก่อนที่ Locale จะเผยแพร่
กรณีทั่วไป
นักพัฒนาเพิ่มส่วนใหม่ 'Premium Features' ที่มี 15 คีย์การแปลใหม่ รุ่นภาษาอังกฤษถูกปล่อยออกมาทันที
การแปลภาษาฝรั่งเศสยังไม่เสร็จ หน้าภาษาฝรั่งเศสแสดงคีย์เปล่าๆ เช่น 'premium.feature1.title', 'premium.feature1.description'.
ผลลัพธ์: ผู้ใช้ภาษาฝรั่งเศสเห็นหน้าที่เสียหายเต็มไปด้วยชื่อคีย์ของนักพัฒนา ทีมสนับสนุมได้รับรายงานบัคหลายรายการ
ทำไมเรื่องนี้ถึงเป็นปัญหา
ยิ่งแอปของคุณใหญ่เท่าไร ช่องว่างระหว่างสตริงภาษาอังกฤษกับการแปลในภาษาอื่นก็ยิ่งใหญ่ขึ้น แอปที่มี 100 ภาษาและ 2,000 สตริงอาจมีการขาดการแปลมากกว่า 10,000 รายการอยู่เสมอ
Fallback-Lojik olmadan eksik çeviri anahtarları undefined, null ya da ham anahtar dizesini döndürebilir (ör. 'checkout.confirmButton'). Şablon motorları hatalar üretebilir, sayfa çöker ya da hiçbir şey render olmayabilir.
ผู้ใช้เห็น UI ที่เสียหาย: ปุ่มว่างเปล่า ไม่มีป้ายชื่อ หรือข้อความที่สับสนอย่าง 'nav.settings.title' แทนข้อความจริง นี่ทำให้สับสนและไม่เป็นมืออาชีพ
วิธีแก้ไข
ตั้งค่ากลุ่ม fallback ภาษาให้มั่นคง และติดตามการครอบคลุมการแปลในทุก locale
ตั้งค่าห่วงภาษา fallback ใน i18n ของคุณ: คีย์ภาษาฝรั่งเศส (fr) ที่หายไปจะตกกลับไปยังภาษาอังกฤษ (en) อย่างอัตโนมัติ
เพิ่มตัวจัดการ Missing-Key ที่บันทึกคีย์ที่ยังไม่ได้แปลไปยังระบบมอนิเตอร์ของคุณ (เช่น Sentry, Datadog) โดยไม่ทำให้ประสบการณ์ผู้ใช้เสียหาย
สร้างแดชบอร์ดความครอบคลุมการแปลที่ติดตามระดับความสมบูรณ์ต่อแต่ละ locale และบล็อกการเผยแพร่หากความครอบคลุมต่ำกว่าค่ากำหนด (เช่น 95%)
ข้อผิดพลาด #9: ปัญหาการเข้ารหัสอักขระ
ปัญหาการเข้ารหัสอักขระเป็น 'ฆาตกรเงียบ' ของการท้องถิ่น ทุกอย่างดูเรียบร้อยในภาษาอังกฤษและภาษาในยุโรป แต่เมื่อคุณเพิ่มภาษาจีน ญี่ปุ่น เกาหลี อารบิก หรือ Emoji จะพบ Mojibake ความผิดพลาดเหล่านี้มักตรวจจับยาก
['ใช้ UTF-8 ในทุกส่วน — ไฟล์ต้นฉบับ, ฐานข้อมูล, คำตอบ API และ HTML Meta-Tags', 'ใช้ utf8mb4 ใน MySQL (ไม่ใช่ utf8) เพื่อรองรับ Unicode ทั้งหมดรวมถึง Emoji', 'ทดสอบด้วยเนื้อหาจริงใน CJK, อารบิก และ Emoji เพื่อค้นหาปัญหาการเข้ารหัสตั้งแต่เนิ่นๆ']
ใช้การเข้ารหัส UTF-8 ทุกที่ — ไฟล์ต้นฉบับ ฐานข้อมูล คำตอบ API และ HTML-Meta-Tags
ใช้งาน utf8mb4 ใน MySQL (ไม่ใช่ utf8) เพื่อรองรับ Unicode ทั้งหมดรวมถึง Emoji
ทดสอบด้วยเนื้อหาจริงใน CJK ภาษาอารบิก และ Emoji เพื่อค้นหาปัญหาการเข้ารหัสล่วงหน้า
สถานการณ์ทั่วไป
นักพัฒนาระบบติดตั้งฐานข้อมูล MySQL ที่มีการเรียงลำดับ latin1 (มาตรฐานเก่า). โค้ดต้นฉบับของแอปพลิเคชันใช้ UTF-8
ผู้ใช้ชาวญี่ปุ่นลงทะเบียนด้วยชื่อจริงของพวกเขา ฐานข้อมูลจะบันทึก '田中太郎' เป็น bytes ที่เสียหาย
ผลลัพธ์: โปรไฟล์ผู้ใช้แสดงข้อความที่เพี้ยน. ที่แย่กว่านั้น: การค้นหาและการเรียงลำดับล้มเหลวสำหรับชื่อ CJK ซึ่งเกี่ยวข้องกับผู้ใช้นับพัน
ทำไมถึงเป็นปัญหานี้
ปัญหาการเข้ารหัสลายลักษณ์อักษรแพร่กระจายทั่วสแต็กของคุณ การตั้งค่าการจัดเรียงฐานข้อมูลผิดพลาดอาจทำให้ชุดข้อมูลหลายล้านแถวเสียหาย — การซ่อมแซมต้องมีการย้ายข้อมูลที่มีค่าใช้จ่ายสูง
การเข้ารหัสที่ไม่สอดคล้องกันในสแต็ก — UTF-8 ในไฟล์ต้นฉบับ, Latin-1 ในฐานข้อมูล และ Windows-1252 ในตอบ API — ทำลายสัญลักษณ์หลาย-byte. หนึ่งชั้นที่กำหนดค่าไม่ถูกต้องจะเปลี่ยน '日本語' เป็น '????' หรือ '日本èª'.
ผู้ใช้เห็นข้อความที่เพี้ยน เครื่องหมายคำถาม หรือช่องว่างที่ไม่แสดงภาษาเมื่อควรมีภาษา ในกรณีร้ายแรง การป้อนข้อมูลในแบบฟอร์มอาจเสียหายในฐานข้อมูล
วิธีแก้ไขปัญหานี้
บังคับให้เข้ารหัส UTF-8 สอดคล้องกันในทุกชั้นของ stack ของแอปพลิเคชัน
ตั้งค่าทุกไฟล์ต้นฉบับให้เป็น UTF-8 (ปรับโปรแกรมแก้แก้ของคุณและ .editorconfig) เพิ่ม <meta charset='UTF-8'> ใน HTML ของคุณ และ 'Content-Type: application/json; charset=utf-8' ในการตอบ API
กำหนดฐานข้อมูลของคุณให้ utf8mb4 (ไม่ใช่ utf8 เท่านั้น เนื่องจาก utf8 เป็นชุดอักษร 3 ไบต์ใน MySQL) ตั้งค่าการเรียงลำดับการเชื่อมต่อเป็น utf8mb4_unicode_ci
เลือกฟอนต์ที่ครอบคลุมระบบตัวอักษรเป้าหมายของคุณ: ลาติน, เคอร์ริลลิค, CJK, อารบิก, เดวานาการี. ใช้ System-Font-Stacks หรือ Google Fonts ที่มี subsets ตามภาษาเพื่อโหลดอย่างเหมาะสม
ทดสอบการใช้งาน i18n ของคุณ
การทดสอบการขยายความยาว
ขยายสตริงที่แปลทั้งหมด 30-40% เพื่อจำลองการขยายข้อความที่เกิดในภาษาอย่างเยอรมัน ฟินแลนด์ หรือกรีก ซึ่งมีคำที่ยาวขึ้น และครอบคลุม container ที่มีความกว้างคงที่ ป้ายที่ถูกตัด และปุ่มที่ล้นก่อนที่คุณจะเริ่มแปลจริง เครื่องมือ pseudo-localization หลายตัวมีฟีเจอร์นี้ในตัว
"Senden" → "Ṡééééñðéñ_éxpáñðéð" (ยาวขึ้น 40%)
โลคัลไลเซชันเทียม
การโลคาลิไซด์ปลอมจะเปลี่ยนทุกตัวอักษรให้เป็นสำเนียงที่มีเครื่องหมาย (เช่น 'a' → 'á') และหุ้มข้อความด้วยเครื่องหมายอย่าง [!! และ !!]. ด้วยวิธีนี้จะเห็นได้ทันทีว่าอักษรใดถูก hard-code และอันไหนมาจากระบบการแปล. รันโลคาลิไซชันปลอมเป็นส่วนหนึ่งของ pipeline CI ของคุณเพื่อจับ regression อัตโนมัติ.
ข้อความบนหน้าจอทั้งหมดที่ไม่ถูกห่ออยู่ใน [!! !!]-เครื่องหมายจะถูกฝังไว้ในโค้ดและต้องถูกแยกออก การทดสอบนี้จะจับประมาณ 95% ของสตริงที่ถูกมองข้ามในเวลาน้อยกว่า 1 นาที
"ส่งข้อความ" → "[!! Ñáçḥŕíçḥṫ ṡéñðéñ !!]"
การทดสอบการออกแบบ RTL
แม้จะไม่มีการแปลภาษาอาหรับหรือฮีบรู คุณยังสามารถทดสอบ RTL-layout โดยการเพิ่ม dir='rtl' ให้กับ HTML root ของคุณ นี่จะเปิดเผยบั๊ก CSS ที่เกี่ยวกับทิศทางทันที: ไอคอนจัดตำแหน่งไม่ถูก, ช่องว่างด้านขวา/ซ้ายผิด, การนำทางทำงานผิด และรายการ Flex เรียงลำดับผิด ทำให้เป็นการตรวจสอบมาตรฐานในทุก Sprint Review — ใช้เวลา 10 วินาทีในการสลับและจับปัญหาที่อาจต้องใช้สัปดาห์ในการแก้ใน Production.
รายการตรวจสอบ i18n
['ข้อความทั้งหมดที่มองเห็นได้ของผู้ใช้ถูกย้ายไปยังไฟล์ทรัพยากร', 'ไม่ใช้การต่อสตริงเพื่อสร้างประโยค', 'ได้ดำเนินการกติกาพหูพจน์ด้วย ICU MessageFormat หรือเทียบเท่า', 'การจัดรูปแบบวันที่ เวลา และตัวเลขโดยใช้ API ที่คำนึงถึง Locale', 'ทดสอบการออกแบบ RTL ด้วยเนื้อหาภาษาอาหรับหรือฮีบรู', 'UI ที่ยืดหยุ่นโดยไม่กำหนดความกว้างคงที่สำหรับองค์ประกอบที่มีข้อความ', 'กำหนดภาษากลับและทดสอบเมื่อคีย์ขาดหาย', 'การเข้ารหัส UTF-8 สอดคล้องกันในทุกไฟล์และฐานข้อมูล', 'เมตาดาต้าของ App Store และ Play Store สำหรับแต่ละตลาดถูกท้องถิ่น', 'ภาพหน้าจอและทรัพยากรการตลาดสำหรับแต่ละภาษาถูกอัปเดต']