Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added translatable reading_label and translations for all default languages + pt (no locale) and ar (no local) #23

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 12 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Grav ReadingTime Plugin
# Grav <sup>Better</sup>ReadingTime Plugin

**ReadingTime** is a [Grav](http://github.com/getgrav/grav) plugin which allows Grav to display the reading time of a page's content. This is especially useful for blogs and other sites as it gives the reader a quick idea of how much time they will need to set aside to read the page in full.
**<sup>Better</sup>ReadingTime** is a [Grav](http://github.com/getgrav/grav) plugin which allows Grav to display the reading time of a page's content. This is especially useful for blogs and other sites as it gives the reader a quick idea of how much time they will need to set aside to read the page in full.

***This fork differs from the original in three main ways:***
1. ***Reading label is translated, not just "minutes" and "seconds".***
2. ***Reading time (and label) is cached, in translated form, in the page headers of every page. This negates the need to run the calculation on every page load if reading time is present in the frontmatter.***
3. ***Disabled seconds. Reading time is rounded to the nearest minute for a cleaner view.***

Enabling the plugin is very simple. Just install the plugin folder to `/user/plugins/` in your Grav install. By default, the plugin is enabled.

Expand All @@ -21,48 +26,14 @@ The contents of the zipped folder should now be located in the `/your/site/grav/
Place the following line of code in the theme file you wish to add the ReadingTime plugin for:

```
{{ page.content|readingtime }}
```

You need to pass a Twig Filter 'readingtime' to display the reading time values. You can translate the labels with this example:

```
{{ page.content|readingtime({'minutes_label': 'Minuti', 'minute_label': 'Minuto', 'seconds_label': 'Secondi', 'second_label': 'Secondo'}) }}
```

I used Italian translation for the labels, you can change with your language.

If you need you can change the format with this avariable variables (the code is default format):

```
{{ page.content|readingtime({'format': '{minutes_short_count} {minutes_text}, {seconds_short_count} {seconds_text}'}) }}
{% if config.plugins.readingtime.enabled %}
{{ page.header.readingTime }}{{ page|readingtime }}
{% endif %}
```

Available variables:

| Variable | Description | Example |
| :---------------- | :---------------------- | :--------------------------------------------------------------------------- |
| `{minute_label}` | Minute Label (Singular) | `minute` |
| `{minutes_label}` | Minutes Label (Plural) | `minutes` |
| `{second_label}` | Second Label (Singular) | `second` |
| `{seconds_label}` | Second Label (Plural) | `seconds` |
| `{format}` | Display Format | `{minutes_text} {minutes_short_count}, {seconds_text} {seconds_short_count}` |

Not available to edit but used in the format variable:

| Variable | Description | Example |
| :---------------------- | :--------------------------------------- | :------ |
| `{minutes_short_count}` | Displays Minutes with Abbreviated Digits | `2` |
| `{seconds_short_count}` | Displays Seconds with Abbreviated Digits | `9` |
| `{minutes_long_count}` | Displays Minutes in Double Digits | `02` |
| `{seconds_long_count}` | Displays Seconds in Double Digits | `09` |

Display variables for text labels:
You need both of these twig variables to make it work. I can't remember why both are required, but if I remove either one it breaks. So just use both.

| Variable | Description | Example |
| :--------------- | :------------------------------------------------------------------------------- | :-------- |
| `{minutes_text}` | Displays the Minutes Text Label (Singular or Plural, Based on Number of Minutes) | `minute` |
| `{seconds_text}` | Displays the Seconds Text Label (Singular or Plural, Based on Number of Seconds) | `seconds` |
I haven't tested image views so I can't tell you if they still work. I also dabbled with the "estimated reading time" PR on the original repo but eventually decided not to include it, so you might find vestigial code from that PR here that I forgot to clean up.

>> NOTE: Any time you are making alterations to a theme's files, you will want to duplicate the theme folder in the `user/themes/` directory, rename it, and set the new name as your active theme. This will ensure that you don't lose your customizations in the event that a theme is updated. Once you have tested the change thoroughly, you can delete or back up that folder elsewhere.

Expand Down
219 changes: 114 additions & 105 deletions classes/TwigReadingTimeFilters.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,129 +8,138 @@

class TwigReadingTimeFilters extends Twig_Extension
{
private $grav;

public function __construct()
{
$this->grav = Grav::instance();
}

public function getName()
{
return 'TwigReadingTimeFilters';
}

public function getFilters()
{
return [
new \Twig_SimpleFilter( 'readingtime', [$this, 'getReadingTime'] )
];
}

public function validatePattern($seconds_per_image)
{
// Get regex that is used in the user interface
$pattern = '/' . $this->grav['plugins']->get('readingtime')->blueprints()->schema()->get('seconds_per_image')['validate']['pattern'] . '/';

if (preg_match($pattern, $seconds_per_image, $matches) === false) {
return false;
}

// Note: "$matches[0] will contain the text that matched the full pattern"
// https://www.php.net/manual/en/function.preg-match.php
return strlen($seconds_per_image) === strlen($matches[0]);
}

public function getReadingTime( $content, $params = array() )
{
private $grav;

$this->mergeConfig($this->grav['page']);
$language = $this->grav['language'];
public function __construct()
{
$this->grav = Grav::instance();
}

$options = array_merge($this->grav['config']->get('plugins.readingtime'), $params);
public function getName()
{
return 'TwigReadingTimeFilters';
}

$words = count(preg_split('/\s+/', strip_tags((string) $content)) ?: []);
$wpm = $options['words_per_minute'];
public function getFilters()
{
return [
new \Twig_SimpleFilter('readingtime', [$this, 'getReadingTime'])
];
}

$minutes_short_count = floor($words / $wpm);
$seconds_short_count = floor($words % $wpm / ($wpm / 60));
public function validatePattern($seconds_per_image)
{
// Get regex that is used in the user interface
$pattern = '/' . $this->grav['plugins']->get('readingtime')->blueprints()->schema()->get('seconds_per_image')['validate']['pattern'] . '/';

if ($options['include_image_views']) {
$stripped = strip_tags($content, "<img>");
$images_in_content = substr_count($stripped, "<img ");
if (preg_match($pattern, $seconds_per_image, $matches) === false) {
return false;
}

if ($images_in_content > 0) {
if ($this->validatePattern($options['seconds_per_image'])) {
// Note: "$matches[0] will contain the text that matched the full pattern"
// https://www.php.net/manual/en/function.preg-match.php
return strlen($seconds_per_image) === strlen($matches[0]);
}

// assumes string only contains integers, commas, and whitespace
$spi = preg_split('/\D+/', trim($options['seconds_per_image']));
$seconds_images = 0;
public function getReadingTime($input, $params = array())
{
$this->mergeConfig($this->grav['page']);
$language = $this->grav['language'];

$options = array_merge($this->grav['config']->get('plugins.readingtime'), $params);

// --- FIX: Use readingTime from header ---
if (is_object($input) && $input instanceof Page && isset($input->header()->readingTime)) {
$minutes_short_count = $input->header()->readingTime;
} else { // Calculate reading time if not already available
$content = is_object($input) ? $input->content() : $input;
$words = count(preg_split('/\s+/', strip_tags((string)$content)) ?: []);
$wpm = $options['words_per_minute'];

$minutes_short_count = floor($words / $wpm);
$seconds_short_count = floor($words % $wpm / ($wpm / 60));

if ($options['include_image_views']) {
$stripped = strip_tags($content, "<img>");
$images_in_content = substr_count($stripped, "<img ");

if ($images_in_content > 0) {
if ($this->validatePattern($options['seconds_per_image'])) {

// assumes string only contains integers, commas, and whitespace
$spi = preg_split('/\D+/', trim($options['seconds_per_image']));
$seconds_images = 0;

for ($i = 0; $i < $images_in_content; ++$i) {
$seconds_images += $i < count($spi) ? $spi[$i] : end($spi);
}

$minutes_short_count += floor($seconds_images / 60);
$seconds_short_count += $seconds_images % 60;
} else {
$this->grav['log']->error("Plugin 'readingtime' - seconds_per_image failed regex vadation");
}
}
}

$round = $options['round'];
if ($round == 'minutes') {
$minutes_short_count = round(($minutes_short_count * 60 + $seconds_short_count) / 60);

if ($minutes_short_count < 1) {
$minutes_short_count = 1;
}

$seconds_short_count = 0;
}
}

for ($i = 0; $i < $images_in_content; ++$i) {
$seconds_images += $i < count($spi) ? $spi[$i] : end($spi);
}
$minutes_long_count = number_format($minutes_short_count, 2);
$seconds_long_count = number_format($seconds_short_count, 2);

$minutes_short_count += floor($seconds_images / 60);
$seconds_short_count += $seconds_images % 60;
if (array_key_exists('minute_label', $options) and $minutes_short_count == 1) {
$minutes_text = $options['minute_label'];
} elseif (array_key_exists('minutes_label', $options) and $minutes_short_count > 1) {
$minutes_text = $options['minutes_label'];
} else {
$this->grav['log']->error("Plugin 'readingtime' - seconds_per_image failed regex vadation");
$minutes_text = $language->translate(($minutes_short_count == 1) ? 'PLUGIN_READINGTIME.MINUTE' : 'PLUGIN_READINGTIME.MINUTES');
}
}
}

$round = $options['round'];
if ($round == 'minutes') {
$minutes_short_count = round(($minutes_short_count*60 + $seconds_short_count) / 60);

if ( $minutes_short_count < 1 ) {
$minutes_short_count = 1;
}

$seconds_short_count = 0;
}
if (array_key_exists('second_label', $options) and $seconds_short_count == 1) {
$seconds_text = $options['second_label'];
} elseif (array_key_exists('seconds_label', $options) and $seconds_short_count > 1) {
$seconds_text = $options['seconds_label'];
} else {
$seconds_text = $language->translate(($seconds_short_count == 1) ? 'PLUGIN_READINGTIME.SECOND' : 'PLUGIN_READINGTIME.SECONDS');
}

$minutes_long_count = number_format($minutes_short_count, 2);
$seconds_long_count = number_format($seconds_short_count, 2);

if (array_key_exists('minute_label', $options) and $minutes_short_count == 1) {
$minutes_text = $options['minute_label'];
} elseif (array_key_exists('minutes_label', $options) and $minutes_short_count > 1) {
$minutes_text = $options['minutes_label'];
} else {
$minutes_text = $language->translate(( $minutes_short_count == 1 ) ? 'PLUGIN_READINGTIME.MINUTE' : 'PLUGIN_READINGTIME.MINUTES');
}
// Adding translation for reading_label
$reading_label = $language->translate('PLUGIN_READINGTIME.READING_LABEL');

if (array_key_exists('second_label', $options) and $seconds_short_count == 1) {
$seconds_text = $options['second_label'];
} elseif (array_key_exists('seconds_label', $options) and $seconds_short_count > 1) {
$seconds_text = $options['seconds_label'];
} else {
$seconds_text = $language->translate(( $seconds_short_count == 1 ) ? 'PLUGIN_READINGTIME.SECOND' : 'PLUGIN_READINGTIME.SECONDS');
}
$replace = [
'minutes_short_count' => $minutes_short_count,
'seconds_short_count' => $seconds_short_count,
'minutes_long_count' => $minutes_long_count,
'seconds_long_count' => $seconds_long_count,
'minutes_text' => $minutes_text,
'seconds_text' => $seconds_text,
'reading_label' => $reading_label,
];

$replace = [
'minutes_short_count' => $minutes_short_count,
'seconds_short_count' => $seconds_short_count,
'minutes_long_count' => $minutes_long_count,
'seconds_long_count' => $seconds_long_count,
'minutes_text' => $minutes_text,
'seconds_text' => $seconds_text
];
$result = $options['format'];

$result = $options['format'];
foreach ($replace as $key => $value) {
$result = str_replace('{' . $key . '}', $value, $result);
}

foreach ( $replace as $key => $value ) {
$result = str_replace('{' . $key . '}', $value, $result);
return $result;
}

return $result;
}

private function mergeConfig( Page $page )
{
$defaults = (array) $this->grav['config']->get('plugins.readingtime');
if ( isset($page->header()->readingtime) ) {
$this->grav['config']->set('plugins.readingtime', array_merge($defaults, $page->header()->readingtime));
private function mergeConfig(Page $page)
{
$defaults = (array)$this->grav['config']->get('plugins.readingtime');
if (isset($page->header()->readingtime)) {
$this->grav['config']->set('plugins.readingtime', array_merge($defaults, $page->header()->readingtime));
}
}
}
}
25 changes: 25 additions & 0 deletions languages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,84 @@ en:
SECONDS: seconds
MINUTE: minute
MINUTES: minutes
READING_LABEL: 'Reading time'

cs:
PLUGIN_READINGTIME:
SECOND: sekunda
SECONDS: sekundy
MINUTE: minuta
MINUTES: minuty
READING_LABEL: 'Čas na čtení'

es:
PLUGIN_READINGTIME:
SECOND: segundo
SECONDS: segundos
MINUTE: minuto
MINUTES: minutos
READING_LABEL: 'Tiempo de lectura'

fr:
PLUGIN_READINGTIME:
SECOND: seconde
SECONDS: secondes
MINUTE: minute
MINUTES: minutes
READING_LABEL: 'Temps de lecture'

hr:
PLUGIN_READINGTIME:
SECOND: sekunda
SECONDS: sekundi
MINUTE: minuta
MINUTES: minuta
READING_LABEL: 'Olvasási idő'

it:
PLUGIN_READINGTIME:
SECOND: secondo
SECONDS: secondi
MINUTE: minuto
MINUTES: minuti
READING_LABEL: 'Momento della lettura'

nl:
PLUGIN_READINGTIME:
SECOND: seconde
SECONDS: seconden
MINUTE: minuut
MINUTES: minuten
READING_LABEL: Leestijd

pt:
PLUGIN_READINGTIME:
SECOND: segundo
SECONDS: segundos
MINUTE: minuto
MINUTES: minutos
READING_LABEL: 'Tempo de leitura'

pt-BR:
PLUGIN_READINGTIME:
SECOND: segundo
SECONDS: segundos
MINUTE: minuto
MINUTES: minutos
READING_LABEL: 'Tempo de leitura'

de:
PLUGIN_READINGTIME:
SECOND: Sekunde
SECONDS: Sekunden
MINUTE: Minute
MINUTES: Minuten
READING_LABEL: Lesezeit

ar:
PLUGIN_READINGTIME:
SECOND: ثانية
SECONDS: ثواني
MINUTE: دقيقة
MINUTES: دقائق
READING_LABEL: 'وقت القراءة'
Loading