UI कॉपी तब एकरूप रहती है जब यूज़र को दिखने वाली हर स्ट्रिंग रेपो के एक ही कैटलॉग में हो, key से रेफ़र की जाए और सिर्फ़ रिव्यू किए गए diff के ज़रिए बदले। यह वही अनुशासन है जो आप लॉजिक पर पहले से लागू करते हैं, बस शब्दों पर लागू किया गया। इस पेज की बाक़ी हर बात वह प्रैक्टिस है जो इसे टिकाऊ बनाती है।

यह ऑपरेशनल गाइड है। इसके पीछे का आर्किटेक्चर, और यह तर्क कि प्रोडक्ट कॉपी का source of truth कोडबेस ही क्यों होना चाहिए, कोड-नेटिव प्रोडक्ट कॉपी में दिया गया है। "क्यों" जानना हो तो उसे पढ़ें। "कैसे" जानना हो तो आगे पढ़ते रहें।

UI कॉपी कहाँ रहनी चाहिए?

हर स्रोत भाषा के लिए एक कैटलॉग फ़ाइल में, रिपॉज़िटरी के भीतर, उस कोड के पास जो उसे रेंडर करता है। स्प्रेडशीट में नहीं, डिज़ाइन फ़ाइल में नहीं, मार्च में सही रहे किसी विकी पेज में नहीं। कैटलॉग वह जगह है जहाँ डेवलपर, रिव्यूअर, अनुवादक और कोडिंग एजेंट सभी यह जानने के लिए देखते हैं कि किसी स्क्रीन पर क्या लिखा है।

इससे जो व्यावहारिक नियम निकलते हैं:

  • एक कैटलॉग, एक source locale। एक अकेली en.json (या .po, .ftl, .strings, जो भी आपकी i18n लाइब्रेरी पढ़ती हो) ही रिकॉर्ड की कॉपी है। अगर मोनोरेपो में कई ऐप हैं, तो हर ऐप का एक कैटलॉग हो, और साझा स्ट्रिंग के लिए एक साझा कैटलॉग हो जिसे दोनों इम्पोर्ट करें। बचना यह है कि एक ही संदेश दो फ़ाइलों में रह सके।
  • स्क्रीन या फ़ीचर के हिसाब से ग्रुप करें, कंपोनेंट के हिसाब से नहीं। checkout.summary.total जैसी keys SummaryCard.totalLabel से बेहतर टिकती हैं, क्योंकि कंपोनेंट के नाम बदलते और वे टूटकर बँटते रहते हैं, जबकि स्क्रीन और संदेश अपनी जगह बने रहते हैं।
  • key का नाम संदेश के आधार पर रखें, शब्दों के आधार पर नहीं। checkout.submit "Place order" से "Buy now" में बदलाव के बाद भी टिका रहता है। checkout.placeOrderButton नहीं टिकता, और key व टेक्स्ट के बीच यही बेमेल बाद में रिव्यूअर को यह मानने पर मजबूर करता है कि ये दो अलग स्ट्रिंग हैं।
  • कैटलॉग को उसी pull request में रखें जिसमें उसे इस्तेमाल करने वाला कोड है। एक PR में जोड़ी गई स्ट्रिंग और दूसरे PR में उसका कंपोनेंट, ऐसी स्ट्रिंग है जो छूट जाएगी, भुला दी जाएगी या डुप्लिकेट हो जाएगी।

अगर आपकी स्ट्रिंग अब भी कंपोनेंट के भीतर लिटरल हैं, तो सबसे पहले कैटलॉग बनाना होगा। इसे हाथ से करना अपने आप में एक प्रोजेक्ट है, और यही रिपॉज़िटरी कनेक्ट करके कन्वर्ज़न को यह काम करने देने का तर्क है, जो आगे बताया गया है।

लिटरल खोजने से बेहतर key से रेफ़र करना क्यों है?

क्योंकि एक key सिर्फ़ एक स्ट्रिंग तक resolve हो सकती है, जबकि एक लिटरल उतने तरीक़ों से लिखा जा सकता है जितने डेवलपर हैं। "Sign in", "Sign In", "Log in" और "Login" सर्च टूल के लिए चार स्ट्रिंग हैं और यूज़र के लिए एक ही मंशा। जब हर कंपोनेंट t("auth.signIn") कॉल करता है, तो कौन-सा शब्द सही है, इसका जवाब एक बार, एक जगह तय हो जाता है, और वह हर उस जगह लागू होता है जहाँ key इस्तेमाल हुई है।

दो आदतें इसे काम करता रखती हैं:

  • जोड़ने से पहले दोबारा इस्तेमाल करें। key बनाने से पहले कैटलॉग में वह संदेश खोजें। अगर common.cancel मौजूद है, तो उसी टेक्स्ट वाली नई dialog.cancelButton कोई हानिरहित डुप्लिकेट नहीं, बल्कि drift की शुरुआत है। जैसे ही कोई एक को बदलेगा, दोनों अलग-अलग एडिट होने लगेंगी।
  • स्ट्रिंग जोड़कर (concatenate) वाक्य न बनाएँ। t("cart.items") + " " + count ऐसा टेक्स्ट बनाता है जिसका वर्णन कैटलॉग की किसी लाइन में नहीं है। अपनी लाइब्रेरी के interpolation और plural फ़ॉर्म इस्तेमाल करें ताकि पूरा वाक्य रिव्यू की जा सकने वाली एक इकाई रहे।

लोग अक्सर यह अपवाद ढूँढते हैं: "यह स्ट्रिंग सिर्फ़ एक बार आती है, इसलिए लिटरल चलेगा।" यह आज एक बार आती है। कल कंपोनेंट कॉपी होगा, उसकी जैसी एक और स्क्रीन बनेगी, और लिटरल उसके साथ जाकर फिर अलग हो जाएगा।

pull request में कॉपी बदलावों का रिव्यू कैसे करें?

बदली हुई कैटलॉग लाइन को वैसे ही देखें जैसे बदले हुए फ़ंक्शन सिग्नेचर को: ऐसी चीज़ जिसे रिव्यूअर पढ़ता है, सवाल करता है और मंज़ूर करता है। यही प्रैक्टिस एकरूपता को याददाश्त की समस्या से रिव्यू की समस्या में बदल देती है, और रिव्यू ही वह पल है जब कोई भरोसेमंद ढंग से ध्यान दे रहा होता है।

कैटलॉग रिव्यू को काम करने लायक़ क्या बनाता है:

  • शब्दों का बदलाव diff में अपनी अलग लाइन होता है। यह तभी सच है जब स्ट्रिंग कैटलॉग में हो। कंपोनेंट के भीतर शब्द बदलना भी diff की एक लाइन है, पर वह लॉजिक के बदलावों के बीच छिप जाती है और "बस टेक्स्ट है" कहकर नज़रअंदाज़ कर दी जाती है।
  • कैटलॉग के बदलाव कॉपी ओनर को भेजें। कैटलॉग पाथ पर CODEOWNERS एंट्री का मतलब है कि शब्दों का ज़िम्मा जिसके पास भी हो, चाहे वह कोई प्रोडक्ट पर्सन हो, कंटेंट डिज़ाइनर हो या वह डेवलपर जिसे इसकी सबसे ज़्यादा परवाह है, उसे हर बदलाव पर रिव्यूअर के रूप में जोड़ा जाता है। PR की बाक़ी चीज़ें सामान्य रिव्यू से गुज़रती हैं।
  • हर बदली हुई लाइन पर तीन सवाल पूछें। क्या यह इस चीज़ के लिए स्वीकृत शब्द है? क्या यह आसपास की स्ट्रिंग से टोन और केसिंग में मेल खाता है? क्या key का नाम अब भी संदेश का वर्णन करता है? जो रिव्यूअर ये तीन सवाल पूछता है, वह स्टाइल गाइड जिन चीज़ों को रोकने के लिए बनती है, उनमें से ज़्यादातर पकड़ लेता है।
  • चुपचाप किए गए rename अस्वीकार करें। जो PR auth.signIn को बिना किसी स्पष्टीकरण के "Sign in" से "Log in" में बदल दे, वह उस व्यक्ति का लिया हुआ कॉपी-निर्णय है जो उस समय एडिट कर रहा था। PR विवरण में एक वाक्य में कारण बताना ज़रूरी है, वरना उसे revert करना होगा।

जिस विफलता से यह बचाता है, उसका एक नाम है। Copy drift वह स्थिति है जब यूज़र को दिखने वाला टेक्स्ट अलग-अलग सतहों पर और समय के साथ अपने स्वीकृत स्रोत से भटक जाता है, और कोई एक source of truth होता ही नहीं जिससे वह भटके। Copy drift क्या है में इसके कारण समझाए गए हैं; diff में कैटलॉग रिव्यू वह प्रैक्टिस है जो इनमें से ज़्यादातर को रोकती है।

नई हार्डकोडेड स्ट्रिंग को घुसने से कैसे रोकें?

नया लिटरल आते ही बिल्ड फ़ेल करवाएँ। नियम और रिव्यू वही पकड़ते हैं जिसे देखना लोगों को याद रहता है। बाक़ी को lint रूल पकड़ता है, हर pull request पर, बिना किसी को कुछ याद रखे।

सेटअप छोटा है:

  • no-literal-string रूल चालू करें। eslint-plugin-i18next JavaScript और TypeScript प्रोजेक्ट के लिए एक रूल देता है। इसे JSX टेक्स्ट और यूज़र-फ़ेसिंग एट्रिब्यूट, जैसे इनपुट हिंट, title, aria-label और alt, तक सीमित रखें, और टेस्ट फ़ाइलों व Storybook stories को छूट दें ताकि रूल की साख बनी रहे।
  • इसे वहाँ चलाएँ जहाँ मर्ज होते हैं। pre-commit hook सुविधाजनक है; CI स्टेप वह है जो मायने रखता है, क्योंकि वह pull request पर चलता है चाहे लेखक का एडिटर कॉन्फ़िगर हो या नहीं।
  • unused और missing keys के लिए चेक जोड़ें। ज़्यादातर i18n लाइब्रेरी के साथ एक सहायक टूल आता है जो उन keys की सूची देता है जो कोड में रेफ़र हैं पर कैटलॉग में नहीं हैं, और उन keys की भी जो कैटलॉग में हैं पर कहीं रेफ़र नहीं हुईं। इसे भी CI में चलाएँ। unused keys वह जगह हैं जहाँ पुराने शब्द छिपे रहते हैं।
  • सख़्ती से शुरू करें, फिर अपवाद कमेंट से दें। जो रूल पूरे components/ फ़ोल्डर के लिए बंद है, वह रूल नहीं रह जाता। जो रूल कारण के साथ सिर्फ़ एक लाइन पर बंद है, वह दस्तावेज़ का काम करता है।

AI कोडिंग टूल से बनाने वाली टीमों को यह ज़्यादा झेलना पड़ता है, क्योंकि टूल लोकल कॉन्टेक्स्ट से कंपोनेंट दोबारा जनरेट करते हैं और अपने साथ लिटरल भी वापस ले आते हैं। Cursor AI बार-बार हार्डकोडेड स्ट्रिंग क्यों जोड़ता है में lint रूल का विस्तार से ज़िक्र है, जिसमें rules फ़ाइल, linter और CI का तीन-स्तरीय सेटअप भी शामिल है।

डेवलपर्स के लिए कॉपी स्टाइल गाइड में क्या होना चाहिए?

रिपॉज़िटरी में एक छोटी फ़ाइल जो एक स्क्रीन में समा जाए, voice बताए, शब्द तय करे, और बताए कि स्ट्रिंग कहाँ जाएँगी। लंबी स्टाइल गाइड विकी में रहती हैं और एक बार पढ़ी जाती हैं। छोटी गाइड कोड के पास रहती है और हर बार स्ट्रिंग जोड़ते समय लोग और टूल उसे पढ़ते हैं।

एक काम करने लायक़ COPY.md (या आपकी मौजूदा contributor गाइड का एक सेक्शन) में ये शामिल होते हैं:

  1. Voice, तीन वाक्यों में। प्रोडक्ट किसकी तरह बोलता है, कितना औपचारिक है, "आप" कहता है या "यूज़र"। इतना काफ़ी है कि ज़्यादातर बहसें निपट जाएँ।
  2. कभी न बदलने वाले शब्द। हर फ़ीचर का प्रोडक्ट-नाम, मुख्य क्रियाओं के शब्द ("Save" या "Update"? "Delete" या "Remove"?), और वे शब्द जिन्हें आपने इस्तेमाल न करने का तय किया है।
  3. केसिंग और विराम-चिह्न के नियम। बटन और हेडिंग में sentence case या title case, टूलटिप में फ़ुल स्टॉप हो या नहीं, संख्याएँ और तारीख़ें कैसे लिखी जाएँ।
  4. स्ट्रिंग कहाँ जाएँगी और keys के नाम कैसे रखे जाएँगे। कैटलॉग का पाथ, ग्रुपिंग का नियम, key-नामकरण का नियम, और "जोड़ने से पहले खोजें"।
  5. अनिश्चित होने पर क्या करें। किससे पूछें, या तब तक कौन-सी key दोबारा इस्तेमाल करें।

इसे रिपॉज़िटरी में रखें ताकि rules फ़ाइल इसकी ओर इशारा कर सके, रिव्यूअर कमेंट में इसका लिंक दे सके, और कोडिंग एजेंट लेबल लिखने से पहले इसे पढ़ सके।

AI कोडिंग एजेंट को एकरूपता बिगाड़ने से कैसे रोकें?

एजेंट को पढ़ने के लिए एक source of truth दें, और ऐसा गेट रखें जो न पढ़ने पर उसे पकड़ ले। फ़ॉर्म जनरेट करता एजेंट एक स्क्रीन पर "Submit" और अगली पर "Send" ख़ुशी-ख़ुशी लिख देगा, क्योंकि हर जगह वही शब्द स्थानीय रूप से सबसे संभावित था। वह लापरवाह नहीं है; उसके पास एकरूप रहने के लिए कुछ है ही नहीं।

दो क़दम ज़्यादातर समस्या सुलझा देते हैं। पहला, एक rules फ़ाइल (AGENTS.md, .cursor/rules, CLAUDE.md, जो भी आपके टूल पढ़ते हों) जो कहे: यूज़र को दिखने वाला टेक्स्ट कैटलॉग में जाए, मौजूदा keys दोबारा इस्तेमाल करें, और लेबल लिखने से पहले COPY.md पढ़ें। दूसरा, ऊपर के सेक्शन वाला lint रूल, क्योंकि rules फ़ाइल अंदाज़ों को घटाती है और lint रूल उन अंदाज़ों को पकड़ता है जो निकल गए।

एजेंट के बारे में इस पेज को बस इतना ही कहना है। एजेंट द्वारा पहले से मौजूद स्ट्रिंग के शब्द बदल देने की ख़ास समस्या, और उस एडिट को चुपचाप होने की जगह दिखाई देने वाला कैसे बनाया जाए, यह अपने आप में अलग विषय है: AI कोडिंग एजेंट को आपकी UI कॉपी दोबारा लिखने से कैसे रोकें।

Localization इसमें कहाँ आती है?

एकरूपता और स्थानीयकरण एक ही चीज़ पर निर्भर हैं, यानी एक तय किया हुआ स्रोत, इसलिए जो कैटलॉग आपको एक देता है, वही दूसरा भी देता है। जो टीम यह नहीं बता सकती कि तीन शब्द-रूपों में से कौन-सा स्वीकृत है, वह उनमें से किसी का भी सही अनुवाद नहीं कर सकती। स्रोत ठीक कर लें तो अनुवाद कैटलॉग से निकला एक व्युत्पन्न आउटपुट बन जाता है, अपने अलग source of truth वाला कोई अलग प्रोजेक्ट नहीं रहता।

व्यवहार में इसका मतलब है कि एकरूपता के लिए आपने जो कैटलॉग बनाया है, वही वह फ़ाइल है जिससे अनुवादक या अनुवाद टूल काम करता है। हर key को हर लक्षित भाषा में उसका समतुल्य मिलता है, वही key नाम लागू रहते हैं, और स्रोत कैटलॉग में शब्दों का बदलाव ऐसे बदलाव के रूप में दिखता है जिसका अनुवादों को अनुसरण करना है। जब अनुवादों को स्रोत से पीछे छूटकर भटकने दिया जाता है, तो वही समस्याएँ आती हैं जो अनुवाद फ़ाइलें सिंक से बाहर क्यों हो जाती हैं में बताई गई हैं, और उनका हल भी वही अनुशासन है: एक कैटलॉग, keys, और बदलाव diff में।

globalize.now कहाँ फ़िट बैठता है

globalize.now स्थानीयकरण के साथ बना एक कोड-नेटिव कॉपी मैनेजमेंट सिस्टम है, और इस पेज की हर प्रैक्टिस वही है जो वह आपकी रिपॉज़िटरी के बारे में मानकर चलता है। आप ऐप में GitHub या GitLab रिपॉज़िटरी कनेक्ट करते हैं। एक स्कैन पता लगाता है कि वहाँ क्या है। अगर स्ट्रिंग कंपोनेंट के भीतर लिटरल हैं, तो एक बार का कन्वर्ज़न उन्हें keys के पीछे कैटलॉग में ले जाता है और, अगर कोई i18n सेटअप नहीं है, तो वह भी जोड़ देता है। अगर आप पहले से next-intl, react-i18next, i18next या Lingui इस्तेमाल करते हैं, तो वह आपके मौजूदा सेटअप के साथ काम करता है। नतीजा अपनी अलग ब्रांच पर एक pull request के रूप में आता है, इसलिए पहला कैटलॉग ठीक वैसे ही रिव्यू से गुज़रता है जैसा ऊपर बताया गया है। उसके बाद, आपके जोड़े हुए नए स्रोत स्ट्रिंग का अनुवाद push jobs करती हैं, जो नतीजे के साथ एक pull request खोलती हैं। रिपॉज़िटरी ही एकमात्र source of truth बनी रहती है।

हम उसी अनुशासन को एक ही स्रोत से मार्केटिंग साइट, लैंडिंग पेज और प्रोडक्ट, सब जगह लागू करने की दिशा में काम कर रहे हैं, ताकि यूज़र को जहाँ-जहाँ फ़ीचर दिखे, वहाँ उसका वर्णन एक जैसा हो। हम इसी ओर बढ़ रहे हैं; यह आज ख़रीदने की चीज़ नहीं है। कीमतें प्राइसिंग पेज पर हैं।

एक चेकलिस्ट जो आप इसी हफ़्ते अपना सकते हैं

  1. हर स्रोत भाषा के लिए रिपॉज़िटरी में एक कैटलॉग बनाएँ, और key-नामकरण का नियम तय करें।
  2. जिन स्क्रीन पर आप सबसे ज़्यादा काम करते हैं, उनकी स्ट्रिंग इसमें ले जाएँ; key से रेफ़र करें।
  3. यूज़र-फ़ेसिंग टेक्स्ट तक सीमित no-literal-string lint रूल चालू करें, और उसे CI में चलाएँ।
  4. कैटलॉग पाथ पर CODEOWNERS लाइन जोड़ें।
  5. COPY.md लिखें: voice, तय किए गए शब्द, केसिंग नियम, स्ट्रिंग कहाँ जाएँगी। बस एक स्क्रीन।
  6. अपनी एजेंट rules फ़ाइल में कैटलॉग और COPY.md का संदर्भ जोड़ें।
  7. रिव्यू के दौरान ऐसे किसी भी शब्दावली-परिवर्तन को अस्वीकार करें, जिसका कारण PR विवरण में न दिया गया हो।

अगर दूसरा चरण महीने भर का काम लगता है, तो रिपॉज़िटरी कनेक्ट करें, उसका सुझाया गया प्लान पढ़ें, और उसके द्वारा खोले गए पुल रिक्वेस्ट की समीक्षा करें।

globalize.now आपके ऐप्लिकेशन की हार्डकोडेड कॉपी को अनुवाद-तैयार locale फ़ाइलों में बदल देता है और जैसे-जैसे आप नई रिलीज़ करते हैं, उन्हें अपडेट रखता है।

globalize.now को निःशुल्क आजमाएं