יצירת תוסף המציג את משך זמן הקריאה של פוסט ותרגומו

במדריך זה ניצור תוסף המאפשר את הצגת משך זמן הקריאה של פוסט.

כשבועיים לפני כתיבת פוסט זה, פורסם כאן מדריך קצר כיצד להציג בכל פוסט את משך זמן הקריאה שלו. במדריך ישנה פונקציה פשוטה מאוד המחשבת ומציגה את משך זמן הקריאה של פוסט על ידי חלוקה של מספר המלים בפוסט ב-200, שזה מהירות קריאה (מעט איטית) של מבוגר.

התלהבתי מאוד מהפשטות אך שני דברים הפריעו לי:

  • נושא התרגום – ממש מציק לי לראות עברית בקוד.
  • מאחר שמדובר בפונקציונליות ולא בעיצוב, תהיתי ושאלתי האם לא עדיף ליצור תוסף למטרה זו.

המענה שקיבלתי לגבי אותן הערות/שאלות היה:

  • איני רואה צורך להוסיף אפשרות תרגום מכיוון ואין הרבה לאן לברוח עם התרגום. דקה זו דקה ושנייה זו שנייה. אפשר ליצור תוסף אך מיותר.
  • התקנת התוסף תקח לך יותר זמן מלעשות copy & paste. שימוש בשפה אנגלית דורשת גם שינוי קוד מכיוון ו״דקה אחת״ ו "one minute״ הפוכות לצורך העניין.

יש לי שריטה בענייני תרגום וכפי שאמרתי ממש מציק לי לראות עברית בקבצי קוד ולכן התשובה לא סיפקה אותי. אני גם יודעת כמה פשוט ליצור תוסף אז גם בזה נותרתי בעמדתי ולכן החלטתי לקחת על עצמי אתגר – לענות על טיעוניו ולעשות את מה שהוא החסיר – ליצור תוסף משך קריאת זמן המשתמש בפונקציית תרגום.

שימוש בתרגום

מאחר שהתרגום הוא החלק הפשוט ורלוונטי גם למצב בו רחמנא ליצלן משאירים את הקוד בתבנית ולא משתמשים בתוסף, אתחיל בו. אמנם האתגר הוא כפול – גם יחיד וגם רבים, וישנו שוני בסדר המלים בין עברית ואנגלית – אבל ישנם עוד מקומות במערכת בהם נעשה שימוש ביחיד ורבים ואפשר לקחת דוגמה משם. אחד המקומות האלו הינו איזור התגובות בפוסט.

יש בו הבדל אם יש "תגובה אחת" (ובאנגלית "One thought", סדר המלים הפוך) או "X תגובות" (כאן אין הבדל בסדר באנגלית). כך למשל נראה הקוד הזה באחת מתבניות ברירת המחדל של וורדפרס (TwnetyThirteen אם חשוב לכם לדעת), בקובץ comments.php:

<?php if ( have_comments() ) : ?>
<h2 class="comments-title">
     <?php
      printf(
           _nx( 'One thought on &ldquo;%2$s&rdquo;', '%1$s thoughts on &ldquo;%2$s&rdquo;', get_comments_number(), 'comments title', 'twentythirteen' ),
      number_format_i18n( get_comments_number() ), '<span>' . get_the_title() . '</span>'
      );
     ?>
</h2>
<?php } ?>

ניקח דוגמה זו ונשתמש בה אצלנו. יש לשים לב שבמקרה זה משתמשים ב-printf מפני שהכתיבה מתבצעת בָּמָקום, אבל הפונקציה במאמר המקורי לא מדפיסה אלא מחזירה ערך, ולכן נמיר את השימוש ב-printf  ב-sprintf (אני בונה על זה שאתם מכירים את הפונקציות האלה ומבינים את השימוש בפרמטרים עם האחוז והדולר כגון %1$s).

בכדי להשתמש בפונקציות תרגום, הדבר ראשון שיש לעשות הוא ליצור קובץ בו מוגדרים התרגומים. ניצור קבצי  pot ו-po, וברגע שנשמור ייווצר קובץ mo אותו וורדפרס מבין. ב-WPSE ישנה תשובה עם הוראות מפורטות איך ליצור קובץ pot ב-Poedit.

ההוראות אמנם נראות ארוכות מפני שהן מאוד מפורטות, אבל תכלס זה עניין של כמה דקות והולך מהר יותר ככל שיותר מנוסים. דבר נוסף שנרצה לעשות אצלנו הוא להודיע ל-Poedit שאנחנו רוצים להשתמש בפונקציה שמשתמשת ביחיד ורבים. זאת ניתן לעשות לאחר שמסיימים את רשימת ההוראות הנ"ל, לפי ההוראות בתשובה אחרת ב-WPSE.

ספוילר: הפונקציה _nx לא עבדה כפי שרציתי (אין לי מושג למה, ואני מקווה שלא יתברר שזה משהו שמוציא אותי סתומה) ולכן עברתי לפונקציה _n, מכאן שיש להגדיר גם אותה ברשימת הפונקציות של Poedit.

לאחר שהגדרנו פונקציה זו, Poedit תאפשר לנו לתרגם גם את הערך ביחיד וגם את הערך ברבים כבתמונה הבאה:

translating-poedit-wordpress

דבר נוסף שיש לעשות הוא לומר לתבנית או לתוסף להשתמש בקובץ התרגום. אם נלך עם הדוגמה מהפוסט המקורי ונכתוב את הקוד בתבנית (לעומת תוסף), יש להשתמש בפונקציה load_textdomain. אם ניצור תוסף (בהמשך הפוסט אדגים כיצד) אז יש להשתמש בפונקציה load_plugin_textdomain.

בשני המקרים צריך להחליט על שם ה-textdomain והוא צריך להתאים לשם שנתנו לפרויקט ב-Poedit. במקרה שלי החלטתי על savvy-estimated-reading-time. כך נראה הקוד בתוסף:

add_action( 'init', 'savvy_estimated_reading_time_load_textdomain' );
 
function savvy_estimated_reading_time_load_textdomain() {
    load_plugin_textdomain( 'savvy-estimated-reading-time', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}

וכעת, לעצם השימוש בפונקציית התרגום. הקוד מפריד בין מקרה שבו יש דקות (ואולי גם שניות) לבין מקרה שיש רק שניות. בשני המקרים נשתמש בפונקציה _n שמתרגמת מחרוזות של יחיד ורבים. הפונקציה מקבלת ארבעה פרמטרים: המחרוזת ביחיד, המחרוזת ברבים, המספר שאותו מתארים (מספר הדקות או השעות), והפרמטר האחרון הוא textdomain.

הצטרפו לרשימת התפוצה!

הפרמטר השלישי (המספר) הוא זה מאפשר לפונקציה להחליט האם להשתמש במחרוזת ביחיד או ברבים.

אם מעניין אתכם, ההבדל בין _n לבין _nx הוא שהשנייה מקבלת עוד פרמטר לפני ה-textdomain, שהוא קונטקסט, אבל לנו זה לא חשוב ולכן לא היה אכפת לי להחליף פונקציה. אפשר לקרוא על זה ועל תרגום תבנית באופן כללי בפוסט קצת ישן אך עדיין רלוונטי מבית Tutsplus.

 // First write the minutes, if exist
 if ( 1 <= $minutes ) {
     $estimated_time .= sprintf( _n( 'One minute', '%1$s minutes', $minutes, 'savvy-estimated-reading-time' ), number_format_i18n( $minutes ) );
     // if there are also seconds, add the "and"
     if ( 1 <= $seconds ) {
         $estimated_time .= __( ' and ', 'savvy-estimated-reading-time' );
     }
 } 
 // Write the seconds 
 if (1 <= $seconds ){
     $estimated_time = sprintf( _n( 'One second', '%1$s seconds', $seconds, 'savvy-estimated-reading-time' ), number_format_i18n( $seconds ) );
 }

הבה נחזיר קומפוננטה שלמה

בסיטואציה זו, אמנם יוצג משך זמן הקריאה, אך סביר להניח כי לקורא לא יהיה ברור על מה מדובר. לשם כך נוסיף טקסט המתאר מה אנו רואים. דבר נוסף שחסר היא האפשרות לעצב את הטקסט ולכן נוסיף עיטופים בתגיות HTML.

מאחר שאין לנו רצון שתגיות ה-HTML תתורגמנה נחזיר אותן מחוץ למחרוזת תרגום. אם ישנה מחרוזת תרגום שצריכה להיות בתוך תגית HTML, נשתמש ב-sprintf כדי לבודד את התרגום מה-HTML.

כדאי לקרוא עוד על זה בפוסט של Otto, מתכנת וורדפרס ידוע: תרגום – אתם בטח עושים את זה לא נכון.

בכל אופן, די למילים, הנה הקוד:

$estimated_time .= sprintf( '<span class="savvy-estimated-reading-time-label">%s</span>', __( 'Estimated reading time: ', 'savvy-estimated-reading-time' ) );

שימו לב ל-%s שספון לו בין התגיות הפותחות והסוגרות של ה-span. כשהפונקציה sprintf תרוץ, היא תחליף את התרגום ב-%s בלי לגעת ב-HTML. קסם!

ללא קשר לתרגום, לדעתי נכון יהיה לעטוף את תצוגת הדקות גם ב-span ואז לעטוף הכל ב-div. הנה הקוד המלא:

if ( $minutes > 0 || $seconds > 0 ) {
     $estimated_time .= '<div class="savvy-estimated-reading-time">';
     $estimated_time .= sprintf( '<span class="savvy-estimated-reading-time-label">%s</span>', __( 'Estimated reading time: ', 'savvy-estimated-reading-time' ) );
     $estimated_time .= '<span class="savvy-estimated-reading-time-text">';
     // First write the minutes, if exist
     if ( 1 <= $minutes ) {
         $estimated_time .= sprintf( _n( 'One minute', '%1$s minutes', $minutes, 'savvy-estimated-reading-time' ), number_format_i18n( $minutes ) );
         // if there are also seconds, add the "and"
         if ( 1 <= $seconds ) {
             $estimated_time .= __( ' and ', 'savvy-estimated-reading-time' );
         }
     } 
     // Write the seconds 
     if ( 1 <= $seconds ) {
         $estimated_time .= sprintf( _n( 'One second', '%1$s seconds', $seconds, 'savvy-estimated-reading-time' ), number_format_i18n( $seconds ) );
     }
     $estimated_time .= '</span>';
     $estimated_time .= '</div>';
 }

ניצור תוסף מהקוד שלנו

אפשר ליצור תוסף בחמש דקות, באמת. אם מותקן לכם התוסף Pluginception , כל מה שצריך כדי ליצור תוסף חדש זה להחליט על שמו. זהו בעצם שדה החובה היחיד שנדרש (מובן שמוזמנים למלא גם את שאר השדות).

pluginception-create-wordpress-plugin

כאשר תמלאו את הטופס ותלחצו על כפתור ה-"!Create a blank plugin and activate it", תיווצר תת-תיקיה בתיקיית ה-plugins עם השם שבחרתם (באותיות קטנות ועם מקפים במקום ריווחים) ובתוכה קובץ PHP עם אותו שם.

הפרטים שמילאתם בטופס ייכנסו להערה בתחילת הקובץ וזה מה שיאפשר לוורדפרס לזהות את התוסף החדש שיצרתם. פפאם! החלק הקשה מאחורינו, עכשיו נותר רק להכניס את הקוד שכתבנו מעלה לתוסף.

הנה הקוד המלא של התוסף:

<?php

/*
  Plugin Name: Savvy Estimated Reading Time
  Plugin URI:
  Description:
  Version:
  Author:
  Author URI:
  License:
  License URI:
 */
 
 
add_action( 'init', 'savvy_estimated_reading_time_load_textdomain' );

function savvy_estimated_reading_time_load_textdomain() {
    load_plugin_textdomain( 'savvy-estimated-reading-time', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}

/**
 * Estimate time to read the article
 *
 * @return string
 */
function display_post_time_estimated_reading_time() {

    $post = get_post();

    //$words = str_word_count( strip_tags( $post->post_content ) );
    $words = count( preg_split( '/\s+/', strip_tags( $post->post_content ) ) );
    $minutes = floor( $words / 200 );
    $seconds = floor( $words % 200 / ( 200 / 60 ) );

    // First write the minutes, if exist
    if ( 1 <= $minutes ) {
        $estimated_time = sprintf( _n( 'One minute', '%1$s minutes', $minutes, 'savvy-estimated-reading-time' ), number_format_i18n( $minutes ) );
        // if there are also seconds, add the "and"
        if ( 1 <= $seconds ) {
            $estimated_time .= __( ' and ', 'savvy-estimated-reading-time' );
        }
    } 
    // Write the seconds 
    if (1 <= $seconds ){
        $estimated_time = sprintf( _n( 'One second', '%1$s seconds', $seconds, 'savvy-estimated-reading-time' ), number_format_i18n( $seconds ) );
    }
    
    return $estimated_time;
}

קריאה לפונקציה של התוסף מתוך תבנית וורדפרס

כעת עלינו לקרוא מתוך התבנית שלנו לפונקציה המציגה את משך זמן הקריאה. מאחר שהקוד לא כתוב בתבנית אלא בתוסף נקרא לפונקציה מתוך התבנית בזהירות ונבדוק שהפונקציה קיימת לפני שננסה לקרוא לה:

if ( function_exists( 'display_post_time_estimated_reading_time' ) ) {
    echo display_post_time_estimated_reading_time();
 }

בונוס – תוסף OOP

מה יותר טוב מתוסף? תוסף שכתוב באופן מונחה עצמים! גם את זה נעשה במספר דקות. עיקר הפעולות שיש לבצע הינן:

  1. לבחור שם לקלאס, במקרה שלנו הוא יהיה:class SavvyEstimatedReadingTime. (שימו לב להתחיל את שם הקלאס באות גדולה).
  2. ליצור פונקציית init ובה להגדיר את ה-action שיקרא לפונקציית ה-load_textdomain(רציתי לעשות זאת בעזרת constructor אך קראתי כאן שזו אינה התנהגות מומלצת מאחר שהיא קושרת את הקלאס בצורה הדוקה מדי לוורדפרס):
    public function init() {
         add_action( 'init', array($this, 'load_textdomain') );
    }
    
  3. להעתיק את הפונקציה מהקוד ממאמר זה או מהמאמר המקורי (עם או בלי התרגומים) לתוך הקלאס:
    class SavvyEstimatedReadingTime {
    
        public function init() {
            add_action( 'init', array($this, 'load_textdomain') );
        }
    
        /**
         * Load plugin textdomain.
         */
         public function load_textdomain() {
             load_plugin_textdomain( 'savvy-estimated-reading-time', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
         }
    }
    
    $savvyEstimatedReadingTime = new SavvyEstimatedReadingTime();
    $savvyEstimatedReadingTime->init();
    
  4. כדי להריץ זאת יש לקרוא לפונקציה בצורה הבאה, הנקודתיים אגב הן מפני שהפונקציה היא סטטית ואנו קוראים לה דרך המחלקה ולא דרך אובייקט:
    <?php  if ( method_exists( 'SavvyEstimatedReadingTime', 'estimated_reading_time' ) ) {
            echo SavvyEstimatedReadingTime::estimated_reading_time();
    } ?>
    

למען הנוחות הנה הקוד המלא של התוסף בגירסת ה OOP:

<?php

/*
  Plugin Name: Savvy Estimated Reading Time
  Plugin URI:
  Description:
  Version:
  Author:
  Author URI:
  License:
  License URI:
 */

class SavvyEstimatedReadingTime {

    public function init() {
        add_action( 'init', array($this, 'load_textdomain') );
    }

    /**
     * Load plugin textdomain.
     */
    public function load_textdomain() {
        load_plugin_textdomain( 'savvy-estimated-reading-time', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
    }

    /**
     * Estimate time to read the article
     *
     * @return string
     */
    public static function estimated_reading_time() {

        $post = get_post();

        //$words = str_word_count( strip_tags( $post->post_content ) );
        $words = count( preg_split( '/\s+/', strip_tags( $post->post_content ) ) );
        //echo $words. ' ';
        $minutes = floor( $words / 200 );
        $seconds = floor( $words % 200 / ( 200 / 60 ) );

        if ( 1 <= $minutes ) {
            $estimated_time = sprintf( _n( 'One minute', '%1$s minutes', $minutes, 'savvy-estimated-reading-time' ), number_format_i18n( $minutes ) );
            if ( 1 <= $seconds ) {
                $estimated_time .= __( ' and ', 'savvy-estimated-reading-time' );
                $estimated_time .= sprintf( _n( 'One second', '%1$s seconds', $seconds, 'savvy-estimated-reading-time' ), number_format_i18n( $seconds ) );
            }
        } else {
            $estimated_time = sprintf( _n( 'One second', '%1$s seconds', $seconds, 'savvy-estimated-reading-time' ), number_format_i18n( $seconds ) );
        }

            return $estimated_time;
    }

}

$savvyEstimatedReadingTime = new SavvyEstimatedReadingTime();
$savvyEstimatedReadingTime->init();

והנה קישור להורדת התוסף עצמו בשביל הנוחות. עד כאן מה שתכננתי לעשות, וזה הסוף.

נספח: קשה להיות שֵמי

תוך כדי שערכתי את השינויים הנ"ל בדקתי את הקוד וגיליתי למרבה ההפתעה שהקוד במאמר המקורי אינו עובד כמו שצריך בעברית (הסירו דאגה מליבכם: הקוד שכאן הוא אחרי תיקון). הפונקציה שנעשה בה שימוש בקוד המקורי וחתכה את הטקסט למילים היתה str_word_count ומסתבר שזו אינה מחלקת נכון את המילים.

מאחר שיצא לי להשתמש בפונקציות מחרוזת של PHP באתרים בעברית ידעתי שלכל פונקציית מחרוזת יש מקבילה שמטפלת בתווים שהם לא ascii ולהם קידומת mb_ (מלשון Multi Byte). מיד חיפשתי לראות אם יש פונקציה מקבילה ל-str_word_count וגיליתי כי אין פונקציה כזו ואף ישנה בקשה שטרם נענתה מלפני חמש שנים ליצור כזו. למרבה השמחה יש פתרון אחר והוא חיתוך באמצעות ביטוי רגולרי.

מובן שמיד הבאתי את הדברים לידיעתו של רועי והוא שיתף פעולה בצורה משמחת מאוד. שאפו.

מאמר זה נכתב ע״י לאה כהן. אם אתם מעוניינים לכתוב עבור סאבי בלוג קראו את ההנחיות של כתיבת פוסט אורח.

 

לאה כהן
לאה כהן

מתכנתת אינטרנט. מתמחה בהדיפת באגים למרחקים ארוכים ואוהבת שעוזבים אותי במנוחה (מלבד המשפחה שלי כמובן). בעלת הבלוג ליקוטי שיבולים.

3תגובות...
  • חתול 9 בינואר 2018, 15:15

    כיף לקרוא פוסט אורח שלך.
    למה הפונקציה סטטית?
    אם כבר יש תוסף, כדאי לעלות אותו לאתר התוספים. זה יקל על ההתקנה וגם יגדיל את החשיפה אליו.

    • לאה 9 בינואר 2018, 19:28

      חתול, כיף לשמוע ממך גם כאן. תודה על הפרגון!

      החלטתי על פונקציה סטטית כי זה חוסך ליצור אובייקט, ואז הקריאה לפונקציה היא רק 2 שורות קוד (בדיקה וקריאה) במקום 3 (בדיקה אם האובייקט קיים, בדיקה אם המתודה קיימת, וקריאה. שלא לדבר על מצב שהאובייקט לא קיים ואז צריך: בדיקה אם המחלקה קיימת, יצירת אובייקט, בדיקה אם המתודה קיימת, וקריאה). לא יודעת אם זו סיבה טובה.

      לגבי התוסף – הוא של רועי לכן אתן לו את הכבוד לענות 🙂 .

    • רועי יוסף 9 בינואר 2018, 19:48

      תודה חתול! לגבי התוסף פשוט אין לי את הזמן….

השאירו תגובה

פעימות