Skip to content

Commit

Permalink
Moved Localization/Code stuff over to setting the value of untranslat…
Browse files Browse the repository at this point in the history
…ed keys to the key iself instead of leaving the value empty. This should make it clearer to the user that these are missing translations instead of just having no text at all which can look broken.
  • Loading branch information
noah-nuebling committed Feb 28, 2024
1 parent 3d19553 commit effc525
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 45 deletions.
2 changes: 1 addition & 1 deletion App/SupportFiles/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>22350</string>
<string>22356</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>NSHumanReadableCopyright</key>
Expand Down
2 changes: 1 addition & 1 deletion Helper/SupportFiles/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>22350</string>
<string>22356</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSBackgroundOnly</key>
Expand Down
49 changes: 49 additions & 0 deletions Localization/Code/Localization Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Localization > Code

## StateOfLocalization

(All of these commands are for the fish shell)

**Install dependencies**

```
python3 -m venv env;\
source env/bin/activate.fish;\
python3 -m pip install -r Localization/Code/StateOfLocalization/requirements.txt;
```

**Run the script**

```
python3 Localization/Code/StateOfLocalization/script.py --api_key <...>
```
(Use --help for an explanation of the args)

**Deactivate the venv**

```
deactivate
```

## UpdateStrings

(All of these commands are for the fish shell)

**Install dependencies**

```
python3 -m venv env;\
source env/bin/activate.fish;\
python3 -m pip install -r Localization/Code/UpdateStrings/requirements.txt;
```

**Run the script**

```
python3 Localization/Code/UpdateStrings/script.py
```
(Use --help for an explanation of the args)

```
deactivate
```
2 changes: 1 addition & 1 deletion Localization/Code/Shared/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def find_localization_files(repo_root, website_root=None, basetypes=['IB', 'stri
for b in files_absolute:
# Validate
_, extension = os.path.splitext(b)
assert extension == '.md', f'Folder at {b}, contained file with extension {extension}'
assert extension == '.md', f'Folder at {b} contained file with extension {extension}'
# Append markdown file
result.append({ 'base': b, 'repo': mmf_repo, 'basetype': 'gh-markdown' })

Expand Down
39 changes: 32 additions & 7 deletions Localization/Code/StateOfLocalization/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
def main():

parser = argparse.ArgumentParser()
parser.add_argument('--api_key', required=True, help="See Apple Note 'MMF Localization Script Access Token'")
parser.add_argument('--api_key', required=False, help="If no API key is supplied, the result will be printed to the console instead of uploaded to GitHub || To find the API key, see Apple Note 'MMF Localization Script Access Token'")
parser.add_argument('--print_latest_for', required=False, help="Debugging tool. Print the latest changes for each key for each translation file whose path contains this value.")
args = parser.parse_args()

Expand All @@ -78,7 +78,12 @@ def main():
missing_analysis = analyze_missing_localization_files(files)
analysis = analyze_localization_files(files, args.print_latest_for)
markdown = markdown_from_analysis(analysis, missing_analysis)
upload_markdown(args.api_key, markdown)

if args.api_key:
upload_markdown(args.api_key, markdown)
else:
print("No API key was supplied. Printing the result to the console instead of uploading to GitHub:\n\n")
print(markdown)

#
# Debug
Expand Down Expand Up @@ -348,6 +353,10 @@ def markdown_from_analysis(files, missing_files):
empty_str = '\n- '.join(map(lambda x: f"Base file: {translation_to_markdown(x['key'], x['base_value'], file_type)}\n Translation: {translation_to_markdown(x['key'], x['value'], file_type)}", sorted(translation_dict['empty_translations'], key=lambda x: x['key'])))
if len(empty_str) > 0:
content_str += f"\n\n**Empty translations**\n\nThe following key-value-pairs are empty in the translation but not empty in the base file. It looks like they have not yet been translated:\n\n- {empty_str}"

equal_to_key_str = '\n- '.join(map(lambda x: f"Base file: {translation_to_markdown(x['key'], x['base_value'], file_type)}\n Translation: {translation_to_markdown(x['key'], x['value'], file_type)}", sorted(translation_dict['equal_to_key_translations'], key=lambda x: x['key'])))
if len(equal_to_key_str) > 0:
content_str += f"\n\n**Equal-to-key translations**\n\nThe following key-value-pairs are have a value that is equal to the key. It looks like they have not yet been translated:\n\n- {equal_to_key_str}"


# Build strings for outdated translations
Expand Down Expand Up @@ -693,8 +702,8 @@ def analyze_localization_files(files, print_latest_for):
At time of writing, is_ok_count is used for 2 things in the code:
1. When the is_ok_count increases for a kv-pair in a commit, that's treated as a `change` to the kv-pair in `get_latest_change_for_translation_keys()`.
This should behave well in all scenarios. E.g. when is_ok_count increases the kv-pair will not be considered outdated. But when the base value changes afterwards, the translation will be considered outdated again.
2. When the is_ok_count for a kv-pair is greater 0, then we filter that kv-pair from the 'unchanged_translations' and the 'empty_translations'.
Important consideration why this makes sense:
2. When the is_ok_count for a kv-pair is greater 0, then we filter that kv-pair from the 'unchanged_translations', the 'empty_translations' and the 'equal_to_key_translations'.
Important consideration why this makes sense:
The only time a translation would 'accidentally' be exactly the same as the base (unchanged) is if the translation has been generated by Xcode and never been touched by a human.
I'm not totally sure if this behaviour also makes sense for 'empty_translations'.
Expand All @@ -709,6 +718,7 @@ def analyze_localization_files(files, print_latest_for):
'superfluous_translations': [{ 'key': <translation_key>, 'value': <ui_text> }, ...],
'unchanged_translations': [{ 'key': <translation_key>, 'value': <ui_text> }, ...],
'empty_translations': [{ 'key': <translation_key>, 'value': <ui_text>, 'base_value': <ui_text>}, ...],
'equal_to_key_translations': [{ 'key': <translation_key>, 'value': <ui_text>, 'base_value': <ui_text>}, ...],
'outdated_translations': {
'<translation_key>': {
'latest_base_change': {
Expand Down Expand Up @@ -879,24 +889,39 @@ def analyze_localization_files(files, print_latest_for):

unchanged_translations = []
empty_translations = []
equal_to_key_translations = []

for k in common_keys:

b = base_keys_and_values[k]['value']
t = translation_keys_and_values[k]['value']

# Check conditions:
# Context:
# - is_ok: !IS_OK flag is set. This is explained elsewhere in this file.
# - is_equal: Not sure atm when this happens
# - is_key: Apples `extractLocStrings` tool sets the value of the kv-pairs equal to the key when it generates a .strings file based on source code.
# - b_is_empty and t_is_empty:
# - We leave some values in .strings files intentionally empty because they are defined elsewhere. In those cases the base value will be empty, and the translation value being also empty shouldn't be reported as a translation issue.
# - If only the translation value is empty but not the base value that's a translation issue. Not sure atm when this happens.

is_ok = t['is_ok_count'] > 0
is_equal = b['text'] == t['text']
is_equal = t['text'] == b['text']
is_key = t['text'] == k
b_is_empty = len(b['text']) == 0 or b['text'] == '<>'
t_is_empty = len(t['text']) == 0 or t['text'] == '<>'
both_are_empty = b_is_empty and t_is_empty

if is_equal and not both_are_empty and not is_ok:
unchanged_translations.append({'key': k, 'value': t['text']})
if not b_is_empty and t_is_empty and not is_ok:
elif not b_is_empty and t_is_empty and not is_ok:
empty_translations.append({'key': k, 'value': t['text'], 'base_value': b['text']})
elif is_key and not is_ok:
equal_to_key_translations.append({'key': k, 'value': t['text'], 'base_value': b['text']})

translation_dict['unchanged_translations'] = unchanged_translations
translation_dict['empty_translations'] = empty_translations
translation_dict['equal_to_key_translations'] = equal_to_key_translations

# Log
print(f' Analyze when keys last changed...')
Expand Down Expand Up @@ -1303,4 +1328,4 @@ def is_website_repo(git_repo):
# Call main
#
if __name__ == "__main__":
main()
main()
38 changes: 30 additions & 8 deletions Localization/Code/UpdateStrings/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,19 @@ def update_strings_files(files, type, repo_root):
(if type == 'sourcecode') Update .strings files to match source code files which they translate
(if type == 'IB') Update .strings files to match .xib/.storyboard files which they translate
Discussion:
- In update_ib_comments we don't update development language files - because there are none, since development language strings are directly inside the ib files.
But in this function, we also update en.lproj/Localizable.strings. Maybe we should've specified development language values directly in the source code using `NSLocalizedStringWithDefaultValue` instead of inside en.lproj. That way we wouldn't need en.lproj at all.
Not sure anymore why we chose to do it with en.lproj instead of all source code for English. But either way, I don't think it's worth it to change now.
Note:
Discussion:
- On location of English (development language) UI strings:
Explanation:
- For IB, the English strings are directly in the source (IB) files
(This isn't the case for every Xcode project, but we chose do t the case for the MMF project.)
- On the other hand, for sourcecode, the English strings not directly in the source (sourcecode) files, instead they are inside en.lproj/Localizable.strings.
Thoughts:
- In update_ib_comments we don't update English .strings files - because currently in MMF there are no English .strings files for IB files - that's because the IB files hold the English UI strings directly.
- Maybe we should've specified development language values directly in the source code using `NSLocalizedStringWithDefaultValue` instead of inside en.lproj. That way we wouldn't need en.lproj at all.
- Not sure anymore why we chose to do it with en.lproj instead of all source code for English. But either way, I don't think it's worth it to change now.
"""

print(f"\nUpdating strings files type {type}...")
Expand All @@ -134,7 +142,13 @@ def update_strings_files(files, type, repo_root):

for file_dict in files:

# Autogenerate fresh .strings file from the source files (IB/sourcecode) using Apples tools
# Notes:
# - The fresh strings file will have its comments and keys up-to-date with the source file
# - We store the content of these fresh strings files inside generated_content

generated_content = ''

if type == 'sourcecode':
source_code_files = shared.find_files_with_extensions(['m','c','cp','mm','swift'], ['env/', 'venv/', 'iOS-Polynomial-Regression-master/', './Test/'])
source_code_files_str = ' '.join(map(lambda p: p.replace(' ', r'\ '), source_code_files))
Expand All @@ -148,18 +162,23 @@ def update_strings_files(files, type, repo_root):
else:
assert False

# Find all the .strings files that translate the source files
translation_file_paths = list(file_dict['translations'].keys())
if type == 'sourcecode':
translation_file_paths.append(file_dict['base'])

for path in translation_file_paths:


# Update the translation .strings file
# using the keys and comments of the generated .strings file
content = shared.read_file(path, 'utf-8')
new_content, mods, ordered_keys = updated_strings_file_content(content, generated_content, path, repo_root)

# Store updates
if new_content != content:
updated_files.append({"path": path, "new_content": new_content})

# Debug
modss.append({'path': path, 'mods': mods, 'ordered_keys': ordered_keys})

# Return
Expand Down Expand Up @@ -237,15 +256,18 @@ def log_modifications(modss):
def updated_strings_file_content(content, generated_content, file_path, repo_root):

"""
At the time of writing:
What this does at time of writing:
- Copy over all comments from `generated_content` to `content`
- Insert kv-pair + comment from `generated_content` into `content` - if the kv-pair is not found in `content`
- Reorder kv-pairs in `content` to match `generated_content`
"""

# Parse both contents
# Notes:
# - Apples `extractLocStrings` tool which generated the (generated_content) sets all values to the key. We can use remove_value=True, to remove these autogenerated values.
# - Update: I think it's better to keep the keys. Makes it more clear to users that 'this string is not translated' instead of having no text at all which looks broken.
parse = parse_strings_file_content(content, file_path)
generated_parse = parse_strings_file_content(generated_content, file_path, remove_value=True) # `extractLocStrings` sets all values to the key for some reason, so we remove them.
generated_parse = parse_strings_file_content(generated_content, file_path, remove_value=False)

# Record modifications for diagnositics
mods = []
Expand Down
12 changes: 6 additions & 6 deletions Localization/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"already-using-defaults-toast.5" = "이미 __5개 버튼__의 마우스에 해당하는 기본 설정을 사용 중입니다.";

/* First draft: Version %@ || Note: %@ will be replaced by the app version, e.g. '3.0.0 (22027)' */
"app-version" = "";
"app-version" = "app-version";

/* First draft: <Rewind key> */
"apple-key-fallback.backward" = "<되감기 키>";
Expand Down Expand Up @@ -213,10 +213,10 @@
"effect.mission-control.hint" = "Mission Control을 엽니다.";

/* First draft: Primary Click */
"effect.primary-click" = "";
"effect.primary-click" = "effect.primary-click";

/* First draft: Works like clicking %@ on a standard mouse. */
"effect.primary-click.hint" = "";
"effect.primary-click.hint" = "effect.primary-click.hint";

/* First draft: Keyboard Shortcut... */
"effect.record-shortcut" = "키보드 단축키...";
Expand All @@ -231,10 +231,10 @@
"effect.right-space.hint" = "우측 Space로 이동합니다.";

/* First draft: Secondary Click */
"effect.secondary-click" = "";
"effect.secondary-click" = "effect.secondary-click";

/* First draft: Works like clicking %@ on a standard mouse. */
"effect.secondary-click.hint" = "";
"effect.secondary-click.hint" = "effect.secondary-click.hint";

/* First draft: Works like pressing '%@' on your keyboard */
"effect.shortcut.hint" = "키보드 상의 '%@'를 누르는 것처럼 동작합니다.";
Expand All @@ -246,7 +246,7 @@
"effect.smart-zoom.hint" = "Safari 등의 앱에서 콘텐츠를 확대하거나 축소합니다.\n \n이는 Apple 트랙패드에서 두 손가락으로 이중 탭하는 것과 같습니다.";

/* First draft: If you have **problems enabling** the app, click [here](https://github.com/noah-nuebling/mac-mouse-fix/discussions/categories/guides). */
"enable-timeout-toast" = "";
"enable-timeout-toast" = "enable-timeout-toast";

/* First draft: **Primary Mouse Button** can't be used\nPlease try another button */
"forbidden-capture-toast.1" = "**마우스 주 버튼**은 사용할 수 없습니다.\n다른 버튼으로 다시 시도하십시오.";
Expand Down
2 changes: 1 addition & 1 deletion Localization/vi.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@
"effect.smart-zoom.hint" = "Phóng to hoặc thu nhỏ trong Safari và các ứng dụng khác.\n \nHoạt động như nhấp bằng hai ngón tay trên Apple Trackpad.";

/* First draft: If you have **problems enabling** the app, click [here](https://github.com/noah-nuebling/mac-mouse-fix/discussions/categories/guides). */
"enable-timeout-toast" = "";
"enable-timeout-toast" = "enable-timeout-toast";

/* First draft: **Primary Mouse Button** can't be used\nPlease try another button */
"forbidden-capture-toast.1" = "**Nút chính** không thể được áp dụng\nVui lòng chọn một nút khác";
Expand Down
12 changes: 6 additions & 6 deletions Localization/zh-HK.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"already-using-defaults-toast.5" = "你**已經在使用**預設的**五鍵**滑鼠設定";

/* First draft: Version %@ || Note: %@ will be replaced by the app version, e.g. '3.0.0 (22027)' */
"app-version" = "";
"app-version" = "app-version";

/* First draft: <Rewind key> */
"apple-key-fallback.backward" = "􀊉〔回帶鍵〕";
Expand Down Expand Up @@ -222,10 +222,10 @@
"effect.mission-control.hint" = "顯示「指揮中心」";

/* First draft: Primary Click */
"effect.primary-click" = "";
"effect.primary-click" = "effect.primary-click";

/* First draft: Works like clicking %@ on a standard mouse. */
"effect.primary-click.hint" = "";
"effect.primary-click.hint" = "effect.primary-click.hint";

/* First draft: Keyboard Shortcut... */
"effect.record-shortcut" = "鍵盤快捷鍵";
Expand All @@ -240,10 +240,10 @@
"effect.right-space.hint" = "向右移動一個空間";

/* First draft: Secondary Click */
"effect.secondary-click" = "";
"effect.secondary-click" = "effect.secondary-click";

/* First draft: Works like clicking %@ on a standard mouse. */
"effect.secondary-click.hint" = "";
"effect.secondary-click.hint" = "effect.secondary-click.hint";

/* First draft: Works like pressing '%@' on your keyboard */
"effect.shortcut.hint" = "作用相當於在你的鍵盤上按下「%@」";
Expand All @@ -255,7 +255,7 @@
"effect.smart-zoom.hint" = "在Safari和其他應用程式中放大或縮小。\n \n作用相當於在Apple觸控板上用兩指點兩下";

/* First draft: If you have **problems enabling** the app, click [here](https://github.com/noah-nuebling/mac-mouse-fix/discussions/categories/guides). */
"enable-timeout-toast" = "";
"enable-timeout-toast" = "enable-timeout-toast";

/* First draft: **Primary Mouse Button** can't be used\nPlease try another button */
"forbidden-capture-toast.1" = "不能使用**主要滑鼠按鈕**\n請換一個按鈕再試";
Expand Down
Loading

0 comments on commit effc525

Please sign in to comment.