Skip to content

Commit

Permalink
Merge pull request #1092 from activist-org/546-base-i18n-obj
Browse files Browse the repository at this point in the history
#546 Base i18n map object and process to create it
  • Loading branch information
andrewtavis authored Jan 16, 2025
2 parents 1d3ee90 + be179f2 commit 58e5f1f
Show file tree
Hide file tree
Showing 6 changed files with 1,145 additions and 4 deletions.
9 changes: 5 additions & 4 deletions frontend/components/SearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
v-if="sidebar.collapsed == false || sidebar.collapsedSwitch == false"
>
<label for="input-search" class="sr-only">{{
$t("_global.search")
$t(i18nMap._global.search)
}}</label>
<input
@focus="onFocus"
Expand All @@ -26,7 +26,7 @@
class="h-5 w-16 bg-transparent outline-none"
:class="{ 'focus:w-5/6': isInputFocused }"
type="text"
:placeholder="$t('_global.search')"
:placeholder="$t(i18nMap._global.search)"
/>
</div>
</Transition>
Expand Down Expand Up @@ -82,14 +82,14 @@
size="1em"
/>
<label for="input-search" class="hidden md:block">{{
$t("_global.search")
$t(i18nMap._global.search)
}}</label>
<input
id="input-search"
class="bg-transparent focus:outline-none"
:class="{ hidden: !expanded }"
type="text"
:placeholder="$t('_global.search')"
:placeholder="$t(i18nMap._global.search)"
/>
<Icon v-if="expanded" class="absolute right-3" :name="IconMap.FILTER" />
</div>
Expand All @@ -99,6 +99,7 @@
import { useMagicKeys, whenever } from "@vueuse/core";
import { IconMap } from "~/types/icon-map";
import { SearchBarLocation } from "~/types/location";
import { i18nMap } from "~/types/i18n-map";

export interface Props {
location: SearchBarLocation;
Expand Down
2 changes: 2 additions & 0 deletions frontend/i18n/check/i18n_check_key_identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
str((frontend_directory / ".nuxt").resolve()),
str((frontend_directory / "node_modules").resolve()),
]
files_to_skip = ["i18n-map.ts"]
file_types_to_check = [".vue", ".ts", ".js"]

with open(json_file_directory / "en-US.json", encoding="utf-8") as f:
Expand All @@ -39,6 +40,7 @@
for file in files
if all(root[: len(d)] != d for d in directories_to_skip)
and any(file[-len(t) :] == t for t in file_types_to_check)
and file not in files_to_skip
)

file_to_check_contents = {}
Expand Down
96 changes: 96 additions & 0 deletions frontend/i18n/check/i18n_check_map_object.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Checks the i18nMap object to make sure that it's in sync with the base localization file.
Usage:
python3 frontend/i18n/check/i18n_check_map_object.py
"""

import json
from collections.abc import MutableMapping
from pathlib import Path

# MARK: Load Base i18n

i18n_check_dir = Path(__file__).parent.parent.resolve()
with open(i18n_check_dir / "en-US.json", encoding="utf-8") as f:
en_us_json_dict = json.loads(f.read())

flat_en_us_json_dict = {k: k for k, _ in en_us_json_dict.items()}

# MARK: Load i18n Map

frontend_types_dir = (Path(__file__).parent.parent.parent / "types").resolve()
with open(frontend_types_dir / "i18n-map.ts", encoding="utf-8") as f:
i18n_object_list = f.readlines()

i18n_object_list_with_key_quotes = []
for i, line in enumerate(i18n_object_list):
if i != 0 or i != len(i18n_object_list):
if line.strip()[0] != '"':
i18n_object_list_with_key_quotes.append(
f'"{line.replace(":", '":').strip()}'
)

else:
i18n_object_list_with_key_quotes.append(
f"{line.replace(':', '":').strip()}"
)

i18n_object = (
"".join(i18n_object_list_with_key_quotes)
.replace("export const i18nMap = ", "")
.replace("};", "}")
.replace('"{', "{")
.replace('"}', "}")
.replace(",}", "}")
)
i18n_object_dict = json.loads(i18n_object)


# MARK: Flatten i18n Obj


def flatten_nested_dict(
dictionary: MutableMapping, parent_key: str = "", sep: str = "."
) -> MutableMapping:
"""
Flattens a nested dictionary.
Parameters
----------
d : MutableMapping
The nested dictionary to flatten.
parent_key : str
The key of the current value being flattened.
sep : str
The separator to be used to join the nested keys.
Returns
-------
MutableMapping
The flattened version of the given nested dictionary.
"""
items = []
for k, v in dictionary.items():
new_key = parent_key + sep + k if parent_key else k
if isinstance(v, MutableMapping):
items.extend(flatten_nested_dict(v, new_key, sep=sep).items())

else:
items.append((new_key, v))

return dict(items)


flat_i18n_object_dict = flatten_nested_dict(i18n_object_dict)

# MARK: Compare Dicts

assert (
flat_en_us_json_dict == flat_i18n_object_dict
), "\nThe current i18nMap object doesn't match the base i18n JSON file. Please re-generate the i18nMap object.\n"

print("\nSuccess: The current i18nMap object matches the base i18n JSON file.\n")
83 changes: 83 additions & 0 deletions frontend/i18n/check/i18n_generate_map_object.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Generates a TypeScript file with the object i18nMap that maps all i18n keys as properties.
This allows type checking of all i18n keys to assure that they've been entered correctly.
Usage:
python3 frontend/i18n/check/i18n_generate_map_object.py
"""

import json
import re
from pathlib import Path

# MARK: Paths / Files

i18n_check_dir = Path(__file__).parent.parent.resolve()

with open(i18n_check_dir / "en-US.json", encoding="utf-8") as f:
en_us_json_dict = json.loads(f.read())

# MARK: Create Map


def _recursively_nest_dict(k: str, v: str | dict, output_dict: dict):
"""
Recursively nests dictionaries.
Parameters
----------
k : str
The key of a sub dictionary.
v : str | dict
The value of a nested dictionary.
output_dict | dict
The output_dict dictionary or sub-dictionary.
"""
k, *rest = k.split(".", 1)
if rest:
_recursively_nest_dict(rest[0], v, output_dict.setdefault(k, {}))

else:
output_dict[k] = v


def nest_flat_dict(flat_dict: dict) -> dict:
"""
Nest a flat dictionary.
Parameters
----------
flat_dict : dict
A flattened dictionary that should be nested.
Returns
-------
nested_dict : dict
The nested version of the original flat dictionary.
"""
nested_dict = {}
for k, v in flat_dict.items():
_recursively_nest_dict(k=k, v=v, output_dict=nested_dict)

return nested_dict


i18n_map_dict = nest_flat_dict({k: k for k, _ in en_us_json_dict.items()})

# MARK: Write Map

frontend_types_dir = (Path(__file__).parent.parent.parent / "types").resolve()

with open(frontend_types_dir / "i18n-map.ts", encoding="utf-8", mode="w") as f:
f.write(f"export const i18nMap = {json.dumps(i18n_map_dict, indent=2)}")

# Rewrite to format the keys to not have quotes.
with open(frontend_types_dir / "i18n-map.ts", encoding="utf-8", mode="r") as f:
i18n_object = f.readlines()

with open(frontend_types_dir / "i18n-map.ts", encoding="utf-8", mode="w") as f:
for line in i18n_object:
f.write(re.sub(r'"([^"]*)":', r"\1:", line))
1 change: 1 addition & 0 deletions frontend/i18n/check/run_i18n_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def main():
"i18n_check_non_source_keys.py",
"i18n_check_unused_keys.py",
"i18n_check_repeat_values.py",
# "i18n_check_map_object.py",
]

for check in checks:
Expand Down
Loading

0 comments on commit 58e5f1f

Please sign in to comment.