Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WordPress Plugins und Themes übersetzbar machen...

WordPress Plugins und Themes übersetzbar machen - WP Camp 2012 Berlin

WordPress Plugins und Themes übersetzbar machen - die Grundprinzipien, Grundfunktionen, hilfreiche Tipps, Tricks, Erfahrungen, häufige Fehler und Best Practices

David Decker

October 13, 2012
Tweet

More Decks by David Decker

Other Decks in Programming

Transcript

  1. David Decker · deckerweb.de · WP Camp 2012 WordPress Plugins

    und Themes übersetzbar machen Session: define( 'WPLANG', 'de_DE' ); $wp_local_package = 'de_DE';
  2. David Decker · deckerweb.de · WP Camp 2012 Themes &

    Plugins nicht übersetzbar... nur halbgar übersetzbar... Ursachen: Fehlendes Wissen Keine Lust, beratungsresistent... Nur Fokus auf USA... Keine Tests durchgeführt Eigene Implementationen jenseits der Standards
  3. David Decker · deckerweb.de · WP Camp 2012 Selbst Hand

    anlegen WordPress bringt alles mit! via PHP Gettext Gettext Funktionen Textdomain laden: allgemein Plugins Themes Child Themes
  4. David Decker · deckerweb.de · WP Camp 2012 Einpflegen? Nützlich

    bei allen öffentlichen Repo-Projekten GitHub.com - simpel & elegant! WordPress.org: Entwickler kontaktieren Ziel: Nutzen für alle!
  5. David Decker · deckerweb.de · WP Camp 2012 Implementieren Textdomain

    laden: load_textdomain() load_plugin_textdomain() load_theme_textdomain() load_child_theme_textdomain() Für Text-Strings: Regulär: __() _e() _x() _ex() _n() Spezial: _n_noop() translate_nooped_plural() _nx_noop() number_format_i18n() date_i18n() Sicherheit: esc_html__() esc_html_e() esc_html_x() esc_attr__() esc_attr_e() esc_attr_x()
  6. David Decker · deckerweb.de · WP Camp 2012 Plugins: dein-plugin.php

    Kopfbereich, Metadaten: /** * Text Domain: dein-plugin-name-slug * Domain Path: rel-sprachdatei-pfad */ add_action( 'init', 'ddw_wpcamp_plugin' ); function ddw_wpcamp_plugin() { load_plugin_textdomain( 'deine-textdomain', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); } Hook „init“ am besten (WMPL!), „plugins_loaded“ ginge wohl auch evtl. Priorität ändern! (experimentieren!) Zusatz: dirname( plugin_basename( __FILE__ ) ) . '/languages/' könnte gefiltert werden (bei Plugins relative Pfade beachten!)
  7. David Decker · deckerweb.de · WP Camp 2012 Themes: style.css

    Kopfbereich, Metadaten: /* Text Domain: dein-theme-name-slug Domain Path: rel-sprachdatei-pfad */ add_action( 'init', 'ddw_wpcamp_theme' ); function ddw_wpcamp_theme() { load_theme_textdomain( 'deine-textdomain', get_template_directory() . '/languages/' ); } Hook „init“ am besten (WMPL!), „after_setup_theme“ evtl. auch evtl. Priorität ändern! (experimentieren!) Zusatz: get_template_directory() . '/languages/' sollte gefiltert werden
  8. David Decker · deckerweb.de · WP Camp 2012 Child Themes:

    style.css Kopfbereich, Metadaten: /* Text Domain: dein-child-theme-slug Domain Path: rel-sprachdatei-pfad */ add_action( 'init', 'ddw_wpcamp_child_theme' ); function ddw_wpcamp_child_theme() { load_child_theme_textdomain( 'deine-child_textdomain', get_stylesheet_directory() . '/languages/' ); } Hook „init“ am besten (WMPL!), „after_setup_theme“ evtl. auch evtl. Priorität ändern! (experimentieren!) Zusatz: get_stylesheet_directory() . '/languages/' könnte gefiltert werden
  9. David Decker · deckerweb.de · WP Camp 2012 Themes: Sprachdatei-Ordner

    filtern /** Set filter for parent themes' languages directory */ $parent_theme_lang_dir = get_template_directory() . '/languages/'; $parent_theme_lang_dir = apply_filters( 'parent_theme_lang_dir', $parent_theme_lang_dir ); add_action( 'init', 'ddw_wpcamp_theme' ); function ddw_wpcamp_theme() { load_theme_textdomain( 'deine-textdomain', $parent_theme_lang_dir ); }
  10. David Decker · deckerweb.de · WP Camp 2012 Plugins vs.

    Themes: Dateinamen der Sprachdateien Themes: de_DE.mo /.po nl_NL.mo /.po fi.mo /.po Plugins: deine-textdomain-de_DE.mo /.po deine-textdomain-nl_NL.mo /.po deine-textdomain-fi.mo /.po Lokale ermitteln: schau bei WPLANG in der wp-config.php! Oder hier recherchieren: http://codex.wordpress.org/WordPress_in_Your_Language
  11. David Decker · deckerweb.de · WP Camp 2012 Themes vs.

    Child Themes get_template_directory() vs. get_stylesheet_directory() load_textdomain() vs. load_child_theme_textdomain() (Child) Themes funktionieren aber auch mit load_textdomain() Streitfrage: Eigene Textdomains für Child Themes JA oder NEIN? DECKERWEB: JA, bitte eigene Domain für Child Themes! = klare Trennung = klarere Organisation = einfachere Pflege (Kundenprojekte...!) plus bessere Performance
  12. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele I

    Korrekt: Templates: <?php _e( 'Germany consists of 16 federal states', 'textdomain' ); ?> In Funktionen: echo __( 'Germany consists of 16 federal states', 'textdomain' ); $string = __( 'Germany consists of 16 federal states', 'textdomain' );
  13. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele II

    Falsch: $string = __( $string, 'textdomain' ); $string = __( "Germany consists of $number federal states", 'textdomain' ); $string = __( 'Germany consists of 16 federal states', $text_domain ); $string = __( 'Germany consists of 16 federal states', PLUGIN_DOMAIN ); $string = __( 'Germany consists of ', 'textdomain' ) . $number . __( ' federal states', 'textdomain' ); Immer: Single Quotes! EINE (1) Textdomain: String in Single Quotes
  14. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele III

    Erweitert: $string = sprintf( __( 'Germany consists of %d federal states', 'textdomain' ), $number ); $string = sprintf( _n( 'Germany consists of %d federal state', 'Germany consists of %d federal states', $number, 'textdomain' ), $number ); $string = sprintf( __( 'I have %d bikes and %d cars', 'textdomain' ), $bike_count, $car_count ); $string = sprintf( __( 'I have %1$d bikes and %2$d cars', 'textdomain' ), $bike_count, $car_count ); Hinweis: "%1$s" macht Probleme (PHP: $s !!!), daher Single Quotes
  15. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele IV

    Kontext: $string = _x( 'Frankfurt', 'German city at the river Main', 'textdomain' ); $string = _x( 'Frankfurt', 'German city at the river Oder', 'textdomain' ); $string = _x( 'Frankfurt', 'an asteroid', 'textdomain' );
  16. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele V

    Formatierungen: $string = sprintf( __( '<h1>I want %d Hefeweizen</h1>', 'textdomain' ), $number ); $string = '<h1>' . sprintf( __( 'I want %d Hefeweizen', 'textdomain' ), $number ) . '</h1>'; $string = sprintf( __( 'I want %s Hefeweizen', 'textdomain' ), '<strong>' . $number . '</strong>' ); Grundregel: KEINE oder so WENIG HTML-Formatierungen wie irgend möglich in den Übersetzungs-Strings!
  17. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele VI

    Sicherheit: Benutzereingaben: esc_html__() esc_html_e() esc_html_x() Ausgaben von Text in HTML-Attributen: esc_attr__() esc_attr_e() esc_attr_x()
  18. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele -

    Spezial I Numerical No-op: $string = sprintf( _n( 'I have %d bike.', 'You have %d bikes.', $number, 'textdomain' ), $number ); $bikes_plural = _n_noop( 'I have %d bike.', 'You have %d bikes.', 'textdomain' ); $string = sprintf( translate_nooped_plural( $bikes_plural, $number ) , $number );
  19. David Decker · deckerweb.de · WP Camp 2012 Syntaxbeispiele -

    Spezial II Zahlen & Daten: number_format_i18n() date_i18n()
  20. David Decker · deckerweb.de · WP Camp 2012 Häufige Fehler:

    Gar keine Übersetzbarkeit Übersetzbarkeit JA, aber kein „load_textdomain“ Mischen von Single & Double Quotes HTML in den Übersetzungs-Strings Falscher Einsatz von Platzhaltern/ Variablen Zu spätes Laden/ Einhängen der Sprachdatei, bzw. vergessen bei Fehlermeldungen/ Aktivierungs-Hooks etc. Eigene „Erfindungen“ fürs Laden der Sprachdateien Keine Verwendung von Kontext, Datum, Plural etc.
  21. David Decker · deckerweb.de · WP Camp 2012 Best Practices

    I ALLES Übersetzbar machen! Laden der Sprachdateien via Hook! Textdomain als String in Single Quotes setzen Generell nur Single Quotes HTML-Formatierungen raus aus den Strings!!! Beliebige Anordnung der Platzhalter ermöglichen via %1$d, %2$d etc. Sicherheit: ESCAPEN bei Benutzereingaben und bei Ausgaben in Attributen!
  22. David Decker · deckerweb.de · WP Camp 2012 Best Practices

    II Pfadangaben checken! load_textdomain() fordert absoluten Pfad load_plugin/theme/child_theme_textdomain() fordern relativen Pfad BONUS 1: Sprachdatei-Ordner filterbar machen bzw. Zusätzlichen Ort für Update-sichere Sprachdateien bereitstellen BONUS 2: Ein GlotPress aufsetzen, um der Community das Übersetzen zu erleichtern... ;-)
  23. David Decker · deckerweb.de · WP Camp 2012 Werkzeuge: define(

    'WP_DEBUG', true ); (wp-config.php) define( 'WPLANG', 'de_DE' ); (wp-config.php) Plugin: „Codestyling Localization“ (zum eigentlichen Übersetzen) Software/ Installation: GlotPress (übersetzen, Export/Import .mo/.po, Verwalten) Lokale Software: PoEdit Editor (übersetzen, validieren -- gut für Platzhalter-Tests!) Testen! Testen! Testen!
  24. David Decker · deckerweb.de · WP Camp 2012 Verwendete Quellen:

    http://ottopress.com/2012/internationalization-youre-probably-doing-it- wrong/ http://ottopress.com/2012/more-internationalization-fun/ http://genesisthemes.de/en/2011-12/seven-cardinal-sins-localizing- wordpress-plugins-themes/ http://codex.wordpress.org/I18n_for_WordPress_Developers http://pippinsplugins.com/localizing-and-translating-wordpress-plugins/ http://remkusdevries.com/how-to-use-glotpress-for-your-translations/
  25. David Decker · deckerweb.de · WP Camp 2012 David Decker

    @deckerweb +David Decker deckerweb.de deckerweb.de/sprachdateien translate.wpautobahn.com Danke! Fragen jetzt! Folien unter: deckerweb.de/wpcamp2012 plus: slideshare.net/deckerweb
  26. David Decker · deckerweb.de · WP Camp 2012 /** Call

    for break and add next session */ add_action( 'wpcamp_berlin_2012', 'wpc_next_session_planning' ) function wpc_next_session_planning() { if ( is_break( array( 10min, coffee, wc ) ) ) { wpcamp_do_next_session(); echo __( 'Enjoy the break', 'wpcamp' ); } else { wpcamp_do_celebrate_community(); echo __( 'Enjoy the conversation', 'wpcamp' ); } }