1414 This catches the common mistake of converting a YAML quoted string
1515 to a block scalar without removing the outer quotes (which become
1616 literal characters in block scalars).
17+ 5. social_callout_{platform}_{n} values fit within each platform's
18+ post character limit after {url} substitution, and contain the
19+ required {url} placeholder so the share intent gets a link.
1720"""
1821
1922import glob
@@ -209,6 +212,78 @@ def check_quoted_block_scalars(path):
209212 return errors
210213
211214
215+ # Per-platform character limits for social_callout_{platform}_{n} messages.
216+ # url_weight = 23 reflects platforms that fold every URL to a fixed width
217+ # (X via t.co, Mastodon per its character-counting rules). Other platforms
218+ # count the URL as its visible length, so we substitute the longest URL
219+ # the site ever emits: https://keepandroidopen.org/{locale}/ — pt-BR and
220+ # zh-CN are tied at 34 chars.
221+ SOCIAL_PLATFORM_LIMITS = {
222+ "x" : {"limit" : 280 , "url_weight" : 23 },
223+ "bluesky" : {"limit" : 300 , "url_weight" : None },
224+ "mastodon" : {"limit" : 500 , "url_weight" : 23 },
225+ "linkedin" : {"limit" : 3000 , "url_weight" : None },
226+ "facebook" : {"limit" : 63206 , "url_weight" : None },
227+ }
228+ LONGEST_SUBSTITUTED_URL = "https://keepandroidopen.org/zh-CN/"
229+
230+ _SOCIAL_CALLOUT_KEY = re .compile (r"^social_callout_([a-z]+)_(\d+)$" )
231+
232+
233+ def check_social_callout_limits (path ):
234+ """Return a list of error strings for social_callout_* keys whose
235+ rendered length exceeds the target platform's post character limit,
236+ or that are missing the required {url} placeholder.
237+
238+ Only applies to src/i18n/locales/*.yaml.
239+ """
240+ if "/locales/" not in path :
241+ return []
242+
243+ errors = []
244+ try :
245+ with open (path ) as fh :
246+ data = yaml .safe_load (fh )
247+ except yaml .YAMLError :
248+ return [] # syntax error already reported
249+
250+ if not isinstance (data , dict ):
251+ return []
252+
253+ for key , value in data .items ():
254+ m = _SOCIAL_CALLOUT_KEY .match (key )
255+ if not m :
256+ continue
257+ platform = m .group (1 )
258+ cfg = SOCIAL_PLATFORM_LIMITS .get (platform )
259+ if cfg is None :
260+ continue
261+ if not isinstance (value , str ):
262+ errors .append (f"{ path } : key '{ key } ': expected string value" )
263+ continue
264+
265+ if "{url}" not in value :
266+ errors .append (
267+ f"{ path } : key '{ key } ': missing required {{url}} placeholder"
268+ )
269+ continue
270+
271+ if cfg ["url_weight" ] is not None :
272+ rendered = value .replace ("{url}" , "x" * cfg ["url_weight" ])
273+ else :
274+ rendered = value .replace ("{url}" , LONGEST_SUBSTITUTED_URL )
275+
276+ n = len (rendered )
277+ if n > cfg ["limit" ]:
278+ errors .append (
279+ f"{ path } : key '{ key } ': { n } chars exceeds { platform } "
280+ f"limit of { cfg ['limit' ]} (counted after {{url}} "
281+ f"substitution)"
282+ )
283+
284+ return errors
285+
286+
212287def main ():
213288 files = find_yaml_files ()
214289 if not files :
@@ -221,6 +296,7 @@ def main():
221296 all_errors .extend (check_escaped_quotes_in_block_scalars (path ))
222297 all_errors .extend (check_html_in_locale_values (path ))
223298 all_errors .extend (check_quoted_block_scalars (path ))
299+ all_errors .extend (check_social_callout_limits (path ))
224300
225301 if all_errors :
226302 print (f"Found { len (all_errors )} error(s):\n " )
0 commit comments