Skip to content

Commit

Permalink
Refactored to extract locales from Python dictionaries to JSON files (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
cicirello authored Sep 7, 2023
1 parent abb8079 commit 53bb18e
Show file tree
Hide file tree
Showing 36 changed files with 1,704 additions and 1,552 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.graphql linguist-detectable
*.json linguist-detectable
quickstart/*.yml linguist-detectable
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased] - 2023-09-06
## [Unreleased] - 2023-09-07

### Added

Expand All @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed

### Fixed
* Refactored everything locale related to extract definitions of locales from Python dictionaries to JSON files to ease the process of contributing additional language translations.

### Dependencies

Expand Down
58 changes: 54 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ The remainder of the documentation is organized into the following sections:
* [Blog Posts](#blog-posts): A selection of blog posts about the GitHub Action.
* [Support the Project](#support-the-project): Ways that you can support the project.
* [Contributors](#contributors): Information for potential contributors.
* [Contributing a Translation](#contributing-a-translation): Detailed instructions
for contributing a language translation for a new locale, the most common type
of contribution to this project.

## Quickstart
See the [Quickstart](https://github.com/cicirello/user-statistician/blob/main/quickstart) directory
Expand Down Expand Up @@ -827,9 +830,9 @@ action will use the default of "en". The following locales are currently support
| tr | Turkish |
| uk | Ukrainian |

If you are interested in contributing a new locale, only the
[src/StatConfig.py](https://github.com/cicirello/user-statistician/blob/main/src/StatConfig.py) file must be updated. See the comments
within that file for guidance in contributing a locale.
If you are interested in contributing a new locale, see the
section [Contributing a Translation](#contributing-a-translation)
for detailed instructions for doing so.

### `fail-on-error`

Expand Down Expand Up @@ -989,7 +992,54 @@ If you would like to contribute to the project, please start by reading
our [Contributing Guidelines](https://github.com/cicirello/.github/blob/main/CONTRIBUTING.md)
and our [Code of Conduct](https://github.com/cicirello/.github/blob/main/CODE_OF_CONDUCT.md).
After reading the contributing guidelines and code of conduct, fork the repository, create
a feature branch in your fork, make your changes, and submit a pull request.
a feature branch in your fork, make your changes, and submit a pull request. Please note that
every pull request must be associated with an issue that it addresses. If there is a change
that you would like to propose that doesn't already have an issue, please submit an issue
first.

### Contributing a Translation

The most common type of contribution to this project has been language translations. Here is
step-by-step guidance on how to contribute a translation.
* Is there an [open issue](https://github.com/cicirello/user-statistician/issues?q=is%3Aopen)
for the language you want to contribute? If so, and if nobody is assigned to it, comment on
the issue expressing interest, and I will assign you to it (e.g., so we don't have multiple
people working on the same thing). If there isn't an open issue for it, then submit an issue
for a feature request for the language. Then comment on your new issue indicating if you are
interested in contributing the translation (e.g., so I know the difference between just a
request for a language vs a request with offer to contribute it). I'll then assign you to it.
* Fork the repository, and create a branch in your fork for your proposed translation.
* Find the [ISO 639-1 two-character language
code](https://www.loc.gov/standards/iso639-2/php/English_list.php) for the language. Some
languages have both a two-character code and a three-character code in the table at that link.
In those cases, we are going with the two-character code. But there are some languages that
only have a three-character ISO-639-2 code, and in those cases we'll use the three-character
code. For example, one of the currently supported languages is Santali, whose code is `sat`.
* In the [src/StatConfig.py](src/StatConfig.py) file, look for the set `supportedLocales` and
add a string for the locale code alphabetically.
* Next, within the directory [src/locales](src/locales), create a JSON file named with the locale
code (in all lowercase) and file extension `.json`. For example, the English version is in
`en.json`. You might consider starting with a copy of the file for a different language that
you also know, and then editing the various strings.
* Don't change any of the key fields, since those are used internally.
* Translate the `"titleTemplate"`, the first mapping you see in each of the JSON files. The `{0}`
that you see in a title template string is a placeholder for where the user's name will go. The
`"titleTemplate"` is required to have that someplace, or else the test cases will fail.
* Translate the `"categoryLabels"`, which are the headings of each of the sections of the SVG, as
well as the column headings within those sections. The categories that don't have columns should
just have `null` for the column headings as you will see in any of the existing JSON files.
* Translate the `"statLabels"`, which are the labels for each individual statistic on the SVG.
* Run the unit tests locally. The test cases will verify that there are strings associated with
all required keys for all locale codes, although they obviously won't verify that the translations
are correct. To run the unit tests locally, from the root of the repository,
execute: `python -B -m unittest tests/tests.py`. Note that for the tests to include your locale,
as well as for the Action itself to include your locale, the code for it must be in the
`supportedLocales` set within [src/StatConfig.py](src/StatConfig.py). See the earlier step on this.
* Despite what the pull request template indicates, you do not need to update the README to document
the new locale code. We'll do that later at the time a new release is made that includes your new
locale. The reason not to document it at the time of your PR is that all changes to the README from
the default branch go live immediately on GitHub's Marketplace, which may confuse users into
believing the new locale is available before it actually is.

## License

Expand Down
91 changes: 42 additions & 49 deletions src/ColorUtil.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# user-statistician: Github action for generating a user stats card
#
# Copyright (c) 2021 Vincent A Cicirello
# Copyright (c) 2021-2023 Vincent A Cicirello
# https://www.cicirello.org/
#
# MIT License
Expand All @@ -24,81 +24,74 @@
# SOFTWARE.
#

def isValidColor(color) :
def isValidColor(color):
"""Checks if a color is a valid color.
Keyword arguments:
color - The color to check, either in hex or as a named color or as an rgba().
color - The color to check, either in hex or as
a named color or as an rgba().
"""
validHexDigits = set("0123456789abcdefABCDEF")
color = color.strip()
if color.startswith("#") :
if len(color) != 4 and len(color) != 7 :
if color.startswith("#"):
if len(color) != 4 and len(color) != 7:
return False
return all(c in validHexDigits for c in color[1:])
elif color.startswith("rgba(") :
elif color.startswith("rgba("):
return strToRGBA(color) != None
else :
else:
return color in _namedColors

def strToRGBA(color) :
def strToRGBA(color):
"""Converts a str specifying rgba color to
r, g, b, and a channels. Returns (r, g, b, a) is valid
and otherwise returns None.
Keyword arguments:
color - a str of the form: rgba(r,g,b,a).
"""
if color.startswith("rgba(") :
if color.startswith("rgba("):
last = color.find(")")
if last > 5 :
if last > 5:
rgba = color[5:last].split(",")
if len(rgba) == 4 :
try :
if len(rgba) == 4:
try:
r = int(rgba[0])
g = int(rgba[1])
b = int(rgba[2])
a = float(rgba[3])
except ValueError :
except ValueError:
return None
if r >= 256 :
r = 255
if g >= 256 :
g = 255
if b >= 256 :
b = 255
if r < 0 :
r = 0
if g < 0 :
g = 0
if b < 0 :
b = 0
if a < 0.0 :
a = 0.0
if a > 1.0 :
a = 1.0
r = min(r, 255)
g = min(g, 255)
b = min(b, 255)
r = max(r, 0)
g = max(g, 0)
b = max(b, 0)
a = max(a, 0.0)
a = min(a, 1.0)
return r, g, b, a
return None

def highContrastingColor(color) :
def highContrastingColor(color):
"""Computes a highly contrasting color.
Specifically, maximizes the contrast ratio. Contrast ratio is
(L1 + 0.05) / (L2 + 0.05), where L1 and L2 are the luminances
of the colors and L1 is the larger luminance. Returns None
if color is not a valid hex color or named color.
Keyword arguments:
color - The color to contrast with, either in hex or as a named color.
color - The color to contrast with, in hex or as a named color.
"""
L = luminance(color)
if L == None :
if L == None:
return None
if (L + 0.05) / 0.05 >= 1.05 / (L + 0.05) :
if (L + 0.05) / 0.05 >= 1.05 / (L + 0.05):
return "#000000" # black
else :
else:
return "#ffffff" # white

def contrastRatio(c1, c2) :
def contrastRatio(c1, c2):
"""Computes contrast ratio of a pair of colors.
Returns the contrast ratio provided both colors are valid,
and otherwise returns None.
Expand All @@ -109,36 +102,36 @@ def contrastRatio(c1, c2) :
"""
L1 = luminance(c1)
L2 = luminance(c2)
if L1 == None or L2 == None :
if L1 == None or L2 == None:
return None
if L1 < L2 :
if L1 < L2:
L1, L2 = L2, L1
return (L1 + 0.05) / (L2 + 0.05)

def luminance(color) :
def luminance(color):
"""Calculates the luminance of a color. Returns None
if color is not a valid hex color or named color.
Keyword arguments:
color - The color, either in hex or as a named color.
"""
color = color.strip()
if color.startswith("rgba(") :
if color.startswith("rgba("):
rgba = strToRGBA(color)
if rgba != None :
if rgba != None:
r, g, b, a = rgba
else :
else:
return None
else :
if not color.startswith("#") :
if color not in _namedColors :
else:
if not color.startswith("#"):
if color not in _namedColors:
return None
color = _namedColors[color]
if len(color) == 4 :
if len(color) == 4:
r = color[1] + color[1]
g = color[2] + color[2]
b = color[3] + color[3]
elif len(color) == 7 :
elif len(color) == 7:
r = color[1:3]
g = color[3:5]
b = color[5:7]
Expand All @@ -152,14 +145,14 @@ def luminance(color) :
b = _sRGBtoLin(b / 255)
return 0.2126 * r + 0.7152 * g + 0.0722 * b

def _sRGBtoLin(c) :
def _sRGBtoLin(c):
"""Transformation from
https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance
Keyword arguments:
c - A color channel (i.e., r, g, or b)
"""
if c <= 0.04045 :
if c <= 0.04045:
return c / 12.92
else :
return ((c + 0.055)/1.055) ** 2.4
Expand Down Expand Up @@ -319,4 +312,4 @@ def _sRGBtoLin(c) :
"whitesmoke" : "#f5f5f5",
"yellow" : "#ffff00",
"yellowgreen" : "#9acd32"
}
}
30 changes: 16 additions & 14 deletions src/PieChart.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# user-statistician: Github action for generating a user stats card
#
# Copyright (c) 2021 Vincent A Cicirello
# Copyright (c) 2021-2023 Vincent A Cicirello
# https://www.cicirello.org/
#
# MIT License
Expand Down Expand Up @@ -32,7 +32,7 @@
_circleTemplate = '<circle fill="{0}" cx="{1}" cy="{1}" r="{1}"/>'
_animationTemplate = '<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 {0} {0}" to="360 {0} {0}" dur="{1}s" repeatCount="indefinite"/>'

def svgPieChart(wedges, radius, animate, speed, includeSVGHeader=False) :
def svgPieChart(wedges, radius, animate, speed, includeSVGHeader=False):
"""Generates an SVG of a pie chart. The intention is to include
as part of a larger SVG (e.g., it does not insert xmlns into the
opening svg tag). If wedges list is empty, it retrurns None.
Expand All @@ -42,20 +42,22 @@ def svgPieChart(wedges, radius, animate, speed, includeSVGHeader=False) :
containing fields color and percentage.
radius - the radius, in pixels for the pie chart.
animate - Pass True to animate the pie chart.
speed - If animate is True, then this input is the number of seconds for one full rotation.
speed - If animate is True, then this input is the number of
seconds for one full rotation.
"""
if includeSVGHeader :
if includeSVGHeader:
components = [_headerTemplate.format(str(2*radius))]
else :
else:
components = []

if len(wedges) == 0 :
if len(wedges) == 0:
return None
elif len(wedges) == 1 :
components.append(_circleTemplate.format(wedges[0]["color"], str(radius)))
else :
elif len(wedges) == 1:
components.append(
_circleTemplate.format(wedges[0]["color"], str(radius)))
else:
startPercentage = 0
for w in wedges :
for w in wedges:
endPercentage = startPercentage + w["percentage"]
w["start"] = startPercentage * 2 * math.pi
w["end"] = endPercentage * 2 * math.pi
Expand All @@ -65,10 +67,10 @@ def svgPieChart(wedges, radius, animate, speed, includeSVGHeader=False) :
# (i.e., last edge should complete a full circle).
wedges[-1]["end"] = 2 * math.pi

if animate :
if animate:
components.append("<g>")

for w in wedges :
for w in wedges:
components.append(
_pathTemplate.format(
w["color"],
Expand All @@ -82,7 +84,7 @@ def svgPieChart(wedges, radius, animate, speed, includeSVGHeader=False) :
)
)

if animate :
if animate:
components.append(
_animationTemplate.format(
radius,
Expand All @@ -91,6 +93,6 @@ def svgPieChart(wedges, radius, animate, speed, includeSVGHeader=False) :
)
components.append("</g>")

if includeSVGHeader :
if includeSVGHeader:
components.append("</svg>")
return "".join(components)
Loading

0 comments on commit 53bb18e

Please sign in to comment.