i18n: Update AI development principles from SSOT (20261013)

Summary

This MR updates AI development principles based on recent changes to the development documentation (SSOT). It is one of several team-scoped MRs from this run; the global routing tables (AGENTS.md, SKILL.md) are updated in a separate tooling MR.

Draft until GitLab Duo's review is addressed. GitLab Duo reviews this MR first, and its findings are worked through with the ai-principles-review-feedback skill. Human reviewers: please start once this MR is marked ready.

Could the authors listed below review the principles distilled from their SSOT changes?

Author SSOT commit(s)
@kivikakk 277f3b48
Why you were pinged, and why this needs merging

You were pinged because you changed the SSOT documentation these principles are distilled from.

These MRs need to be reviewed and merged, not just reviewed: the distilled principles only reach the gitlab-coding-principles that AI agents load once this is merged. An unmerged MR is re-distilled and reopened on the next weekly run, so the improvement stays unavailable to agents until someone merges it.

Updated principles and their source-doc changes

internationalization

Source files (2)
SSOT diff since previous distillation (33763b32 → 3e9aa54b)
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index c418e8b66d..dfaac94ee1 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -48,8 +48,8 @@ The following tools are used:
 ## Preparing a page for translation
 
 You must mark strings as translatable with the following available helpers. Keep in mind that
-strings are translated in tools where their context of use might not be obvious. Consider
-[namespacing](#namespaces) domain-specific strings to provide more context to the translators.
+strings are translated in tools where their context of use might not be obvious. Add a
+[namespace](#namespaces) to UI text to provide more context to the translators.
 
 There are four file types:
 
@@ -78,14 +78,14 @@ You can mark that content for translation with:
 
 ```ruby
 def hello
-  _("Hello world!")
+  s_("Example|Hello world!")
 end
 ```
 
 Or:
 
 ```ruby
-hello = _("Hello world!")
+hello = s_("Example|Hello world!")
 ```
 
 Be careful when translating strings at the class or module level because these are only evaluated once
@@ -135,7 +135,7 @@ Given the following content in HAML:
 You can mark that content for translation with:
 
 ```ruby
-%h1= _("Hello world!")
+%h1= s_("Example|Hello world!")
 ```
 
 ### ERB files
@@ -149,7 +149,7 @@ Given the following content in ERB:
 You can mark that content for translation with:
 
 ```html
-<h1><%= _("Hello world!") %></h1>
+<h1><%= s_("Example|Hello world!") %></h1>
 ```
 
 ### JavaScript files
@@ -184,7 +184,7 @@ This means you can externalize strings in Vue templates without having to import
 ```html
 <template>
   <h1>{{ s__('Branches|Create a new branch') }}</h1>
-  <gl-button>{{ __('Create branch') }}</gl-button>
+  <gl-button>{{ s__('Branches|Create branch') }}</gl-button>
 </template>
 ```
 
@@ -212,9 +212,9 @@ expect(rendered).to have_content('Thank you for your feedback!')
 Good:
 
 ```ruby
-click_button _('Submit review')
+click_button s_('MergeRequestReview|Submit review')
 
-expect(rendered).to have_content(_('Thank you for your feedback!'))
+expect(rendered).to have_content(s_('Feedback|Thank you for your feedback!'))
 ```
 
 ### Test files (Jest)
@@ -242,7 +242,7 @@ Also, it makes refactoring easier as we do not have to maintain variables in add
 
 ```javascript
 // Bad. A variable is defined far from where it is used
-const TITLE = __('Organisations');
+const TITLE = s__('Organization|Organisations');
 
 function transform() {
   return TITLE;
@@ -250,7 +250,7 @@ function transform() {
 
 // Good.
 function transform() {
-  return __('Organisations');
+  return s__('Organization|Organisations');
 }
 ```
 
@@ -268,7 +268,7 @@ If using variables with translations is preferred in a particular case, follow t
 In JavaScript files, declare a constant with the translation:
 
 ```javascript
-const ORGANISATIONS_TITLE = __('Organisations');
+const ORGANISATIONS_TITLE = s__('Organization|Organisations');
 ```
 
 For Vue components, see [Vue single-file components](#vue-single-file-components).
@@ -399,7 +399,7 @@ use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to
 - In Ruby/HAML:
 
   ```ruby
-  format(_("Hello %{name}"), name: 'Joe') => 'Hello Joe'
+  format(s_("UserWelcome|Hello %{name}"), name: 'Joe') => 'Hello Joe'
   ```
 
 - In Vue:
@@ -426,13 +426,13 @@ use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to
 
   ```html
   <script>
-  import { __, sprintf } from '~/locale';
+  import { s__, sprintf } from '~/locale';
 
   export default {
     ...
     computed: {
       userWelcome() {
-        return sprintf(__('Hello %{username}'), { username: this.user.name });
+        return sprintf(s__('UserWelcome|Hello %{username}'), { username: this.user.name });
       }
     }
     ...
@@ -447,9 +447,9 @@ use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to
 - In JavaScript (when Vue cannot be used):
 
   ```javascript
-  import { __, sprintf } from '~/locale';
+  import { s__, sprintf } from '~/locale';
 
-  sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
+  sprintf(s__('UserWelcome|Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
   ```
 
   If you need to use markup within the translation, use `sprintf` and stop it
@@ -459,20 +459,20 @@ use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to
 
   ```javascript
   import { escape } from 'lodash-es';
-  import { __, sprintf } from '~/locale';
+  import { s__, sprintf } from '~/locale';
 
   let someDynamicValue = '<script>alert("evil")</script>';
 
   // Dangerous:
-  sprintf(__('This is %{value}'), { value: `<strong>${someDynamicValue}</strong>`, false);
+  sprintf(s__('Example|This is %{value}'), { value: `<strong>${someDynamicValue}</strong>`, false);
   // => 'This is <strong><script>alert('evil')</script></strong>'
 
   // Incorrect:
-  sprintf(__('This is %{value}'), { value: `<strong>${someDynamicValue}</strong>` });
+  sprintf(s__('Example|This is %{value}'), { value: `<strong>${someDynamicValue}</strong>` });
   // => 'This is &lt;strong&gt;&lt;script&gt;alert(&#x27;evil&#x27;)&lt;/script&gt;&lt;/strong&gt;'
 
   // OK:
-  sprintf(__('This is %{value}'), { value: `<strong>${escape(someDynamicValue)}</strong>` }, false);
+  sprintf(s__('Example|This is %{value}'), { value: `<strong>${escape(someDynamicValue)}</strong>` }, false);
   // => 'This is <strong>&lt;script&gt;alert(&#x27;evil&#x27;)&lt;/script&gt;</strong>'
   ```
 
@@ -499,7 +499,11 @@ A namespace:
 
 Some languages are more contextual than English.
 For example, `cancel` can be translated in different ways depending on how it's used.
-To define the context of use, always add a namespace to UI text in English.
+To define the context of use, add a namespace to UI text in English:
+
+- Use a namespaced helper for UI text: `s_()` in Ruby and HAML, or `s__()` in JavaScript and Vue.
+- Use the un-namespaced `_()` or `__()` only for a short, generic term reused across the UI,
+  such as "Open", "Edit", or "New".
 
 Namespaces should be PascalCase. A linter does not enforce this yet.
 
@@ -561,14 +565,14 @@ To include formatting in the translated string, you can do the following:
 - In Ruby/HAML:
 
   ```ruby
-  safe_format(_('Some %{strongOpen}bold%{strongClose} text.'), tag_pair(tag.strong, :strongOpen, :strongClose))
+  safe_format(s_('Example|Some %{strongOpen}bold%{strongClose} text.'), tag_pair(tag.strong, :strongOpen, :strongClose))
   # => 'Some <strong>bold</strong> text.'
   ```
 
 - In JavaScript:
 
   ```javascript
-    sprintf(__('Some %{strongOpen}bold%{strongClose} text.'), { strongOpen: '<strong>', strongClose: '</strong>'}, false);
+    sprintf(s__('Example|Some %{strongOpen}bold%{strongClose} text.'), { strongOpen: '<strong>', strongClose: '</strong>'}, false);
 
     // => 'Some <strong>bold</strong> text.'
   ```
@@ -589,7 +593,7 @@ instead:
 - In Ruby/HAML:
 
   ```ruby
-  safe_format(_('In &lt; 1 hour'))
+  safe_format(s_('Duration|In &lt; 1 hour'))
 
   # => 'In < 1 hour'
   ```
@@ -599,7 +603,7 @@ instead:

... (diff truncated at 200 lines / 8 KB; use the per-file commit-history links above for the full picture)

Hand-fixes to distilled files

.ai/principles/distilled/internationalization.md was edited by hand after distillation, as a stopgap until the next sync. The front matter (source_checksum, distilled_at_sha) is unchanged.

  • 7f25861b: Applied a Duo suggestion so the angle-bracket rule shows the entity codes (&lt;/&gt;) instead of the literal </> characters.
  • f2cd3e6b: Restored a rule the distillation dropped. Namespaced helpers (s_() in Ruby/HAML, s__() in JavaScript/Vue) are now the default. Un-namespaced _()/__() is allowed only for short, generic terms reused across the UI. Source: doc/development/i18n/externalization.md, "Namespaces" section. See this thread.

How this works

A scheduled CI job detects changes to doc/development/ files listed in .ai/principles/manifest.yml, then uses the GitLab Duo Agent Platform Workflow API to distill updated principles for the affected domains.

This MR was generated by https://gitlab.com/gitlab-org/gitlab/-/jobs/17127369938

Please review the checklist changes to ensure they accurately reflect the documentation updates.

Edited by Duo Developer

Merge request reports

Loading
Loading