Pages

Σάββατο 21 Σεπτεμβρίου 2013

Πολυγλωσσικές αναρτήσεις στο blogger.comMultilingual posts on blogger.com

Ξεκίνησα το blog μου στο blogger τον Αύγουστο του 2009. Όταν ξεκίνησα αγνοούσα τη δυνατότητα του να έχω πολυγλωσσικές αναρτήσεις. Πίστευα πως όταν χρειαζόταν να κάνω μια ανάρτηση σε άλλη γλώσσα από τη μητρική μου, απλά θα την έκανα. Η έννοια του να έχω μια ανάρτηση σε δύο γλώσσες δεν ήταν κάτι που με απασχολούσε. Λάθος μου!

I started my blog in blogger on August 2009. In the beginning I totally ignored the ability to make multilingual posts. I believed that when I needed to write something in a different language than my native one, I would just use that language. The thought of having a post written in two different languages was something that didn't even pass through my mind. I was wrong!

Όταν άρχισαν να διαβάζουν τις αναρτήσεις μου ακόμα και τα ανήψια μου, εκεί κατάλαβα πόσο σημαντικό είναι οι αναρτήσεις μου να είναι στα Ελληνικά. Ταυτόχρονα, οι αναρτήσεις θα έπρεπε να είναι και στα Αγγλικά (δυστυχώς δε γνωρίζω άλλη γλώσσα) διότι αφορούν τεχνολογικό περιεχόμενο. Συνεπώς, καλό είναι να μπορούν να το καταλάβουν και άλλοι αναγνώστες οι οποίοι ενδιαφέρονται για το αντικείμενο που περιγράφεται, εκτός Ελλάδας. Παράδειγμα υπήρξε ακόμα και στις λίστες του FreeBSD όπου έκανα αναφορά σε ένα how-to που έχω γράψει, το οποίο όμως είναι γραμμένο στα Ελληνικά. Ο ενδιαφερόμενος φυσικά και δυσκολεύτηκε να πετύχει αυτά που περιέγραφε το άρθρο γιατί ήταν χωμένος μέσα σε εντολές κονσόλας και σε... μεταφραστικό...

When even my siblings started to read my posts, I understood the importance of my posts to be written in Greek. At the same time some posts should be also in English (unfortunately I don't speak any other language than these two) because they are on technological aspects. So, it is a good thing for other readers, others than Greek, that are interested in the subject of the post, to be able to understand it. An example was on FreeBSD lists when I referenced an article of mine (a how-to) that it was, unfortunately, written in Greek. It was hard for the guy that was interested in the topic to achieve the goal of the post because he fell into terminal commands and... a translator...

Η απόφαση του να προσπαθήσω να κάνω το blog μου δίγλωσσο δεν άργησε (αντίθετα με την υλοποίησή του...).

The decision to convert my blog to multilingual was not that far (as opposed to its implementation...).

Τι θα δούμε σε αυτό το άρθρο

What's in this article

Σε αυτό το άρθρο θα παρουσιαστούν τα ακόλουθα κομμάτια:

In this article the following parts will be presented:

Πολυγλωσσική λειτουργία - βασικές ανάγκες

Multilingual Function - Basic Functionality

Για να μπορέσει να γίνει σωστά η λειτουργία της πολυγλωσσικής μορφής ενός άρθρου θα πρέπει το σύστημα τα ακολουθεί κάποιους κανόνες:

For the multilingual form of a post to be correct the system should follow some rules:

  • Όταν κάποιος επισκέπτεται τη σελίδα για πρώτη φορά, το σύστημα θα πρέπει να αποφασίζει σε ποια γλώσσα θα δείξει τα άρθρα.
  • When somebody visits the web page of the blog for the first time the system should decide what will be the presentation language.
  • Θα πρέπει ο αναγνώστης να μπορεί να αλλάξει γλώσσα εύκολα.
  • The reader must be able to change the language presented, easily.
  • Θα πρέπει να μπορεί το σύστημα να καταλάβει σε ποιες γλώσσες είναι γραμμένο το άρθρο, ώστε να δίνονται οι αντίστοιχες επιλογές στον αναγνώστη.
  • The system should be able to understand what are the available languages of the post, so only them should be presented to the reader as options.
  • Όταν ο αναγνώστης κάνει μια επιλογή μιας γλώσσας, το σύστημα θα πρέπει να τη θυμάται.
  • When the reader makes a language choice, the system should remember it.
  • Όταν ο αναγνώστης επιστρέψει μια άλλη μέρα στο blog θα πρέπει η πρώτη επιλεγμένη γλώσσα να είναι αυτή που χρησιμοποιήθηκε τελευταία φορά.
  • When the reader returns to the blog another day, the presentation language should be the one he last used.
  • Το σύστημα να μπορεί να διαχειριστεί άρθρα τα οποία είναι γραμμένα μόνο σε μια γλώσσα.
  • The system should be able to handle posts that are written in only one language.
  • Θα πρέπει να υπάρχει link parameter που να υποχρεώνει την εμφάνιση ενός άρθρου/άρθρων σε συγκεκριμμένη γλώσσα.
  • There must be a URL link parameter that should force the presentation of a post/article in a specific language.

Οι δύο βασικοί τρόποι για να συμβεί κάτι τέτοιο είναι:

The two basic ways of implementing such functionality are:

  1. Συγγραφή ξεχωριστού άρθρου για κάθε γλώσσα και χρήση links που να οδηγούν από τη μια γλώσσα στην άλλη
  2. Write of a new post for every language and usage of links to move from one language to the other
  3. Χρήση JavaScript η οποία να εμφανίζει τα στοιχεία HTML της επιλεγμένης γλώσσας και να εξαφανίζει αυτά που ανήκουν στις άλλες.
  4. JavaScript usage to display HTML elements that belong to the selected language and hide the others that belong to others.

Ο κάθε τρόπος έχει τα υπέρ και τα κατά του. Ο τρόπος που προτιμάει να χρησιμοποιήσει ο καθένας είναι καθαρά αντικειμενικός. Σε αυτό το blog χρησιμοποιείται ο δεύτερο· χρήση JavaScript.

Each way has its own pros and cons. The choise of the implementation way is just a matter of preference. In that blog we use and describe the latter; JavaScript usage.

Επιλογή Γλώσσας

Language Selection

Για να υλοποιηθεί η διαδικασία της πολυγλωσσικότητας πρώτα απ' όλα θα πρέπει να υπάρχει ο κατάλληλος τρόπος για την επιλογή μιας από τις διαθέσιμες γλώσσας από τον χρήστη. Η εμφάνιση μιας σημαίας για κάθε διαθέσιμη γλώσσα είναι κάτι βολικό για μια τέτοια λειτουργία. Το σημείο και ο τρόπος με τον οποίο θα εμφανίζεται η εν λόγω επιλογή είναι καθαρά θέμα του συγγραφέα του blog στο οποίο θα εφαρμοστεί η πολυγλωσσικότητα. Άλλη μια ιδέα για την επιλογή είναι η εμφάνιση μιας λίστας με τις διαθέσιμες γλώσσες μέσα από την οποία ο χρήστης θα επιλέγει αυτή που επιθυμεί.

For the multilanguage process to be implemented, first of all, there must be a propper way of reader selecting one of the available languages for the post. The presentation of a small flag for each available language option is very convenient for that purpose. The layout, the way of presentation and the part of the page that will host the language selection is only a matter of preference of the blog's writer. Another idea is to present a dropdown list of the available languages through which the user can apply his/her language choise.

Στο παρόν blog κρίθηκε κατάλληλο το να εμφανίζονται όλες οι διαθέσιμες επιλογές επάνω στον τίτλο του κάθε άρθρου, έτσι ώστε ο αναγνώστης να βλέπει αμέσως τις διαθέσιμες επιλογές. Κρίθηκε, επίσης, σκόπιμο η γλώσσα στην οποία εμφανίζεται το άρθρο να μην υπάρχει στις διαθέσιμες επιλογές (μιας και δεν υπάρχει νόημα στο να επιλέξει κανείς μια γλώσσα την οποία ήδη χρησιμοποιεί).

At the current blog the decision was to present all the available language options at the right of the post title, so every reader can see at once what are the options he/she has. Also, the flag of the currently presented language not to be presented (as there is no meaning to select a language that is already in use).

Και πως μπορεί να ελέγχεται ποια κομμάτια θα εμφανίζονται, ώστε κάθε φορά με την επιλογή μιας γλώσσας να εμφανίζονται μόνο τα επιθυμητά; Η λύση βρίσκεται στη δυνατότητα της HTML να μπορεί να περιέχει κρυφά και φανερά κομμάτια. Αν με κάποιο τρόπο σηματοδοτήσουμε ότι μια παράγραφος ανηκει στο Ελληνικό κείμενο και μια άλλη ανήκει σε κείμενο μιας άλλης γλώσσας, τότε σαρώνωντας τα δομικά στοιχεία της σελίδας θα μπορούν να αποκρύπτωνται τα στοιχεία που ανήκουν σε γλώσσα εκτός της επιλεγμένης και να εμφανίζονται μόνο αυτά που ανήκουν στην επιλεγμένη. Τα στοιχεία τα οποία δεν έχουν τέτοια σηματοδότηση θα εμφανίζονται πάντα. Ο τρόπος κατά τον οποίο γίνεται εύκολη αυτή η διεργασία της σηματοδότησης είναι, ποιος άλλος, η εφαρμογή μιας κλάσης με το όνομα της γλώσσας στην οποία ανήκει το δομικό στοιχείο. Έτσι, μια παράγραφος που ανήκει στην κλάση "el" ανήκει στο Ελληνικό κείμενο, ενώ μια άλλη που ανήκει στην κλάση "en" θα ανήκει στο Αγγλικό κείμενο.

And how is it possible to control which parts of the post will be visible, so each time a language selection is made, only the propper parts will be presented? The solution lies in the HTML ability of containing non visible elements in a page. If, somehow, there is a kind of note in a paragraph element that it belongs to the Greek text and another note in another pragraph that it belongs to some other language's text, then by scanning all the page's elements the ones that belong to some other language could be forced invisible, and leave visible only the ones of the reader's choice. Every other elements that do not contain such a note will be always visible. The way to apply such a note in every wanted element is, which else, the application of a class with the name of the valid language descriptor. So, if one paragraph belongs to the HTML class "el", it belongs to Greek text, while anothe that belongs to HTML class "en", belongs to English text.

Και πως γίνεται να προσθέσουμε τα δομικά στοιχεία και τον κώδικα που θα εκτελεί όλες τις λειτουργίες; Πρώτα θα πρέπει να δούμε τι εργαλεία υπάρχουν για εφαρμογή θέματος στη σελίδα του blog μας.

And how can we add the bulding code blocks that will perform such functionality? First, we have to see what kind of tools we have for applying a template to our blog.

Εφαρμογή Προτύπου

Apply Template

Το blogger.com μας δίνει τη δυνατότητα εφαρμογής προτύπων στη σελίδα μας. Δε θα μπορούσε να είναι σελίδα κατασκευής blogs αν δεν υπήρχε αυτή η δυνατότητα. Φυσικά, κάθε πρότυπο έχει και τη δική του δομή. Σε γενικές γραμμές, ο τρόπος που περιγράφεται εδώ είναι ίδιος με αυτόν που μπορείτε να ακολουθήσετε και σε blog με το δικό σας πρότυπο. Λίγο ο κώδικας του προτύπου, λίγο ο firebug, μπορούμε να βρούμε τα δομικά στοιχεία και τον τρόπο με τον οποίο θα κάνουμε τη παρέμβασή μας.

blogger.com offeres the ability to apply templates on our blog page. It would not be a blog spot if it didn't. Of course, every template has its own structure. In general the way described here is the same as the one you have to follow for your blog page with a different template. Well, some template code understanding, some firebug usage, we can hack around and find the bulding blocks of the template and the way to make our intervention in order to achieve our goal.

Ξεκινάμε από την εφαρμογή του προτύπου που μας ενδιαφέρει. Κάνουμε login στο blog μας και εκεί έχουμε τη δυνατότητα να επιλέξουμε πρότυπο εμφάνισης.

Lets start from the application of the template we are interested in. Lets log into our blog; there we have the option to select a template.

Εδώ μπορούμε να κάνουμε επιλογή του προτύπου που μας ενδιαφέρει. Το βασικό, όμως, είναι πως έχουμε τη δυνατότητα να επέμβουμε στον κώδικά του με τη χρήση του πλήκτρου "Επεξεργασία HTML". Με τη χρήση αυτού του πλήκτρου εμφανίζεται μπροστά μας όλος ο κώδικας που φτιάχνει την εμφάνιση της σελίδας μας.

Here is the place to choose the template we like. But, the most important thing is the access to the template's code, using the "Edit HTML" button. Using that we are able to alter the code that forms the visual part of our blog page at will.

Δομικά Στοιχεία

Structural Components

Κοιτάζοντας λίγο τον κώδικα του προτύπου μας, μπορούμε να δούμε όλα τα gadgets της σελίδας μας. Εκεί, μπορούμε να δούμε ότι η λίστα με τις αναρτήσεις αποτελείται από:

Looking around at our template's code we can find all the gadgets that are included in our page. Ther we can also find the structure of our posts. It is formed as described:

  • Στοιχείο HTML <div> που ανήκει στην κλάση blog-posts. Αυτό περιέχει όλες τις αναρτήσεις που εμφανίζονται στην ιστοσελίδα.
  • A <div> HTML element that belongs to blog-posts class. It contains all the posts that appear on the HTML page.
  • Μέσα σε αυτό περιέχειται ένα <div> που ανήκει στην κλάση date-outer. Αυτό φιλοξενεί τις αναρτήσεις μιας ημέρας.
  • In there, there is another <div> that belongs to date-outer class. It hosts all the posts of the same day.
  • Το τμήμα των αναρτήσεων μιας ημέρας αποτελείται από μια επικεφαλίδα (στη δικιά μας περίπτωση <h2> που ανήκει στην κλάση date-header) που δηλώνει την ημερομηνία που έγιναν οι ακόλουθες αναρτήσεις και ένα <div> που ανήκει στην κλάση date-posts. Εκεί μέσα είναι που θα φιλοξενηθούν οι αναρτήσεις της ίδιας ημέρας.
  • The part of a day's posts is formed by a heading element (in our case it is a <h2> tag tha belongs to date-header class) presenting the date of the posts and a <div> element that belongs to date-posts class. The latter hosts all the posts of that day.
  • Κάθε ανάρτηση είναι από μόνη της ένα <div> που ανήκει στην κλάση post-outer και περιέχει δύο <div> στοιχεία, ένα που περιέχει την ανάρτησή μας (ανήκει στην κλάση post) κι ένα που περιέχει τα σχόλια και τη φόρμα ανάρτησης σχολίων από τους αναγνώστες (ανήκει στην κλάση comments). Αυτό που μας ενδιαφέρει, φυσικα, είναι το πρώτο από τα δύο.
  • Each post is a single <div> that belongs to post-outer class and contains two other <div> elements, one for the post's text (it belongs to post class) and one containing the comments of the readers and a form for comments posting (it belongs to comments class). We are intrested on the first of these two, of course.
  • Κάθε ανάρτησή μας αποτελείται από τέσσερα βασικά τμήματα. Το πρώτο είναι ο τίτλος, το δεύτερο είναι κάποιου είδους επικεφαλίδα (στη δικιά μας περίπτωση είναι πάντα κενό), το τρίτο είναι το κείμενο της ανάρτησης και το τελευταίο είναι το τέλος της ανάρτησης που περιέχει κοινά στοιχεία σε όλες τις αναρτήσεις, όπως ονομα του συγγραφέα, εικονίδια για διαμοιρασμό της ανάρτησης σε facebook, google+, κ.λ.π.
  • Each post is formed by four parts. The first is the title, the second is the post's header (in our case it is always empty), the third part is the text of our post and the last one is the post's footer that contains common things to all the posts, like the author of the post, some post sharing buttons to social networks like facebook, google+, etc.
  • Ο τίτλος της ανάρτησης είναι κάτι που μας ενδιαφέρει. Είναι ένα HTML στοιχείο τύπου <h3> που ανήκει στην κλάση post-title. Μας ενδιαφέρει γιατί εκεί θα ενσωματωθούν οι ενδείξεις για τις διαθέσιμες γλώσσες. Επίσης, από τον τίτλο θα μπορεί να καταλάβει ο κώδικας JavaScript και ποιες είναι αυτές οι διαθέσιμες γλώσσες στις οποίες έχει γραφτεί η ανάρτηση.
  • The title of the post is something we are interested in very much. It is an HTML <h3> element that belongs to post-title class. In there will be inserted the necessary indications of the available languages. At the same time, the JavaScript code can understand what are the available languages of the post from the languages that are included in this title.
  • Το επόμενο κομμάτι της ανάρτησης που μας ενδιαφέρει είναι το HTML στοιχείο <div> που περιέχει το κυρίως κείμενο. Αυτό μας ενδιαφέρει, γιατί εκεί είναι που θα γίνεται η παρέμβαση της JavaScript για να εμφανίσει μόνο τα στοιχεία που ανήκουν στην επιλεγμένη γλώσσα. Το εν λόγω <div> ανήκει στην κλάση post-body.
  • The next part of the post that we are interested in, is the <div> HTML element that contains the main text. There is the most JavaScript code's intervention for showing only the elements that belong to the chosen language. The <div> in question belongs to post-body class.

Προσθήκη Κανόνων στο CSS

Adding CSS Rules

Η πρώτη προσθήκη που έχουμε να κάνουμε στον κώδικα είναι να προσθέσουμε κάποιους κανόνες στο CSS κομμάτι που δηλώνεται μέσα στο πρότυπο που χρησιμοποιούμε για την εμφάνιση της ιστοσελίδας μας. Όταν πατήσουμε το πλήκτρο "Επεξεργασία HTML" που φαίνεται και στο προηγούμενο στιγμιότυπο, κάτω από την προεπισκόπιση της σελίδας μας, μεταφερόμαστε στον κειμενογράφο όπου μπορούμε να "πειράξουμε" τον κώδικα του προτύπου.

The first addition we have to make to the template code is to add some rules in its CSS part. By pressing the "Edit HTML" button that we can see on the previous snapshot, under the template preview, we are transfered to an editor that we can alter the template's code.

Εκεί μπορούμε να βρούμε το σημείο στο οποίο βρίσκονται οι κανόνες CSS που στέλνονται για τη μορφοποίηση της σελίδας. Μια μικρή παρέμβαση που κάνουμε είναι η ακόλουθη:

We can find the part that there are a lot of CSS rules, already, formating our blog's page. A small addition we must make is the following:

...Some template code...
<b:skin>
...Some CSS code...

/*Added for multilingual support*/
.flag {
  height:24px;
  width:24px;
  margin-top:-3px;
  background-size:24px;
  float:right;
  cursor:pointer;
}

.flgen {
  background-image:url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhevngcbkeTVwuM79NIvECzw-c8ZDzPx1ueBR_uMxZT79MzzsyOCfd_XnzsChqU2F0sGclwpb5NMTite-y16niyB37Py5plX-SDq7hwsFOWEHkhvds6pdLgrzoIQVKSgtYfXluB47JVymTb/s320/United-Kingdom-flag-icon.png);
}

.flgel {
  background-image:url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMMfgCwXuktRQ6cQlLeCD76uvA_2aHdNmdTrHW-HnsspX4fccXA03KUanP8eI72HyzWk8gMrKp_Y9UntV45F2GGTiQSkfYUSeqSd-MeW_WTjU0xGMQEmH3nt0bJpQ0tkhyw315gEBFbFyZ/s1600/Greece-Flag2.png);
}

en {
  display:none;
}
//End of multilingual support edditions

...Some more CSS code...
</b:skin>
...Some more template code...

Εδώ χρειάζονται λίγες διευκρινήσεις σχετικά με το τμήμα του κώδικα που προσθέσαμε. Κάθε ένα σημαιάκι που θα εμφανίζεται για την επιλογή γλώσσας θα ανήκει στην κλάση flag η οποία ορίζει τα οπτικά χαρακτηριστικά, όπως το μέγεθος και το ότι θα εμφανίζεται στα δεξιά του τμήματος στο οποίο προστίθεται· κοινώς, στα δεξιά στον τίτλο του άρθρου. Για κάθε μια γλώσσα που χρησιμοποιούμε, δημιουργούμε από μια κλάση που έχει όνομα flg και το όνομα της γλώσσας. Για την Ελληνική γλώσσα, δηλαδή, η ονομασία της κλάσης είναι η flgel, ενώ για την Αγγλική (γενικά) θα είναι flgen. Αν υποστηρίζαμε και άλλη μια γλώσσα π.χ. την Ισπανική, τότε θα δημιουργούσαμε ακόμα μια κλάση με όνομα flges, μιας και το es είναι το χαρακτηριστικό της Iσπανικής γλώσσας. Κάθε μια από αυτές τις κλάσεις θα εφαρμόζεται στο αντίστοιχο ενδεικτικό σημαιάκι επιλογής γλώσσας, μαζί με την προηγούμενη κλάση που περιγράφηκε και ο μόνος κανόνας που περιλαμβάνεται είναι αυτός που θέτει ως υπόβαθρο την εικόνα της σημαίας που μας ενδιαφέρει να απεικονίζεται. Εδώ μπορείτε να αλλάξετε το URL της εικόνας που χρησιμοποιώ και να θέσετε το δικό σας. Τέλος, για κάθε άλλη γλώσσα, εκτός από αυτή που θέλουμε να είναι προεπιλεγμένη, δημιουργούμε μια κλάση με το όνομα της γλώσσας, όπου κάνουμε το συστατικό που ανήκει σε αυτή να μην εμφανίζεται στην οθόνη μας. Γι' αυτό το λόγο υπάρχει η κλάση en, μιας και η προεπιλεγμένη γλώσσα για το παρόν blog θεωρείται η Ελληνική· η Αγγλική αποκρύπτεται. Αν υποστηρίζαμε και την Ισπανική γλώσσα, μιας και δεν είναι η προεπιλεγμένη, θα έπρεπε να δημιουργήσουμε άλλη μια κλάση με το όνομα es που να περιέχει τον κανόνα απόκρυψης, δηλαδή το display:none.

Here we have to give some clarifications according to the part of the code we just added. Every little flag indicator for language selection will belong to flag class. This class sets the common optical characteristics of these flags, like the size, to be presented at the right side of the element that it is added to, meaning the right side of the title, cause that's were we are going to add it, etc. For each language we use, we create a class with name comprised of flg and the characteristic name of the language in question. Thus, for the Greek language the name of this class will be flgel, and for the English (general) will be flgen. If we support another language, lets say the Spanish, we should also create another class named flges, since es is the characteristic of this language. Each of these described classes will be applied to the corresponding indicator flag for this language selection, together with the previous one (flag class). The only rule they include is the one that sets the background image to the indication we like, meaning the flag image. Here you can set your URL instead of the one I use. Finally, for each other language we use except for the default one, we create a class with its characteristic name, were we make the elements belonging to that class, invisible. That is why there is a CSS rule for en class, since the default language in this blog is the Greek one; the English becomes invisible. If we also supported the Spanish language here, then we should create one more rule for es class making it invisible, also (having display:none).

Βασική Λειτουργία του Κώδικα JavaScript

Basic Functionality Of JavaScript Program

Ήρθε η ώρα να δούμε τι λειτουργίες και με ποια σειρά θα πρέπει να κάνει ο κώδικας της JavaScript. Εν συντομία έχουμε τα ακόλουθα βήματα:

Now it's time for the functionality of the JavaScript code we add, and in which order it must perform its functions. In short there are the following steps:

  • Ο κώδικας θα πρέπει να εκτελείται αμέσως μόλις φορτωθεί η σελίδα (HTML τμήμα· δε μας ενδιαφέρει να έχουν φορτωθεί και οι εικόνες, αλλά μόνο το DOM). Συνεπώς, ο κώδικας προστίθεται στο τέλος της σελίδας, πριν κλείσει το </body>.
  • The program must start its execution just after the page is loaded (HTML part· we don't care about extra files, like images etc., but only the DOM). That means that we have to add the code just before the closing </body> tag.
  • Το πρώτο πράγμα που πρέπει να γίνει στην εμφάνιση της σελίδας είναι η προσθήκη των διαθέσιμων γλωσσών στον τίτλο κάθε άρθρου. Για να μπορέσει να βρει ο κώδικας τις διαθέσιμες γλώσσες του ενός άρθρου, αρκεί να κοιτάξει τον τίτλο του και να δει σε ποιες γλώσσες είναι γραμμένος.
  • First thing the code must do is to add the language selection flags at the presentation of the page, just at the title of each post. For the code to be able to understand in which languages the post is available, it must look at the title and find out in which languages it is written.
  • Αν υπάρχει μόνο μια γλώσσα στον τίτλο του άρθρου, τότε ενεργοποιεί μόνο αυτή τη γλώσσα και στο κείμενό του. Χρήσιμο κυρίως για τα άρθρα που έχουν γραφτεί παλιότερα και γίνεται τώρα η μετάφρασή τους. Κατά τη διάρκεια της μετάφρασης, το μεταφρασμένο κείμενο δεν εμφανίζεται, αν δε προστεθεί και ο τίτλος στη δεύτερη γλώσσα.
  • If there is only one available language in the title, then, for this post only, it must make it active. That is useful for the older posts that are not translated and they are in a translation phase. During this phase we do not want the partial translated text to be presented. We add the second language only when the translation phase is complete.
  • Αν δε βρεθεί καμιά γλώσσα στον τίτλο του άρθρου, τότε δε γίνεται καμιά επεξεργασία στο κείμενο.
  • If there is no language specified in thte title of the post, then the text is presented as is; without any processing.
  • Θα πρέπει να μπορεί να διαβαστεί μια παράμετρος GET (δηλαδή από τη γραμμή διεύθυνσης) για την επιθυμητή γλώσσα. Με αυτό τον τρόπο, αν κάποιος θέλει να στείλει ένα link σε κάποιον άλλο γι διαμοιρασμό του άρθρου που διαβάζει, μπορεί να προσθέσει την εν λόγω παράμετρο έτσι ώστε ο αποδέκτης να ενεργοποιήσει τη προτοποθετημένη γλώσσα αυτόματα. Αυτό βοηθάει και στην προεπισκόπιση του άρθρου κατά τη διάρκεια της συγγραφής του. Με τη προσθήκη της παραμέτρου στη URL της προεπισκόπισης μπορούμε να δουμε την προεπισκόπιση του άρθρου σε μια άλλη γλώσσα.
  • There must be a GET parameter (in the URL address of the post) to force the presentation in a specific language. In that way, if someone wants to share a link of the post with a friend, he/she can add the parameter in question so the receiver will read the article in that language automatically. This is also helpful in the post preview during a post editing. By adding the parameter in the preview URL you can preview the post in the willing language.
  • Για να μπορεί να θυμάται το σύστημα την επιλογή γλώσσας του αναγνώστη θα πρέπει να γίνει χρήση Cookies.
  • For the system to be able to remember the language choice of the reader, it must use Cookies.
  • Θα πρέπει να μπορεί να διαβαστεί η επιλογή γλώσσας που έχει γίνει από τον φυλλομετρητή του αναγνώστη.
  • Also, there must be the ability to read the language settings of the reader's browser.
  • Αν από η γλώσσα που αποφασίστηκε να ακολουθηθεί δεν υπάρχει στο παρόν άρθρο τότε θα πρέπει να εμφανιστεί η προεπιλεγμένη. Το σύστημα θα πρέπει να λαμβάνει υπόψη του και την κοντινότερη γλώσσα επιλογής. Δηλαδή, αν η γλώσσα επιλογής είναι τα Αγγλικά Ηνωμένων Πολιτειών (με χαρακτηριστικό en_US), όταν αυτή δε βρεθεί, πρώτα θα πρέπει να ελέγχεται η ύπαρξη της κοντινότερης γλώσσας, δηλαδή η Αγγλική γενικότερα (με χαρακτηριστικό en).
  • If the reader's language decision ends to a language that is not used for the currently presented post, then the presented language must be the default one. But first, the system must take into account the existence of a closest language. To be more specific, if the user's choice is the United States English (with en_US literal characteristic), if this is not one of the post's languages, it must first check for the existence of the closest one, meaning the General English (with en literal characteristic).
  • Από τις σημαίες επιλογής γλώσσας πρέπει να αποκρύπτεται η γλώσσα στην οποία εμφανίζεται το άρθρο.
  • From the language selector indications, the post's currently presented one must become hidden.
  • Όταν ο χρήστης επιλέγει κάποια σημαία για αλλαγή γλώσσας εμφάνισης ενός άρθρου, θα πρέπει να ενημερώνεται το Cookie, να αποκρύπτεται η σημαία επιλογής, να εμφανίζεται η σημαία της προηγούμενης επιλογής, ενώ από το κείμενο να αποκρύπτονται όλα τα στοιχεία το οποία ανήκουν μόνο σε άλλες γλώσσες από την επιλεγμένη.
  • When the user makes a language choice, the cookie must be updated, the older hidden selector must become visible and the new one that becomes current must become hidden and, of cource, the post must become visible in the newlly selected language.

Κάτι που θα πρέπει να προσεχθεί είναι η χρήση της παραμέτρου display στα στοιχεία της ιστοσελίδας. Για κανονική εμφάνιση δεν αρκεί να πάρει την τιμή block. Κάποια στοιχεία για να εμφανιστούν σωστά πρέπει να έχουν άλλη τιμή, όπως τα στοιχεία <span> και τα στοιχεία <li>

Something that must be taken care of is the display CSS parameter of the page's elements. For normal appearance, not all of them must have the block value. Some, to be normally presented, must have a different value as happens for <span> and <li> elements.

Ο Κώδικας Βήμα Βήμα

The Code Step By Step

Ας δούμε τον κώδικα βήμα προς βήμα.

Lets walk through the code and examine it step by step.

Ορισμοί Γενικών Μεταβλητών
Global Variables

Ο κώδικας όπως προαναφέραμε προστίθεται ακριβώς πριν το κλείσιμο του tag </body>. Ας δούμε βήμα βήμα τον κώδικα. Ξεκινάμε απο τα "προκαταρκτικά"

As we mentioned earlier, the code we add lies just before the closing </body> tag of the page. Lets see this code step by step. We start from the "preliminary" part

...Previous Template Code...

<script type='text/javascript'>
  //Script for making the blog multilingual.
  var PREFEREDLANGS = &quot;el en&quot;;
  var LANGCOOKIENAME = &quot;echlang&quot;;
  var EXPIREDAYS = 7;
  var TITLES = {};
  TITLES[&quot;el&quot;] = &quot;Δείτε το άρθρο στα Ελληνικά&quot;;
  TITLES[&quot;en&quot;] = &quot;View the article in English&quot;;

...More code, will be presented later...

Πρώτα ορίζουμε μερικές μεταβλητές. Αυτές είναι που θα πειράξετε για να προσθέσετε γλώσσες και να παραμετροποιήσετε το πολυγλωσσικό σύστημα. Θα παρατηρήσατε, βέβαια, πως κάποιοι χαρακτήρες γράφονται με την HTML κωδικοποίηση, όπως π.χ. τα εισαγωγικά γράφονται με την έκφραση &quot;. Αυτό συμβαίνει γιατί στην ουσία πειράζουμε ένα αρχείο xml και αυτό θα πρέπει να μπορεί να το χειριστεί χωρίς προβλήματα ο xml parser του blogger.com. Για τα εισαγωγικά δεν τίθεται θέμα, μιας και από μόνο του το σύστημα τα μετατρέπει. Το πρόβλημα βρίσκεται σε άλλους χαρακτήρες, όπως π.χ. το '<' και το '>'. Αν αυτούς τους χαρακτήρες τους γράψουμε κανονικά και όχι με τη μορφή HTML, δηλαδή &lt; και &gt; αντίστοιχα, τότε το σύστημα θα παραπονεθεί.

First we define some variables. These are the ones you will have to alter to add some languages and use this multilingual system. I believe you noticed that some of the characters are written in their HTML notation, for example the quote characters are written as &quot;. This happens because what we alter here is the xml file of the template and the xml parser of blogger.com must be able to handle it without confusion. For the quotes there is no problem to write them without this notation, but for other characters as '<' and '>' this becomes a problem. If we use these characters not with their HTML notation (&lt; and &gt;, respectively) but straight then the system will thing it has a new tag and will complain.

Ας επανέλθουμε στο τμήμα του κώδικα που δώθηκε και ας δούμε μια προς μια τις μεταβλητές που ορίστηκαν:

Lets come back to the presented part of the code and see the global variables defined, one by one:

  • PREFEREDLANGS: Λίστα από τις υποστηριζόμενες γλώσσες. Η πρώτη είναι η βασική. Οι γλώσσες διαχωρίζονται από κενόSupported languages list. The first one is the default. The language literals are separated by a space character..
  • LANGCOOKIENAME: Το όνομα του Cookie που θα δημιουργείται. Αυτό θα περιέχει και τη προτίμιση της γλώσσας του χρήστηThe name of the Cookie to be created. It is going to store the reader's language choice.
  • EXPIREDAYS: Ο χρόνος διάρκειας του Cookie που δημιουργείται. Όταν ο αναγνώστης επισκέπτεται και πάλι τη σελίδα, το Cookie θα ανανεώνεται για άλλο τόσο χρονικό διάστημαThe duration time of the cookie. When the reader returnes to our blog some time after, its expiration time will be updated for as much time as it is defined here.
  • TITLES: Πρόκειται για ένα map. Ο λόγος ύπαρξής του είναι για το μικρό tooltip που θα εμφανίζεται όταν ο αναγνώστης αφήσει για λίγη ώρα τον κέρσορα του ποντικιού στο σημαιάκι επιλογής της αντίστοιχης γλώσσαςIt is a map type variable. The reason of its existence is for the text that appears in the small tooltip when the reader leaves his/her mouse pointer over the small indicator flag.
Προσθήκη Εικονιδίων Σημαίων Επιλογής Γλώσσας στον Τίτλο
Adding Small Selection Flags On Post Title

Ας δούμε το κομμάτι του κώδικα που προσθέτει τις σημαίες επιλογής γλώσσας:

The following is the part of the code that adds the small language selection flags:

...Previous JavaScript code...

  function setupFlags() {
    var availLangs = PREFEREDLANGS.split(&quot; &quot;);
    var headings = document.getElementsByClassName(&quot;post-title&quot;);
    var usedLangs = new Array();
    for(i=0; i&lt;headings.length; i++) {
      var tempHead = headings[i];
      usedLangs.length = 0;
      if(tempHead != undefined) {
        for(j=0; j&lt;availLangs.length; j++) {
          var tempElems = tempHead.getElementsByClassName(availLangs[j]);
          if(tempElems.length&gt;0) {
            usedLangs.push(availLangs[j]);
          }
        }
        if(usedLangs.length&gt;0) {
          for(j=0; j&lt;usedLangs.length; j++) {
            newDiv = document.createElement(&quot;div&quot;);
            newDiv.className = &quot;flag flg&quot; + usedLangs[j];
            newDiv.setAttribute(&quot;onclick&quot;, &quot;applyLang(&#39;&quot; +usedLangs[j]+ &quot;&#39;)&quot;);
            newDiv.title = TITLES[usedLangs[j]];
            tempHead.appendChild(newDiv);
          }
        }
      }
    }
  }

...More JavaScript code...

Στη γραμμή 4 φτιάχνεται ένας πίνακας με τις υποστηριζόμενες γλώσσες. Στη γραμμή 5 διαβάζονται όλοι οι διαθέσιμοι τίτλοι αναρτήσεων που υπάρχουν στη σελίδα που εμφανίζεται. Μια σελίδα μπορεί να προβάλει περισσότερες από μία αναρτήσεις. Αρκεί να θυμηθούμε ότι κάθε τίτλος ανάρτησης ανήκει στην κλάση post-title. Το επόμενο βήμα είναι να βρούμε σε πόσες και ποιες γλώσσες είναι διαθέσιμη κάθε ανάρτηση. Αυτό κάνει το for loop. Για κάθε τίτλο που έχει βρεθεί, μηδενίζει αρχικά τον πίνακα των χρησιμοποιούμενων γλωσσών (usedLangs - γραμμή 9) και αν πραγματικά έχει βρεθεί κάποιος τίτλος ανάρτησης, τότε μέσα σε αυτόν ελέγχει ποιες από τις διαθέσιμες γλώσσες χρησιμοποιούνται (εσωτερικό for loop - γραμμές 11 έως 16). Οι γλώσσες που βρίσκονται προστίθενται στη μεταβλητή usedLangs.

On line 4 an array is built containing all the supported languages. On line 5 the code reads all the available titles of the posts that appear on page. There can be more than one post. Just remember that every title belongs to post-title class. The next step is to find how many and which languages each post is available. This is the for loop's job. For each title found, it first clears the aray of used languages (usedLangs - line 9) and if there is a title found, it checks which of the available languages are used (nested for loop - lines 11 to 16). Each language found it is added to usedLangs.

Ας θυμηθούμε λίγο μια προΥπόθεση που έχουμε θέσει προτύτερα· αν κάποιο άρθρο δεν έχει καθόλου μετάφραση, τότε ο τίτλος δεν περιέχει καμιά κλάση γλώσσας ενώ αν ο τίτλος περιέχει μόνο μια γλώσσα, τότε βρισκόμαστε σε κατάσταση μεταγλώτισης η οποία δεν έχει τελειώσει κι έτσι ο κώδικας θα πρέπει να αποκρύπτει τα τμήματα των μη δηλωμένων γλωσσών. Η σημαία επιλογής της τρέχουσας γλώσσας πρέπει να μην εμφανίζεται. Για περισσότερες δηλωμένες γλώσσες στον τίτλο της ανάρτησης, δε νομίζω πως θα πρέπει να πούμε κάτι παραπάνω.

At this point we have to remember one condition we set earlier; If a post is not translated at all then there is no element with language class in the title; If there is only one language class defined then the post is in a translation phase so the code must hide the partial translated part, meaning the elements that belong to other than the specified in the title one, languages. The selection flag must also be hidden. I don't thing we have to say any more on that.

Αν λοιπόν βρέθηκαν δηλωμένες γλώσσες στον τίτλο, τότε θα πρέπει να δημιουργηθούν οι σημαίες επιλογής για κάθε μία. Αυτό κάνει και ο κώδικας των γραμμών 17 έως 25.

So, if there is any language defined in the title, then for each one there must be one selection flag. That is what lines 17 to 25 do.

Ανάγνωση Παραμέτρου από τη URL
Read URL Parameters

Το τμήμα που θα δούμε ακολούθως είναι αυτό που διαβάζει τις παραμέτρους τύπου GET από το URL του φυλλομετρητή μας. Πρόκειται για μια υπορουτίνα που δέχεται σαν παράμετρο εισόδου τη παράμετρο που αναζητάμε. Αν Αυτή βρεθεί, τότε επιστρέφει την τιμή της. Αν δε βρεθεί επιστρέφεται η τιμή null:

The part that follows is the one that reads GET type parameters from the URL of our browser. It is a funcition that takes the name of the wanted parameter as input. If it is found in URL then it returns the found value, else it returns null:

...Previous JavaScript code...

  function getParam(p_name) {
    pArray = location.search.substring(1).split(&quot;&amp;&quot;);
    retVal = null;
    pLength = pArray.length;
    for(i=0; i &lt; pLength; i++) {
      keys = pArray[i].split(&quot;=&quot;);
      if(decodeURIComponent(keys[0]) == p_name) {
        retVal = decodeURIComponent(keys[1]);
        break;
      }
    }
    return retVal
  }

...More JavaScript code...

Στη γραμμή 4 διαβάζεται το τμήμα της URL που περιέχει τις παραμέτρους. Ο πρώτος χαρακτήρας είναι ο "?" και γι' αυτό αποκόπτεται (substring(1)) ενώ το υπόλοιπο δημιουργεί ένα πίνακα που περιέχει τις παραμέτρους που δηλώνωνται. Η μια παράμετρος από την επόμενη χωρίζονται από τον χαρακτήρα "&". Αν βρεθούν παράμετροι, τότε εξετάζονται μια προς μία για να βρούμε αυτή που δηλώθηκε κατά την κλήση της υπορουτίνας. Προσέξτε ότι χρησιμοποιείται η decodeURIComponent για να αποκωδικοποιηθούν σωστά οι ειδικοί χαρακτήρες που μπορεί να περιέχονται στο URL. Αν η παράμετρος βρεθεί τότε σταματάει το ψάξιμο και επιστρέφεται η τιμή της, ενώ αν δε βρεθεί επιστρέφεται η προτοποθετημένη τιμή null.

In line 4 the parameters part of the URL is fetched. The first character is always "?" so it is omitted (substring(1)) and the rest forms an array containing the parameters read and their values. Each parameter is separated from the next by the "&" character. If there is any parameter found, they are checked one by one to match the one specified by the caller of the function. Mind that decodeURIComponent is used to decode the HTML characters used in URLs correctly. If the parameter needed is found in the list then the searching stops and its value is returned, else the returned value is the predefined null.

Θα μπορούσε κάποιος να παρατηρήσει πως η συγκεκριμμένη ρουτίνα δεν ξέρει να ασχοληθεί με παραμέτρους που μπορούν να πάρουν πολλές τιμές ταυτόχρονα (όπως επιλογές από checkboxes με το ίδιο όνομα). Αυτό δεν είναι κάτι που μας ενδιαφέρει γιατί η μόνη παράμετρος που διαχειριζόμαστε είναι η lang. Θα ήταν, λοιπόν, άσκοπο να προσθέσουμε κάτι παραπάνω σε αυτή τη ρουτίνα για κάτι που δε χρειαζόμαστε.

Someone could notice that this function does not know how to handle parameters that can take many values at the same time; a list of values (like from checkboxes with the same name). This is not of our concern because the only parameter we need to handle is lang and this takes only one value. So, it would be useless to try to handle something more complicated than that.

Διαχείριση Cookies
Cookies Manipulation

Ο κώδικας πρέπει να θυμάται την επιλογή της γλώσσας του αναγνώστη. Είναι αρκετά άβολο, κάθε φορά που ένας αναγνώστης επιλέγει να δει μια καινούργια ανάρτηση να πρέπει να ξαναεπιλέξει και τη γλώσσα που τον ενδιαφέρει να διαβάζει το άρθρο. Για να μπορεί να θυμάται το σύστημα την επιλογή της γλώσσας για τον κάθε ένα αναγνώστη ξεχωριστά, ο μόνος τρόπος είναι η χρήση ενός Cookie. Ας δούμε τις ρουτίνες που τα διαχειρίζονται.

The code must be able to remember reader's language choice. It is very inconvenient each time the reader visits our page, or changes post in our blog, to have to select again and again the same language. For the system to be able to remember each reader's choice, the only way is to use Cookies. Lets see the functions that manipulate them.

...Previous JavaScript code...

  function setCookie(c_name, c_inval, exdays) {
    if (exdays) {
      var exdate = new Date();
      exdate.setTime(exdate.getTime()+(exdays*24*60*60*1000));
      var expires = &quot;; expires=&quot; + exdate.toGMTString();
    } else {
      var expires = &quot;&quot;;
    }
    var c_value = escape(c_inval);
    document.cookie = c_name + &quot;=&quot; + c_value + expires + &quot;; path=/&quot;;
  }

  function getCookie(c_name) {
    var nameEQ = c_name + &quot;=&quot;;
    var ca = document.cookie.split(&quot;;&quot;);
    var caLength = ca.length;
    for(var i=0; i &lt; caLength; i++) {
      var c = ca[i];
      while (c.charAt(0)==&quot; &quot;) c = c.substring(1,c.length);
      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
  }

  function updCookie(c_name, c_newval) {
    var c_val = c_newval || getCookie(c_name);
    if (c_val) {
      setCookie(c_name, c_val, EXPIREDAYS)
    }
  }

...More JavaScript code...

Σε αυτό το τμήμα του κώδικα βλέπουμε τρεις υπορουτίνες. Η πρώτη (setCookie) δημιουργεί ένα Cookie με όνομα που δηλώνεται στην παράμετρο c_name, η τιμή του καθορίζεται από τη παράμετρο c_inval ενώ ο χρόνος εγκυρότητάς του δηλώνεται από τη παράμετρο exdays.

In this part of the code we can see three functions. The first one, (setCookie), creates a Cookie with the name set at c_name calling parameter, its value is set according to c_inval and the time to live is set by exdays.

Η δεύτερη υπορουτίνα διαβάζει το Cookie με όνομα που δηλώνεται στη παράμετρο εισόδου c_name κι επιστρέφει την τιμή του. Αν δε το βρει επιστρέφει null.

The second function reads a Cookie named according to c_name input parameter and returns its value. If the cookie is not found then null is returned.

Η τρίτη κι τελευταία υπορουτίνα χρησιμοποιεί τη setCookie για να κάνει τη δουλειά της. Αν υπάρχει το Cookie που δηλώνεται στην παράμετρο c_name τότε το ενημερώνει με μια πιθανά καινούργια τιμή, αν αυτή έχει δηλωθεί στη παράμετρο c_newval και μια ημερομηνία που ορίζεται από τις αρχικές μεταβλητές που δηλώσαμε στην αρχή, την EXPIREDAYS.

The third and last function uses the previously mentioned setCookie to do its job. If there is already a Cookie named as the input parameter c_name specifies, it possibly updates its value to the new one specified by c_newval. If the parameter is omited then it does not change the cookie's value. Also, the expiration date is updated according to the global variable we saw in the beginning of the code, EXPIREDAYS.

Απόφαση Γλώσσας Αναγνώστη
Readers Language Decision

Ήρθε η ώρα να αποφασίσουμε τελικά σε ποια από τις διαθέσιμες γλώσσες θα εμφανίσουμε το άρθρο. Η απόφαση πρέπει να γίνει με βάση τα εξής κριτήρια:

Time to decide, finaly, which one of the available languages will be the one that the post will be displayed. The decision is made according to the following criteria:

  1. Αν υπάρχει παράμετρος με όνομα lang στη URL του φυλλομετρητή, τότε η απόφαση ορίζεται από αυτή την τιμήIf there is a parameter named lang in the URL of the browser, then the language to be used is the one specified there.
  2. Αν δεν υπάρχει η παράμετρος τότε εξετάζεται το ενδεχόμενο να έχει ξαναεπισκευτεί τη σελίδα ο αναγνώστης, δηλαδή το να υπάρχει αποθηκευμένο Cookie με το όνομα που δηλώνει η αρχική μεταβλητή LANGCOOKIENAME. Αν υπάρχει τότε η γλώσσα επιλέγεται από αυτόIf that parameter si not found then we have to check if the reader has visited our page before, by checking the existance of a Cookie with the name specified by global variable LANGCOOKIENAME. If it is true, then the language to be used is set by the Cookie's value.
  3. Αν ο αναγνώστης επισκέπτεται για πρώτη φορά τη σελίδα μας, ή έχει καιρό να την επισκευτεί έτσι ώστε να έχει λήξει η προθεσμία του Cookie, τότε η απόφαση θα παρθεί από τις ρυθμίσεις του φυλλομετρητήIf this is the first time the reader visits our page, or he/she has a long time to visit it so the Cookie in question has expired, then the decision must be made by the browser settings.
  4. Τέλος, αν όλες οι προηγούμενες προσπάθειες αποτύχουν, ορίζεται ως γλώσσα η πρώτη από τη λίστα που ορίζει η αρχική μεταβλητή PREFEREDLANGSFinally, when all the previous attempts fail, the default language is used and this is the first one in the global list in PREFEREDLANGS.

Όλα τα παραπάνω φαίνονται στο ακόλουθο κομμάτι κώδικα:

All the previously mentioned are performed by the following code:

...Previous JavaScript code...

  function getLang() {
    var langParam = getParam(&quot;lang&quot;);
    var availLangs = PREFEREDLANGS.split(&quot; &quot;);
    var browserLang = langParam || getCookie(LANGCOOKIENAME) || window.navigator.userLanguage || window.navigator.language || availLangs[0];
    var langCnt = availLangs.length;
    retVal = 0;
    for(i=0; i &lt; langCnt; i++) {
      if(browserLang == availLangs[i]) {
        retVal = i;
        break;
      } else if(browserLang.substring(0, browserLang.indexOf(&quot;_&quot;)) == availLangs[i]) {
        retVal = i;
 break;
      }
    }
    return availLangs[retVal];
  }

...More JavaScript code...

Στη γραμμή 6, εξετάζονται μια προς μια οι παράμετροι, με τη σειρά που εξηγήσαμε. Όποια βρεθεί πρώτη είναι και αυτή που θα δώσει την τιμή της στη μεταβλητή browserLang. Για να μπορέσουμε, όμως, να εξασφαλίσουμε ότι η γλώσσα που θα επιστραφεί είναι μια από τις αποδεκτές που ορίζονται στην αρχή του όλου κώδικα, με τη μεταβλητή PREFEREDLANGS, θα πρέπει να γίνει μια αντιπαράθεση με μια προς μια τις διαθέσιμες γλώσσες. Αυτό κάνει και το for loop στις γραμμές 9 έως 17. Μια μικρή λεπτομέρεια είναι πως η γλώσσα μπορεί να αποτελείται από το γενικό κομμάτι της (π.χ. "en" για την Αγγλική) αλλά μπορεί να περιέχει και το χαρακτηριστικό της περιοχής (όπως π.χ. "en_US" για την Αγγλική γλώσσα των Ηνωμένων Πολιτειών). Αυτό ο κώδικας το λαμβάνει υπόψη του.

In line 6, the parameters are checked ony by one in the described order. The first found one sets the value of browserLang. To be able to secure that the returned language value to be used is one of the available ones, set by PREFEREDLANGS global variable, we should compare their values. This is done by the for loop in lines 9 to 17. One small detail is that a language literal characteristic may contain only the general part of the language (ie. "en" for English) or may include the country literal (as in "en_US" for United States English). The code takes it into account, also.

Εφαρμογή Γλώσσας Στις Αναρτήσεις
Apply Language Selection On Posts

Γνωρίζωντας, πλέον, τη γλώσσα στην οποία θα πρέπει να εμφανιστεί το άρθρο, το μόνο που μένει είναι να εφαρμόσουμε αυτή την επιλογή. Η εφαρμογή γίνεται από την ίδια ρουτίνα που χρησιμοποιείται και από τις σημαίες επιλογής γλώσσας που εμφανίσαμε στον τίτλο της ανάρτησης. Αν γυρίσουμε, για μια στιγμή μόνο, πίσω στο κομμάτι του κώδικα που πρόσθεσε τα ενδεικτικά σημαιάκια στον τίτλο. Βλέπουμε στη γραμμή 21 εκείνου του κώδικα να ορίζεται το event onclick να καλεί μια υπορουτίνα με όνομα applyLang και παράμετρο τη γλώσσα που αντιπροσωπεύει το αντίστοιχο ενδεικτικό σημαιάκι. Ας δούμε τον κώδικα της υπορουτίνας:

Knowing the language that the post should be presented, the only thing left is to apply this decision. The same function that applies the language choice when selecting one of the title's flags, is the one that does the same job here, also. If we go back to the part of code that added the small language selection flags in the title, we can see in line 21 of that code part that it sets the onclick event to call a function named applyLang having as calling parameter the language that coresponds to the selected flag. Lets see now the code of this function:

...Previous JavaScript code...

  function applyLang(inLang) {
    var posts = document.getElementsByClassName(&quot;post&quot;);
    setCookie(LANGCOOKIENAME, inLang, EXPIREDAYS);
    if(posts[0] == undefined)
      return;
    for(k=0; k&lt;posts.length; k++) {
      var flags = posts[k].getElementsByClassName(&quot;flag&quot;);
      var langExist = (posts[k].getElementsByClassName(&quot;flag flg&quot; + inLang) != undefined);
      var flagsCnt = flags.length;
      if(flagsCnt==0) continue;
      var tempLang = inLang;
      if((flagsCnt==1) || !langExist) {
        tempLang = flags[0].className.indexOf(&quot;flg&quot;);
 tempLang = flags[0].className.substring(tempLang+3);
      }
      for(i=0; i&lt;flagsCnt; i++){
        curI = flags[i].className.indexOf(&quot;flg&quot;+tempLang);
        if(curI &gt;= 0) {
          flags[i].style.display = &quot;none&quot;;
        } else {
          flags[i].style.display = &quot;inline&quot;;
        }
      }
      var availLangs = PREFEREDLANGS.split(&quot; &quot;);
      for(i=0; i&lt;availLangs.length; i++){
        var currLang = availLangs[i];
        if(currLang == tempLang)
          continue;
        var divs = document.getElementsByClassName(currLang);
        var divsCnt = divs.length;
        for(j=0; j&lt;divsCnt; j++) {
          divs[j].style.display = &quot;none&quot;;
        }
      }
      var divs = document.getElementsByClassName(tempLang);
      var divsCnt = divs.length;
      for(j=0; j&lt;divsCnt; j++) {
        elemType = divs[j].tagName.toLowerCase();
        divDisplay = &quot;block&quot;;
        switch(elemType) {
          case &quot;a&quot;:
          case &quot;span&quot;:
            divDisplay = &quot;inline&quot;;
            break;
          case &quot;li&quot;:
            divDisplay = &quot;list-item&quot;;
            break;
        }
        divs[j].style.display = divDisplay;
      }
    }
  }

...More JavaScript code...

Πιθανόν κάποιος να αναρωτηθεί για τον τρόπο με τον οποίο είναι γραμμένη αυτή η υπορουτίνα. Ας δούμε λίγο τι κάνει.

Well, perhaps someone will ask for the way this function is written. Lets see how it operates.

Αρχικά ανανεώνει το Cookie για τη γλώσσα που πρόκειται να παρουσιαστεί και φυσικά το χρόνο λήξης της ισχύος του (γραμμή 5). Στη συνέχεια αν δεν υπάρχει κάποια διαθέσιμη ανάρτηση, τότε δεν υπάρχει κάτι να κάνει, οπότε και τερματίζεται (γραμμές 6 και 7).

In the beginning it updates the Cookie with the language value to be used and, of course, its expiration time (line 5). Next, if there is no post on screen it has nothing to do so it returns (lines 6 and 7).

Για κάθε ανάρτηση που βρίσκει αναγνωρίζει ποιες είναι οι διαθέσιμες γλώσσες και αν η ανάρτηση περιέχεται στη γλώσσα που επιλέχθηκε. Αν δε βρεθεί κάποια γλώσσα για την ανάρτηση την οποία ελέγχει τότε προχωράει στην επόμενη (γραμμή 12).

For every post it finds it checks its available languages and if the selected one appears in the list. If this is not true it continues to the next post (line 12).

Το τμήμα που αποτελείται από τις γραμμές 13 έως 17, αποφασίζει ποια γλώσσα από τις διαθέσιμες του άρθρου είναι αυτή στην οποία θα εμφανιστεί. Ο λόγος που γίνεται αυτό, είναι γιατί ένα άρθρο μπορεί να είναι ολοκληρωμένο μόνο σε μια γλώσσα, η οποία να είναι διαφορετική από την επιλεγμένη, οπότε και θα εμφανιστεί στη γλώσσα στην οποία είναι γραμμένο, ή να μην έχει γραφτεί καθόλου στην επιλεγμένη γλώσσα, συνεπώς εμφανίζεται στην προεπιλεγμένη. Μην ξεχνάμε πως ο λόγος να έχει δηλωθεί μόνο μια γλώσσα στον τίτλο ενός άρθρου, είναι κατά τη διάρκεια της μεταγλώτισής του ενώ αυτό έχει αναρτηθεί, έτσι ώστε να αποκρύπτεται η ημιτελής μετάφραση.

The part that is formed by lines 13 to 17, decides which of the post's available language will be used, finaly.The reason for this is that a post may be in a translation phase and the completed language be different from the one specified. Thus, the completed language is the one that should be used. The post may also be written only in a different language, so it is presented in the default one. Don't forget the reason of specifying only one language in the post's title. It means the article is in a translation phase and the non completed language should not be presented.

Από τη γραμμή 18 έως την 25 ενημερώνεται η εμφάνιση των σημαιών επιλογής γλώσσας. Εμφανίζονται, δηλαδή, οι σημαίες των γλωσσών που δεν είναι επιλεγμένες και αποκρύπτεται αυτή στην οποία εμφανίζεται η παρούσα ανάρτηση.

Form line 18 to line 25 the avalable selection flags are updated; the languages that are not used become visible and the one that represents the current selection becomes hidden.

Τα επόμενο βήμα είναι να εμφανιστούν τα HTML στοιχεία που ανήκουν στη γλώσσα που έχει αποφασιστεί να προβληθεί η ανάρτηση. Αυτό γίνεται σε δύο κομμάτια. Το πρώτο αποτελείται από τις γραμμές 27 έως 36, το οποίο αποκρύπτει όλες τις παραγράφους που έχουν επισήμανση γλώσσας. Το δεύτερο κομμάτι αποτελείται από τις γραμμές 37 έως και 52. Αυτό εμφανίζει τα στοιχεία τα οποία ανήκουν στη γλώσσα προβολής του άρθρου. Εδώ δίνεται και η δυνατότητα να μπορέσουν τα διαφορετικά στοιχεία να πάρουν διαφορετική τιμή στη παράμετρο display.

The next step is for the HTML elements that belong to the selected language to be displayed. It is done in two phases. The first is the code in lines 27 to 36, that hides all the elements that belong to another language. The second phase, code lines 37 to 52, displays the elements that belong to the currently selected language. Here every element can take a different display value for their correct appearance.

Ο λόγος για τον οποίο η εμφάνιση γίνεται με διπλό πέρασμα των στοιχείων της ανάρτησης είναι γιατί ένα στοιχείο μπορεί να ανήκει σε δύο γλώσσες. Παραδείγματος χάρην, αν το άρθρο έχει γραφτεί σε Ελληνικά, Αγγλικά και Κινέζικα, τότε ένα στοιχείο που εμφανίζεται και στα Ελληνικά, αλλά και στα Αγγλικά θα πρέπει να ανήκει και στις δύο κλάσεις γλώσσας (class="el en"). Αν δεν περνούσαμε την εμφάνιση σε δύο βήματα, τότε η λειτουργία της εμφάνισης δε θα γινόταν σωστά· ναι μεν στην επιλογή της εμφάνισης του άρθρου σε Κινέζικα δε θα εμφανιζόταν το εν λόγω στοιχείο, αλλά αναλόγως με τη θέση της γλώσσας των Ελληνικών και των Αγγλικών στη λίστα των διαθέσιμων γλωσσών δε θα είχαμε εμφάνιση και στις δύο γλώσσες...

The reason of the two phases insted of one, is that an element can belong to two or more languages. In example, if an article is written in Greek, English and Chinese, there is the posibility for an element to be presented for both Greek and English languages and be hidden in Chinese. For this to become true, the element must belong to both el and en classes (class="el en"). If there were not two phases the presentation of the article would be broken; in the Chinese post everything would be fine, but in the other two languages the correct representation would be only in the one that appears first in the global list of languages. We would not see the element in question in both specified ones...

Τελευταίες Πινελιές στον Κώδικα
Some Code's Finishing Touches

Τι δεν έχουμε δει ακόμα; Το κομμάτι του κώδικα που ξεκινάει την αυτόματη εκτέλεσή του. Μιας και δεν έχουμε τον πλήρη έλεγχο όλου του κώδικα που τρέχει στη σελίδα, παρά μόνο στο δικό μας κομμάτι, δε μπορούμε να χρησιμοποιήσουμε την onload. Μπορεί ήδη να τη χρησιμοποιεί κάποιο άλλο κομμάτι κώδικα από όλα αυτά που φορτώνονται για την επιβολή των στατιστικών κ.λ.π. που μας παρέχει το blogger.com. Κατα συνέπεια, ο μόνος διαθέσιμος τρόπος είναι οι αυτόματα εκτελέσιμες υπορουτίνες της JavaScript:

What we haven't seen yet? The part of the code that automatically starts its execution. Since we don't have the complete control of the whole code that runs in our page, but only in our part of code, we cannot use onload. It may be in use from another part of the code that the page loads for google statistics etc. and for the gadjets that blogger.com offeres. The only safe way is the autorun JavaScript functions:

...Previous JavaScript code...

  (function(){
    setupFlags();
    setupLang();
  })();

  function setupLang() {
    currLang = getLang();
    applyLang(currLang);
  }
</script>

</body>
</html>

Οι γραμμές 8 έως 11 δεν κάνουν τίποτα άλλο από το να διαβάσουν τη γλώσσα που θα πρέπει να χρησιμοποιηθεί, την πρώτη φορά που φορτώνεται η σελίδα, ενώ οι γραμμές 3 έως και 6 είναι αυτές που εκτελούνται αυτόματα με το που φορτώνεται η σελίδα μας. Αυτές είναι που ξεκινάνε την εμφάνιση των σημαιών επιλογής γλώσσας κι εμφανίζουν τα άρθρα που υπάρχουν στη σελίδα στη γλώσσα που είναι πιο κοντά στον χρήστη, όπως περιγράφτηκε στην παράγραφο "Απόφαση Γλώσσας Αναγνώστη". Σε αυτό το σημείο ολοκληρώνεται και ο κώδικας που περιέχεται σε αυτό εδώ το blog για την πολυγλωσσική εμφάνιση των αναρτήσεών του.

Lines from 8 to 11 do nothing more than finding the propper language the first time the page is loaded. Lines 3 to 6 are those that take advantage of the autorun feature of JavaScript. When the page is loaded they are automatically executed. These are the ones that start adding the language selection flags and present the posts on the page in the correct languages, the ones closer to the reader, just as described in "Readers Language Decision". And in that point the code we present is complete and is the code that this blog uses to offer nultilanguage capabilities in our posts.

Το Συνολικό Πρόγραμμα της JavaScript

The Whole JavaScript Program

Μιας και ο κώδικας περιγράφτηκε σε τμήματα, για να είναι πιο εύκολο για τον αναγνώστη να μπορέσει να τον αντιγράψει και να τον χρησιμοποιήσει/παραλλάξει σύμφωνα με τις ανάγκες του, ο κώδικας παρατίθεται ολόκληρος σε αυτό το σημείο. Μπορείτε να τον χρησιμοποιήσετε ελεύθερα.

Since the code was described part to part, for the reader to be easier to copy and use/alter it according to his/her needs, the whole code will be presented here. You can copy and use it freely.

<script type='text/javascript'>
  //Script for making the blog multilingual.
  //Written by Elias Chrysocheris
  /*THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS
    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
    OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  var PREFEREDLANGS = &quot;el en&quot;;
  var LANGCOOKIENAME = &quot;echlang&quot;;
  var EXPIREDAYS = 7;
  var TITLES = {};
  TITLES[&quot;el&quot;] = &quot;Δείτε το άρθρο στα Ελληνικά&quot;;
  TITLES[&quot;en&quot;] = &quot;View the article in English&quot;;

  (function(){
    setupFlags();
    setupLang();
  })();

  function setCookie(c_name, c_inval, exdays) {
    if (exdays) {
      var exdate = new Date();
      exdate.setTime(exdate.getTime()+(exdays*24*60*60*1000));
      var expires = &quot;; expires=&quot; + exdate.toGMTString();
    } else {
      var expires = &quot;&quot;;
    }
    var c_value = escape(c_inval);
    document.cookie = c_name + &quot;=&quot; + c_value + expires + &quot;; path=/&quot;;
  }

  function getCookie(c_name) {
    var nameEQ = c_name + &quot;=&quot;;
    var ca = document.cookie.split(&quot;;&quot;);
    var caLength = ca.length;
    for(var i=0; i &lt; caLength; i++) {
      var c = ca[i];
      while (c.charAt(0)==&quot; &quot;) c = c.substring(1,c.length);
      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
  }

  function updCookie(c_name, c_newval) {
    var c_val = c_newval || getCookie(c_name);
    if (c_val) {
      setCookie(c_name, c_val, EXPIREDAYS)
    }
  }

  function getParam(p_name) {
    pArray = location.search.substring(1).split(&quot;&amp;&quot;);
    retVal = null;
    pLength = pArray.length;
    for(i=0; i &lt; pLength; i++) {
      keys = pArray[i].split(&quot;=&quot;);
      if(decodeURIComponent(keys[0]) == p_name) {
        retVal = decodeURIComponent(keys[1]);
        break;
      }
    }
    return retVal
  }

  function getLang() {
    var langParam = getParam(&quot;lang&quot;);
    var availLangs = PREFEREDLANGS.split(&quot; &quot;);
    var browserLang = langParam || getCookie(LANGCOOKIENAME) || window.navigator.userLanguage || window.navigator.language || availLangs[0];
    var langCnt = availLangs.length;
    retVal = 0;
    for(i=0; i &lt; langCnt; i++) {
      if(browserLang == availLangs[i]) {
        retVal = i;
        break;
      } else if(browserLang.substring(0, browserLang.indexOf(&quot;_&quot;)) == availLangs[i]) {
        retVal = i;
 break;
      }
    }
    return availLangs[retVal];
  }

  function setupLang() {
    currLang = getLang();
    applyLang(currLang);
  }

  function applyLang(inLang) {
    var posts = document.getElementsByClassName(&quot;post&quot;);
    setCookie(LANGCOOKIENAME, inLang, EXPIREDAYS);
    if(posts[0] == undefined)
      return;
    for(k=0; k&lt;posts.length; k++) {
      var flags = posts[k].getElementsByClassName(&quot;flag&quot;);
      var langExist = (posts[k].getElementsByClassName(&quot;flag flg&quot; + inLang) != undefined);
      var flagsCnt = flags.length;
      if(flagsCnt==0) continue;
      var tempLang = inLang;
      if((flagsCnt==1) || !langExist) {
        tempLang = flags[0].className.indexOf(&quot;flg&quot;);
 tempLang = flags[0].className.substring(tempLang+3);
      }
      for(i=0; i&lt;flagsCnt; i++){
        curI = flags[i].className.indexOf(&quot;flg&quot;+tempLang);
        if(curI &gt;= 0) {
          flags[i].style.display = &quot;none&quot;;
        } else {
          flags[i].style.display = &quot;inline&quot;;
        }
      }
      var availLangs = PREFEREDLANGS.split(&quot; &quot;);
      for(i=0; i&lt;availLangs.length; i++){
        var currLang = availLangs[i];
        if(currLang == tempLang)
          continue;
        var divs = document.getElementsByClassName(currLang);
        var divsCnt = divs.length;
        for(j=0; j&lt;divsCnt; j++) {
          divs[j].style.display = &quot;none&quot;;
        }
      }
      var divs = document.getElementsByClassName(tempLang);
      var divsCnt = divs.length;
      for(j=0; j&lt;divsCnt; j++) {
        elemType = divs[j].tagName.toLowerCase();
        divDisplay = &quot;block&quot;;
        switch(elemType) {
          case &quot;a&quot;:
          case &quot;span&quot;:
            divDisplay = &quot;inline&quot;;
            break;
          case &quot;li&quot;:
            divDisplay = &quot;list-item&quot;;
            break;
        }
        divs[j].style.display = divDisplay;
      }
    }
  }

  function setupFlags() {
    var availLangs = PREFEREDLANGS.split(&quot; &quot;);
    var headings = document.getElementsByClassName(&quot;post-title&quot;);
    var usedLangs = new Array();
    for(i=0; i&lt;headings.length; i++) {
      var tempHead = headings[i];
      usedLangs.length = 0;
      if(tempHead != undefined) {
        for(j=0; j&lt;availLangs.length; j++) {
          var tempElems = tempHead.getElementsByClassName(availLangs[j]);
          if(tempElems.length&gt;0) {
            usedLangs.push(availLangs[j]);
          }
        }
        if(usedLangs.length&gt;0) {
          for(j=0; j&lt;usedLangs.length; j++) {
            newDiv = document.createElement(&quot;div&quot;);
            newDiv.className = &quot;flag flg&quot; + usedLangs[j];
            newDiv.setAttribute(&quot;onclick&quot;, &quot;applyLang(&#39;&quot; +usedLangs[j]+ &quot;&#39;)&quot;);
            newDiv.title = TITLES[usedLangs[j]];
            tempHead.appendChild(newDiv);
          }
        }
      }
    }
  }

</script>
</body>
</html>

Απλά θυμηθείτε πως υπάρχει και το τμήμα του CSS καθώς επίσης θα πρέπει να παραλλάξετε τις global μεταβλητές σύμφωνα με τις δικές σας ανάγκες.

Just keep in mind that there is also the CSS part of the code and that you may have to alter the global variables for your needs.

Παράδειγμα Χρήσης

An Example Of Usage

Για να δούμε πώς μπορούμε να φτιάξουμε ένα πολυγλωσσικό κείμενο. Μιας και στο παρόν blog χρησιμοποιούμε Ελληνική και Αγγλική γλώσσα, το παράδειγμα που θα δοθεί είναι γι' αυτές. Ας δούμε ένα κομμάτι κειμένου που αναρτούμε σε ένα άρθρο μας. Να σημειωθεί ότι το κέιμενο είναι γραμμένο σε καθαρή HTML:

Lets see how we can create a multilingual text. Since in our blog we use only Greek and English languages, the example will be given for these two. Lets see a part of text that will be presented in our post. Notice that it is written in pure HTML:

<p class="el">Αυτό είναι το κείμενο της παραγράφου στα Ελληνικά. Εφόσον η κλάση που δηλώνεται
στο tag της παραγράφου είναι <tt>el</tt>, η παράγραφος αυτή θα εμφανίζεται όταν βλέπετε το
Ελληνικό κείμενο!</p>
<p class="en">This is the English text of the paragraph. Since the &lt;p&gt; tag belongs to
<tt>en</tt> class, this paragraph will be visible only when the English text is displayed!</p>

Το αποτέλεσμα είναι η ακόλουθη παράγραφος στο γκρι πλαίσιο. Η ενδεικτική σημαία δεν προστίθεται με αυτό τον HTML κώδικα, αλλά την έχουμε προσθέσει εμείς για την ευκολία της παρατήρησης του αναγνώστη. Δοκιμάστε να επιλέξετε άλλη γλώσσα από τον τίτλο του κειμένου (ή από τη σημαία στα δεξιά) και παρατηρείστε τι εμφανίζεται σε αυτή την παράγραφο:

The result is the following paragraph in the gray box. The selector flag at the right is not added by this HTML code, but we have added it for the reader's convenience to observe the results. Try to select another language from the post's tilte (or the indicator flag at the right) and observe the appeared paragraph:

Αυτό είναι το κείμενο της παραγράφου στα Ελληνικά. Εφόσον η κλάση που δηλώνεται στο tag της παραγράφου είναι el, η παράγραφος αυτή θα εμφανίζεται όταν βλέπετε το Ελληνικό κείμενο!

This is the English text of the paragraph. Since the <p> tag belongs to en class, this paragraph will be visible only when the English text is displayed!

Κάτι ακόμα που θα πρέπει να αναφερθεί, είναι πως κατά τη διάρκεια της προεπισκόπισης ενός άρθρου που ακόμα συντάσεται, χωρίς να έχει αναρτηθεί, επειδή το blogger κόβει κάποιες από τις λειτουργίες, ένας απλός τρόπος για να γίνει προεπισκόπιση σε άλλη γλώσσα, είναι η χρήση της παραμέτρου lang στο URL του παραθύρου της προεπισκόπισης. Δηλαδή, όταν πατήσουμε το πλήκτρο προεπισκόπιση κατά τη διάρκεια της συγγραφής ενός άρθρου, ανοίγει μια καινούργια καρτέλα (ή καινούργιο παράθυρο, ανάλογα με τις ρυθμίσεις του φυλλομετρητή) και μας δειχνει πως θα φαίνεται το άρθρο όταν αναρτηθεί. Αν στο URL προσθέσουμε το "&lang=en", τότε υποχρεώνουμε το σύστημα να μας εμφανίσει το άρθρο στην Αγγλική γλώσσα.

One more thing must be reported is that during the preview of a post that is still editted without being published, because blogger.com uses a "click trap", a simple way of previewing the post in a different language is to use the lang parameter in the URL of the preview page. Meaning, when we use the Preview button during the editing of a post, a new tab (o a new window, depending on your browser's settings) opens and it presents us how the post will appear when it is published. If we add "&lang=en" in the preview URL, then we force the system to present the article in English.

Συμπεράσματα

Conclusions

Το να μπορεί κάποιος να διαβάσει ένα κείμενο που τον ενδιαφέρει στη μητρική του γλώσσα, είναι κάτι πολύ σημαντικό. Σκεφτείτε πόσες φορές έχετε βρει ένα κείμενο σε μια γλώσσα που δεν καταλαβαίνετε· είτε δεν το προσπαθήσατε καθόλου, είτε χωθήκατε μέσα σε ένα μεταφραστικό και... ότι βγάλατε βγάλατε. Μην ξεχνάμε ότι παντού γίνονται προσπάθειες μετάφρασης. Από κλειστά λογισμικά μέχρι ανοικτά, ιστοσελίδες και ό,τι μπορεί να περιέχει κείμενο, κρύβουν μια ομάδα, μεγάλη ή μικρή που δουλειά της είναι να μεταφράζει τα κείμενα σε διάφορες γλώσσες. Όσοι ασχολείστε με το ανοικτό λογισμικό πιστεύω πως θα ασχοληθήκατε, ή έστω θα διαπιστώσατε τη μεγάλη προσπάθεια που γίνεται για τη μετάφραση των προγραμμάτων, τόσο του γραφικού περιβάλλοντός τους όσο και του κειμένου βοήθειας.

For somebody to be able to read a text in his/her native language is something very important. Just think of the times that you have found an interesting article in a language that you cannot understand. Either you didn't try to read it at all, or you fell into a translating site (or dictionary) and... whatever came out of this. Lets not forget that everywhere there are translation tries. From closed source programs to opensource ones, web pages and whatever may include text, everything hides a translating team behind the scenes, big or small to translate documents into different languages. Whoever of you uses open source programs and operating systems I believe that you have tried to do something like that, or at least you have seen the big effort made for translation of all programs, graphical environment and help files.

Το blogger.com δυστυχώς δε μας δίνει τη δυνατότητα να έχουμε μεταφράσεις του κειμένου μιας ανάρτησής μας. Αυτό το εμπόδιο, με λίγη προσπάθεια, το προσπερνάμε και έχουμε μεταφρασμένες αναρτήσεις. Με αυτό τον τρόπο η απήχηση του blog μας αυξάνει.

Unfortunately, blogger.com doesnot offer the ability to have multilingual posts. That barrier is oversteped with little effort, and now we can have what we need. In that way the audience of our blog grows.

Μια μικρή ματιά σε ιστότοπους, όπως αυτό της Transifex, νομίζω πως μπορεί να σας πείσει για την αναγκαιότητα της μετάφρασης.

Just a small search arround, to web pages and companies like Transifex, can show you the necessity of translation, I believe.

Ηλίας Χρυσοχέρης

Elias Chrysocheris

Κυριακή 23 Ιουνίου 2013

NoIP στο Raspberry Pi και μείωση του Internet TrafficNoIP in Raspberry Pi and Internet Traffic Reduction

Εισαγωγή

Introduction

Πολλές φορές χρειαζόμαστε να στήσουμε κάποιου είδους server έτσι ώστε να μπορούμε να έχουμε πρόσβαση σε αυτόν από τον "έξω κόσμο", δηλαδή από παντού, όπου υπάρχει πρόσβαση στο διαδίκτυο. Το πρόβλημα σε αυτό είναι πως οι περισσότεροι πάροχοι Internet (Internet Service Providers, εν συντομία ISPs) δε δίνουν σταθερή διεύθυνση IP στα router που έχουμε σπίτι μας (εκτός, φυσικά, αν το ζητήσουμε με κάποια παραπάνω χρέωση). Έτσι, θα πρέπει κάθε φορά να γνωρίζουμε την IP διεύθυνση του router μας για να μπορέσουμε να έχουμε πρόσβαση στον server που κάθεται πίσω από αυτό. Αυτόν ακριβώς το σκοπό έχει και το παρόν άρθρο. Τι θα λέγατε όμως, αν το σύστημα που θα μας βοηθούσε είναι ένα Raspberry Pi; Όχι πως αυτά που περιγράφονται εδώ δε μπορούν να εφαρμοστούν και σε άλλο σύστημα υπολογιστή... Αντιθέτως, ένα δυνατό σημείο του ανοικτού λογισμικού, είναι πως η διαχείρισή του γίνεται με τον ίδιο ακριβώς τρόπο, είτε αυτό τρέχει σε ένα μηχάνημα με 64 επεξεργαστές, είτε τρέχει σε μια... τοστιέρα (βλ. NetBSD!).

There are sometimes that we need to setup a small server in a way to have access to it from the "outer world", meaning everywhere, wherever there is internet access. The problem is that most of the Internet Service Providers (abbreviated as ISPs) do not give static IP address to our home routers (unless of course, we ask for it, in extra charge). So, we have to know the IP address of our router in order to gain access to our server behind it. This is the goal of this article. But, what if the helper system for our purpose is a Raspberry Pi? Not that what is described in here cannot be applied to some other computer system... Contrariwise, this is a very powerful part of opensource; the administration and setup of a system is a similar way, whether it runs on a 64 core system, or even a... toaster (see NetBSD site!).

Τι θα δούμε

What is in this article

Παρότι το άρθρο προορίζεται σε ανθρώπους που ξέρουν να ρυθμίζουν routers, να στήνουν servers, να διαχειρίζονται υπηρεσίες σε λειτουργικά όπως το Linux και να έχουν τη δυνατότητα, ίσως, να ασχοληθούν με ένα gadget του τύπου Raspberry Pi, εν τούτης θα δοθούν πληροφορίες ακόμα και σε πιο "αρχικά" πράγματα. Μέσα σε όλους τους σκοπούς του άρθρου είναι να μπορέσει ακόμα και κάποιος αρχάριος να βρει κάποιες εναρκτήριες πληροφορίες, εισαγωγικές περισσότερο σε όλα αυτά με τα οποία σχετίζεται η πρόσβαση ενός υπολογιστή σε ένα άλλο, μέσω του διαδικτύου. Ακόμα και για την πληρότητα του άρθρου, δε θα μπορούσαν να λείπουν αυτές οι πληροφορίες. Αν είστε κάπως πιο προχωρημένος χρήστης/γνώστης και κάποια παράγραφος σας είναι απλή ή δεν προσφέρει κάποιες παραπάνω γνώσεις σε αυτές που ήδη έχετε, μπορείτε ασφαλώς να την προσπεράσετε και να μπείτε στο "ψητό" της ιστορίας.

Though this article is for people who know how to setup a router, servers, administrate services in operating systems such as Linux and have the ability, perhaps, to deal with a gadget of Raspberry Pi's type, yet, the information in here will be in a novice level. Between the article's goals is for a not so advanced user to find some basic steps in all these stuff that communication between two computers through the internet, involves. Even for clarity, some of these information could not be omitted. If you are more advanced user and you find that some paragraphs do not contribute to your knowledge, feel free to skip it and proceed to the "hot point" of the story.

Μια μικρή περίληψη όσων θα δούμε μέσα στο παρόν άρθρο:

A brief of what we will find is:

DNS, Ευρετήριο διευθύνσεων διαδικτύου

DNS, Domain names index of the internet

Κάθε υπολογιστής ή άλλη συσκευή που συνδέεται στο διαδίκτυο με σκοπό να ανταλλάξει πληροφορίες διαμέσου αυτού, παίρνει μια μοναδική διεύθυνση στον κόσμο. Αυτή είναι και η βασική ιδέα. Αυτή την ονομάζουμε διεύθυνση IP. Φυσικά και υπάρχουν διευθύνσεις που θα τις δούμε να υπάρχουν ταυτόχρονα σε πολλούς υπολογιστές (π.χ. μια διεύθυνση της μορφής 192.168.1.1), αλλά ας μείνουμε στη βασική ιδέα του συστήματος του διαδικτύου και ας μην επεκταθούμε σε τέτοιες λεπτομέρειες. Το router που έχουμε στο σπίτι μας ή στο γραφείο μας (ή οπουδήποτε, τέλος πάντων) είναι και αυτό μια συσκευή επικοινωνίας που συνδέεται με το διαδίκτυο. Κατά συνέπεια και αυτό έχει μια διεύθυνση IP. Γιατί γίνεται αυτό; Αυτή η διεύθυνση είναι ένα κομμάτι της "ταυτοποίησης" του αντικειμένου. Όταν γίνεται μια επικοινωνία = μεταφορά δεδομένων από ένα σύστημα σε ένα άλλο, τότε απλά οι διευθύνσεις είναι αυτές που καθορίζουν τον αποστολέα και τον παραλήπτη. Όπως ακριβώς και η μεταφορά αλληλογραφίας με το κανονικό ταχυδρομείο που χρειάζεται να φέρει ο φάκελος τις διευθύνσεις αποστολέα και παραλήπτη.

Every computer or other network device that is connected to the internet with the purpose of exchanging data through it, must have a unique address all over the world. That's the basic idea. This is the address we call "IP Address". Of course we will find IP addresses that exist in more than one computer at the same time (ie. an address of type 192.168.1.1), but lets stick on the basic idea of the internet's system and not expand to such details for now. The routing device (called router) we have at home or office (or wherever) is a communication device that is connected to the internet. So, it also has an IP address. Why that happens? This address is used for some kind of identification of this device. When a communication takes place = data transfer from one system to another, the addresses identify who is the sender and to who is the target, in all over the network; similar to the way mail transfer takes place at the ordinary post office; the envelope has both the sender's and the recipient's address.

Οι διευθύνσεις που δίνονται σε όλα αυτά τα συστήματα επικοινωνίας, είτε είναι υπολογιστές είτε είναι ένας δικτυακός εκτυπωτής κ.λ.π, έχουν τη μορφή 4 αριθμών χωρισμένων από τελείες (.). Ο κάθε αριθμός μπορεί να πάρει τιμές από 0 μέχρι και 255. Υπάρχει και ένας πιο σύγχρονος τρόπος διευθυνσιοδότησης, όπου η κάθε διεύθυνση αποτελείται από 8 τετραψήφιους δεκαεξαδικούς αριθμούς χωρισμένους από άνω-κάτω τελείες (:).

The given addresses to all these communication and networking devices, whether they are computers or network printers etc., they consist of 4 numbers separated by dots (.). Each number cat take a value from 0 to 255. There is also a newer way of addressing where the address is formed by 8 four-digit hexadecimal numbers separated by colons (:).

Και γιατί τα αναφέρουμε όλα αυτά; Βλέπετε πουθενά λέξεις, όπως google.com, blogspot.gr ή my_really_really_fancy_site.biz; Όχι, γιατί ΔΕΝ ΥΠΑΡΧΟΥΝ. Αυτό σημαίνει πως για να μπορέσουμε να επισκεφθούμε τη μηχανή αναζήτησης της Google, το site που φιλοξενεί τα περισσότερα blogs ή μια άλλη τοποθεσία με το πιο σημαντικό υλικό που μας ενδιαφέρει (λέμε τώρα!) θα έπρεπε να θυμόμαστε ένα σωρό νούμερα που να δώσουν στον browser μας να καταλάβει ποια τοποθεσία θέλουμε να δούμε. Λίγο δύσκολο ε;... Ναι. Και για να μπορέσουμε να βρούμε τοποθεσίες στο internet θα έπρεπε να έχουμε ένα τεράστιο κατάλογο με αντιστοιχία διευθύνσεων σε ονόματα των site που αντιστοιχούν, ή ιδιότητες των site κ.λ.π. Και κάθε φορά που θα θέλαμε να επισκεφθούμε μια τοποθεσία θα έπρεπε να κατεβάζουμε τον αντίστοιχο τόμο από τη βιβλιοθήκη μας και να τον ψάχνουμε, μέχρι να βρούμε αυτή την τοποθεσία που ψάχνουμε... Ακόμα χειρότερα, κάθε φορά που ένα καινούργιο site θα δημιουργόταν στο διαδίκτυο, θα έπρεπε να ανανεώσουμε το ράφι με αυτούς τους καταλόγους!

And why do we point all of these? Do you see words like google.com, blogspot.gr or my_really_really_fancy_site.biz anywhere? No, because THEY DO NOT EXIST. In order to visit Google's search engine, the site that contains many blogs or another web site we are interested in, we have to remember a lot of numbers that will give our browser the information of which site we want to visit. Kind of hard, isn't it?... Yeap!. And in order to be able to find out some websites later we should own a really huge index that should have a one to one correlation between addresses and site names or site properties etc. And every time we would like to visit a site we should take the corresponding volume from our library and search through it untili we find tyhe desired site... There's even worse, every time a new site appeared in the total network, we should update the index!

"Χμμμ... Μα εγώ δε θυμάμαι να δίνω τέτοια νούμερα που αναφέρεις στον browser μου...". Επειδή ακριβώς το να θυμόμαστε νούμερα είναι πολύ δυσκολότερο από το να θυμόμαστε λέξεις, για να μη μπαίνουμε στον κόπο να ψάχνουμε τις διευθύνσεις που πρέπει να δώσουμε για να μπορέσουμε να προσπελάσουμε τον κάθε ιστότοπο που επιθυμούμε, έχουν δημιουργηθεί κάποιοι servers οι οποίοι κάνουν ακριβώς αυτό· αντικαθιστούν το τεράστιο ράφι της βιβλιοθήκης μας, που λέγαμε πριν... Τους δίνουμε ένα όνομα ιστότοπου και αν αυτό υπάρχει μας επιστρέφουν διεύθυνση IP στην οποία βρίσκεται ο εν λόγω ιστότοπος. Αλλά μπορούν να κάνουν και το αντίθετο, δηλαδή να μας πουν το όνομα του ιστότοπου στον οποίο οδηγεί μια διεύθυνση.

"Hmmm... But I don't remember specifying such numbers in my browser...". Due to the difficulty in remembering numbers compared to remembering words, in order not to try to search through IP addresses every time we need to access a site, there are some servers they perform this action for us: They replace the huge library shelf we were talking about... We can send them the site name we want to access and they return the IP address the web site in question lies. But they can also perform the reverse function; they can tell the name of the website an IP address points to.

Διαδικασία ονοματοδοσίας

Naming

Και το ερώτημα είναι πώς γίνεται μια ονοματοδοσία στον τεράστιο χώρο του internet; Όταν κάποιος θελήσει να στήσει ένα server στο διαδίκτυο, τότε θα πρέπει να κατοχυρώσει ένα όνομα, το οποίο όλοι οι υπόλοιποι θα χρησιμοποιούν για να έχουν πρόσβαση σε αυτή την υπηρεσία που προσφέρει ο εν λόγω εξυπηρετητής. Αυτό το όνομα είναι μοναδικό. Αφού κατοχυρωθεί το όνομα, σε κάποιο server του διαδικτύου, μπορεί να φιλοξενηθεί η ιστοσελίδα ή η υπηρεσία που μας ενδιαφέρει. Ο server αυτός έχει κάποια συγκεκριμένη διεύθυνση IP. Μπορεί να ανήκει σε κάποια εταιρία φιλοξενίας (hosting) ιστοσελίδων, ή ακόμα και στον υπολογιστή που έχουμε στο σπίτι μας! Το τελευταίο βήμα, είναι να "παντρέψουμε" το όνομα που κατοχυρώσαμε με τη διεύθυνση στην οποία βρίσκεται η εν λόγω ιστοσελίδα/υπηρεσία· αυτή, συνήθως, τη φτιάχνουμε εμείς.

And the question now is how the naming of the sites happen throughout the huge internet space? If someone wants to set a server in the internet, he must first register a name, the one that all the others will use to be served by the server in question. This name must be unique. After the registration of the name, another server must host the web page or the needed service to be offered. That server must have a specific IP address. It can be owned by a web hosting company, or can be even the computer we have at our place! The final step is to tie together the name and the IP address in question. The web page or the service is the one that we create.

Πού βρίσκεται το πρόβλημα; Όταν το μόνο που μας ενδιαφέρει είναι να έχουμε ένα ωραιότατο server για δική μας δουλειά, θα πρέπει να κατοχυρώσουμε ένα όνομα στο διαδίκτυο (=κόστος), να πληρώσουμε σε κάποια εταιρία hosting χώρο που να τον διαμορφώσουμε κατάλληλα ώστε να φιλοξενήσει την υπηρεσία που μας ενδιαφέρει (=κόστος) και τέλος να δηλώσουμε σε κάποιο DNS ότι το όνομα που κατοχυρώσαμε "δείχνει" στον server της εταιρίας hosting (=κόστος) η οποία φιλοξενεί την υπηρεσία μας... Εναλλακτικά, μπορούμε να πληρώσουμε τον ISP μας για να μας δώσει σταθερή IP διεύθυνση, οπότε τη φιλοξενία της υπηρεσίας μας την κάνουμε στον υπολογιστή στο σπίτι μας. Όλα αυτά μας επιβαρύνουν οικονομικά.

Where the problem is? If the only thing that matters is to have a nice personal server, for our personal usage only, we should buy an internet domain name (=added cost), pay a hosting service company to give us space in their servers, configure that space to host our service (=added cost) and finally submit the new domain name in a DNS pointing the IP address of the host server (=added cost, again) that serves our service... Also an alternative is to pay our ISP to give us a static IP address, so we can use our own computer to serve whatever we want from our home. But all these cost us money.

Το ερώτημα είναι: «Αφού δεν κάνω επαγγελματική ιστοσελίδα, αλλά με ενδιαφέρει να έχω πρόσβαση στο δίκτυό μου για τη δική μου διευκόλυνση και μόνο. Δε μπορώ να το κάνω χωρίς κόστος;» Αυτό που χρειαζόμαστε είναι μια υπηρεσία που να μπορεί να μας αντιστοιχεί μια δυναμική διεύθυνση σε ένα σταθερό όνομα.

The question is: "Since I don't service a professional web page, but I just want to have access to my own home network for my convenience and only, does it have to cost me that much?". In that case what we really need is a web service that can bound a static domain name to a dynamic IP address, just as a normal DNS server does.

Δυναμικός DNS για δυναμικές IP

Dynamic DNS for Dynamic IP addresses

Αυτή είναι μια υπηρεσία που πλέον βρίσκεται αρκετά στο διαδίκτυο. Η εγγραφή σε μια υπηρεσία δυναμικού DNS είναι μια απλή λύση στο πρόβλημά μας. Οι περισσότερες δυναμικές DNS υπηρεσίες που προσφέρονται από κάποια sites, θέλουν να έχει κάποιος ένα κατοχυρωμένο όνομα. Άλλες πάλι, αφήνουν τον χρήστη να επιλέξει από μια σειρά ονόματα που έχει διαθέσιμα. Ένα χαρακτηριστικό παράδειγμα είναι το DynDNS.

This is a service that can be found nowadays on the internet. The registration in a Dynamic DNS service is a simple solution to our problem. Most of the Dynamic DNS services available through some sites, just need someone to have a registered domain name. Some other, let the user select one from the available to that site domain names. An example is DynDNS.

Το DynDNS είναι ένα site που προσφέρει υπηρεσίες δυναμικού DNS. Ο χρήστης έχει τη δυνατότητα να χρησιμοποιήσει ένα όνομα της αρεσκείας του, αρκεί αυτό να ανήκει σε domains που ανήκουν στην ίδια τη DynDNS. Παράδειγμα, ένας χρήστης θα μπορούσε να χρησιμοποιεί το όνομα eliasfiles.dyndns-at-home.org γιατί ανήκει στο domain της DynDNS (dyndns-at-home.org). Το καλό που έχει το DynDNS είναι πως τα περισσότερα, πλέον, routers έχουν ενσωματωμένη την υποστήριξη για τη συγκεκριμένη υπηρεσία. Όμως, τον τελευταίο καιρό το site άρχισε να προσφέρεις τις υπηρεσίες του με πληρωμή. Πιο συγκεκριμένα, ένας καινούργιος χρήστης είναι υποχρεωμένος να δοκιμάσει την υπηρεσία DynDNSPro ελεύθερα για 14 ημέρες και αν δεν τη διακόψει νωρίτερα τότε χρεώνεται για τη χρήση της. Αυτό του δίνει το δικαίωμα να έχει την υπηρεσία για ένα χρόνο. Αν τη διακόψει πριν τη 14η ημέρα, τότε δε μπορεί να έχει καθόλου υπηρεσίες DynDNS. Για κάποιον ο οποίος θέλει να έχει πρόσβαση σε κάποια αρχεία του, κάποια βάση δεδομένων με δικές του πληροφορίες κ.λ.π. δεν είναι ότι καλύτερο να πρέπει να πληρώσει για κάτι το οποίο δεν του αποφέρει καθόλου κέρδος.

DynDNS is a site that offers Dynamic DNS services. The user has the ability to use a domain name he/she likes, provided that this name belongs to domains that really belong to DynDNS itself. For example, a user could use eliasfiles.dyndns-at-home.org because it belongs to a registered DynDNS domain. The good part is that most of the routers available for home usage can be configured to use this service. But, lately the site started to offer their services with a cost. More specifically, a new user is forced to test the DynDNSPro service free of charge for 14 days only. If he/she does not stop using it then, after the 14th day a small fee is charged for one more year usage. If he/she stops using the service then there will be no more DynDNS services. For someone who wants to have access to his files, a personal database having his own data etc, it is not the best idea to pay for something that does not offer him some income.

NoIP: Υπηρεσία δυναμικού DNS για όλους

NoIP: Dynamic DNS Services for all

Αρκετό ψάξιμο στο internet μου εμφάνισε σελίδες που προσέφεραν υπηρεσίες δυναμικού DNS, αλλά σε όλες έπρεπε να έχει κάποιος κατοχυρωμένο domain name. Τελικά, μια από όλες μου τράβηξε περισσότερη την προσοχή, η NoIP. Αυτή η σελίδα προσφέρει υπηρεσίες δυναμικού DNS, αλλά και ελεύθερης κατοχύρωσης ονόματος που θα ανήκει στο domain no-ip.biz (και άλλα). Κάτι παρόμοιο, δηλαδή, με το παλιό καλό DynDNS.

After a log search through the internet there were many web sites offering Dynamoc DNS services, but they needed the user to have a domain name already registered. Finally, one of them just paid my attention, NoIP. That web page offers Dynamic DNS, but also gives the ability to use a NoIP registered domain name that belongs to no-ip.biz (and many more). It is something similar to DynDNS.

Τι άλλο προσφέρει η NoIP:

What else does NoIP offer:

  • Ελεύθερο (χωρίς χρέωση) Domain Name που ανήκει στο no-ip.biz (ή κάποιο άλλο από τα προκαθορισμένα του)
  • Free of charge (no cost at all) Domain Name that belongs to no-ip.biz (or another that belongs to NoIP list)
  • Έναν client που αναλαμβάνει την ενημέρωση της λίστας του DNS με την καινούργια IP. Αυτόν τον ονομάζει DUC - Dynamic Update Client (περισσότερα ακολούθως)
  • A client program that updates the DNS database with the new IP when there is an IP address change from our ISP. They call it DUC - Dynamic Update Client (more on this later)
  • Ο DUC είναι ανοιχτού κώδικα
  • DUC is open sourced!
  • Οδηγείες για τη δημιουργία δικού μας client αν δε μας αρέσει ο δικός της
  • There are also information on how to create our own update client if for some reason we don't like theirs

Ο NoIP Client είναι ένα πρόγραμμα που τρέχει στον υπολογιστή μας και ανά τακτά διαστήματα ανιχνεύει τη διεύθυνση που έχουμε από τον ISP και αν δει ότι διαφέρει από την προηγούμενη, ενημερώνει τον DNS Server της NoIP για την καινούργια διεύθυνση. Έτσι, το όνομα το οποίο έχουμε κατοχυρώσει δείχνει στην καινούργια μας διεύθυνση. Το ίδιο ακριβώς σύστημα έχει και το DynDNS με ένα πρόγραμμα client που ονομαζόταν ddclient.

NoIP Client is a small program that runs in our computer and at regular intervals it checks for our IP address the ISP has set and if it is different from the one we had earlier, then it updates NoIP DNS server. So, the domain name we have registered points to our new IP address. DynDNS laso uses the same updating system, using a client called ddclient.

Πλεονεκτήματα της χρήσης ενός client προγράμματος είναι ότι αυτός τρέχει σε έναν υπολογιστή και ενημερώνει τη NoIP για τις αλλαγές της IP διεύθυνσης χωρίς να χρειάζεται να κάνει κάτι ο χρήστης.

The advantage of using an update client program is that it runs in a computer at the background and informs NoIP whenever there is an IP address change, without user's intervention.

Μειονεκτήματα της χρήσης ενός client είναι πως ο client λειτουργεί μόνο όση ώρα είναι σε λειτουργία ο υπολογιστής που τον τρέχει. Συνεπώς, υπάρχει η περίπτωση να πρέπει να έχουμε ένα υπολογιστή ενεργοποιημένο 24 ώρες το 24ωρο. Ο client ελέγχει για αλλαγή διεύθυνσης ανά τακτά διαστήματα. Τέτοια αλλαγή μπορεί να γίνει οποιαδήποτε ώρα και στιγμή από τον provider. Αυτό οδηγεί στο δεύτερο μειονέκτημά, πως από τον χρόνο που θα εκτελέσει ο ISP μια αλλαγή στη διεύθυνσή μας μέχρι τη στιγμή που ο client θα ελέγξει για μια τέτοια κατάσταση, ο DNS θα έχει την παλιά μας διεύθυνση και έτσι δε θα μπορούμε να έχουμε πρόσβαση στον server μας γι' αυτό το χρονικό διάστημα. Τέλος, ο client για να ανιχνεύσει την αλλαγή της διεύθυνσης, στέλνει ένα request στο server της NoIP. Αυτό σημαίνει διακίνηση δεδομένων στο διαδίκτυο, κατά συνέπεια δε μπορούμε να ρυθμίσουμε τον client να κάνει τον έλεγχο σε πολύ τακτά χρονικά διαστήματα (π.χ. 5 δευτερολέπτων). Χρήση τακτικών ελέγχων βοηθάει στο να μειώσουμε το χρόνο της ασυμφωνίας της διεύθυνσης που έχει ο DNS με την πραγματική μας, μετά από αλλαγή από τον ISP. Ταυτόχρονα, όμως, αυξάνει και τη διακίνηση πληροφορίας προς τον NoIP server. Αποτέλεσμα είναι να κινδυνεύουμε να μας κάνει τελικά ban ο server και να μη δέχεται τις πληροφορίες μας, διότι θεωρεί ότι του κάνουμε ηλεκτρονική επίθεση.

The drawback of using such a client is that it works only when the computer that runs it is powered on. Thus, we may have to keep our computer running 24 hours a day. The client checks for an address change at regular intervals. An IP address can be performed by the internet provider any time. This leads to a second drawback. From the time the ISP changes our IP address till the time the update client does its job, the DNS servers over the internet will have our old IP address, so we will not be able to access our server during that time. Finally, the client tracks the address change by sending a request to NoIP server. This, in turn, means internet traffic, so we cannot configure the client to use a small update interval (such as 5 seconds). The small updating interval helps having a small lag during IP address change and DNS tables updating. But, at the same time increases the traffic to NoIP server. This could make NoIP server to ban us and ignoring information send by our update client, because it could thing we perform an electronic attack.

Γιατί το Raspberry Pi;

Why Raspberry Pi?

Το Raspberry Pi είναι ένας μικρός υπολογιστής μεγέθους πιστωτικής κάρτας (όχι δε μπορείτε να κάνετε πληρωμές σε καταστήματα με αυτό! :)) βασισμένος σε αρχιτεκτονική επεξεργαστή ARM. Καταναλώνει ελάχιστα και τρέχει οποιαδήποτε διανομή Linux (ή ακόμα και FreeBSD) είναι μεταγλωττισμένη γι' αυτή την αρχιτεκτονική. Είναι πολύ βολικό για τέτοιες καταστάσεις.

Raspberry Pi is a small computer at the size of a credit card (well, no, you cannot perform in shop payments using it! :)) based on ARM architecture. It consumes little power and can run any Linux distribution (or even FreeBSD) that is compiled for that architecture. It is very convenient for cases like ours.

Επίσης, δεν υπάρχει λόγος γιατί να πρέπει να λειτουργεί 24/7 ένας υπολογιστής (εκτός και αν είναι απαραίτητο για άλλους σκοπούς). Το Raspberry Pi λόγω της μικρής του κατανάλωσης, αλλά και του μικρού του μεγέθους, μπορεί να λειτουργεί συνεχώς, χωρίς πρόβλημα, ενώ δεν πιάνει χώρο επάνω σε ένα γραφείο, όπως τα PC. Μπορεί να μπει δίπλα από το router μας (κάποια από αυτά διαθέτουν και μια USB θύρα και μπορούν να το τροφοδοτήσουν), ενώ υπάρχουν αρκετά όμορφα κουτάκια τα οποία το κάνουν να δείχνει σοβαρό σε οποιοδήποτε χώρο (μη φωνάζει η γυναίκα πως δεν ταιριάζει με τις κουρτίνες :Ρ).

There is no reason why a PC computer should be on 24/7 (unless it must be on for specific services). Because Raspberry Pi consumes so little power and because of its small size, it can be powered on without problems, and at the same time it has small footprint on our desktop, unlike PCs. It can live side by side with our router (some of them having USB connectors can also power it). There are also some very good looking boxes for it, making suitable for the space it lives in our house (not to let your wife comlain that it does not suit with the curtains of the room :Ρ).

Όσα περιγράφονται στο παρόν άρθρο έχουν γίνει επάνω στο λειτουργικό Raspbian που είναι βασισμένο στο Debian. Επειδή, όμως, οι διανομές Linux έχουν πάρα πολλά κοινά μεταξύ τους, δεν υπάρχει λόγος γιατί, με έστω λίγες αλλαγές, να μη μπορούν να τρέξουν και σε οποιαδήποτε άλλη διανομή για οποιαδήποτε αρχιτεκτονική.

What is described in this article is done in Raspbian operating system which is based on Debian. But, since Linux distributions share a lot in common, there is no particular reason why, even with small changes, they cannot be done in other distribution or architecture.

Ο Dynamic Update Client της NoIP

NoIP Dynamic Update Client

Η NoIP δίνει έναν client για το update της διεύθυνσης IP στον DNS τους, ο οποίος είναι γραμμένος σε γλώσσα C. Για απλή χρήση είναι αυτό που χρειάζεται κάποιος. Μπορείτε να τον κατεβάσετε και να τον κάνετε compile κανονικά, όπως και κάθε άλλο πρόγραμμα. Στο Raspberry Pi γίνεται κανονικά compile, ενώ τρέχει χωρίς κανένα πρόβλημα.

NoIP provides an update client for updating their DNS server with our new IP address. It is written in C language. For a simple use it is what anybody needs. You can download and compile it normally, as any other program that can be installed from its source code. In Raspberry Pi its compilation and execution runs flawlessly without any problems.

Η εγκατάσταση είναι απλή. Αρχικά κατεβάζετε τον πηγαίο κώδικα, μιας και είναι opensource, από το site της NoIP. Στη συνέχεια αποσυμπιέζετε το αρχείο:

The installation is very simple. First you download the program's source code, since it is opensource, from NoIP site. The decompression follows:

pi@raspberrypi:~ > tar -xvzf noip-duc-linux.tar.gz
...
pi@raspberrypi:~ > 

Αυτό θα δημιουργήσει ένα κατάλογο με όνομα noip-version. Μέσα σε αυτόν βρίσκεται όλος ο πηγαίος κώδικας, scripts για διάφορες διανομές ώστε να λειτουργέι σαν υπηρεσία κ.λ.π. Μπορείτε ελεύθερα να περιηγηθείτε μέσα στα περιεχόμενα του καταλόγου. Όταν τελικά αποφασίσετε να το εγκαταστήσετε κάνετε τα ακόλουθα: (η έκδοση που βρίσκεται ο DUC τη στιγμή που γράφεται το άρθρο είναι η 2.1.9-1)

A directory named noip-version will be created. In there you can find the source code, service scripts for some distributions, etc. You can freely browse through the contents of the directory. When you finally decide to install it just do the following: (The version of DUC the time this article is edited is 2.1.9-1)

pi@raspberrypi:~ > cd ~/noip-2.1.9-1
pi@raspberrypi:~ > make
...
pi@raspberrypi:~ > sudo make install
...
pi@raspberrypi:~ > 

Αυτές οι εντολές θα εγκαταστήσουν το noip2 στον κατάλογο /usr/local/bin. Οι πληροφορίες που δίνονται στα διάφορα αρχεία README.FIRST είναι αρκετές για να το χρησιμοποιήσετε.

These commands will install noip2 in /usr/local/bin directory. The provided information by various files like README.FIRST are enough to make you use it.

Μιας και τα μειονεκτήματα του DUC τα είδαμε πριν, ας προχωρήσουμε σε μια διαφορετική υλοποίηση από αυτή του NoIP DUC που βελτιώνει κάποια χαρακτηριστικά.

Since we presented the drawbacks of DUC earlier, lets proceed to a different implementation than that of NoIP's DUC, that improves it.

Μείωση του Internet Traffic

Internet Traffic Reduction

Η μεγαλύτερη διακίνηση πληροφορίας που γίνεται στο Internet από τον client είναι για την εύρεση της IP διεύθυνσης που μας έχει δώσει ο provider μας. Κάθε φορά που ο DUC θέλει να μάθει την IP μας, υποβάλει ένα ερώτημα στον server της NoIP. Αυτός βλέπει από ποια IP γίνεται το ερώτημα και την επιστρέφει στον DUC. Αν διαπιστωθεί αλλαγή της, τότε ο DUC στέλνει άλλο ένα request που αναφέρει πως θα πρέπει να γίνει η αλλαγή της διεύθυνσης και στον κατάλογο του DNS. (αυτή είναι η γενική ιδέα)

The most exchanged data by the client program through the internet is the question of the current IP address our ISP provided to us. Every time DUC wants to find our IP, it sends a request to NoIP's server. The server checks which is the source IP of the request and returns it to DUC. If DUC finds a different IP than the one of the previous request, it sends another request for the DNS table updating (That is the general idea)

Επειδή η αλλαγή της διεύθυνσης συμβαίνει σε αραιά χρονικά διαστήματα (ανά μερικές ημέρες), η περισσότερη επικοινωνία που συμβαίνει (το ερώτημα της τρέχουσας IP διεύθυνσης) είναι άσκοπη. Ταυτόχρονα, σκοπός μας είναι να μπορέσουμε να υποβάλουμε ερώτημα για τη διεύθυνση IP τόσο συχνά ώστε να μην υπάρχει μεγάλο χρονικό διάστημα όπου ο DNS περιέχει παλαιά διεύθυνση (να είναι ασυγχρόνιστος).

Since an IP change happens in very big intervals (several days), the most communication traffic happens (the query of tha current IP address) is pointless. At the same time, our goal is to send an IP address query as often as possible, for the time the DNS contains our old address (not be synchronized) be as small as it can be.

Ο πιο απλός τρόπος είναι να μην υποβάλλεται το ερώτημα στον server της NoIP, αλλά στο router μας. Πολλά router έχουν ενεργοποιημένο telnet server, έτσι ώστε να μπορούμε να συνδεθούμε με αυτό το πρωτόκολλο και να εκτελούμε εντολές. Μια άλλη εναλλακτική υπάρχει αν το router που διαθέτουμε τρέχει ssh server, οπότε μπαίνουμε με ssh στο router και πάλι μπορούμε να εκτελέσουμε εντολές. Κάποια, βέβαια, routers δεν έχουν τίποτα από τα δυο (π.χ. κάτι oxygen)... Εκεί θα πρέπει να κάνουμε ερώτημα μέσω https (συνήθως).

The simplest way is to send the IP address query, not to NoIP's server but to our router. Many routers provide a telnet server, thus, we can connect to it using that protocol and execute commands. Another alternative exists is our router runs a ssh server, so we connect to it using ssh and again we can execute commands. Well, there is also the possibility for some routers not to provide any of the above (ie. some oxygen routers)... We can send them queries using https protocol (usually).

Επειδή δεν είναι σταθερός ο τρόπος με τον οποίο υποβάλλουμε ερώτημα για την IP στο router, σε αυτό το άρθρο θα παρουσιαστεί ένα script που είναι για χρήση σε πρωτόκολλο telnet που είναι και το πιο συχνά εμφανιζόμενο. Συγκεκριμένα έχει δοκιμαστεί σε τρία διαφορετικά routers. Ένα ZTE (που δίνει ο ΟΤΕ), ένα TP-Link και ένα AirTies. Φυσικά, με διαφορετικό configuration. Τα δύο από τα τρία εκτελούν κανονικές εντολές Linux, ενώ το TP-Link έχει δικές του εντολές.

Since the way we question our router for our IP address is not by default the same to all routers, in this article we present a script that uses telnet protocol, which is the most applicable. We have tested it in three different routers, a ZTE (the Greek Telecomunications Company provides), a TP-Link and an AirTies. Of course, we used different configuration. Two of these execute normal Linux commands, while the TP-Link has its own.

Αρχικά, ας δημιουργήσουμε τις συνθήκες για να κάτσουν τα script. Πρώτο βήμα είναι να φτιαχτεί ο κατάλογος που θα φιλοξενήσει τα scripts:

First, lets create the space our scripts will sit in. The first step is to create a directory that will host the scripts:

pi@raspberrypi:~ > sudo mkdir /usr/scripts
pi@raspberrypi:~ > cd /usr/scripts
pi@raspberrypi:/usr/scripts > 

Ας δούμε το script που υποβάλλει ερώτημα στο router για την IP. Το script είναι γραμμένο σε python. Μιας και δεν είμαι γνώστης της python, δεν περιμένω ότι το script αυτό είναι το τελειότερο του κόσμου (λέμε τώρα!). Είναι το πρώτο script που γράφω στη συγκεκριμένη γλώσσα προγραμματισμού οπότε κάποιος έμπειρος python προγραμματιστής πολύ πιθανό να βρει κάποιες ατέλειες. Επίσης, είναι μια πρώτη έκδοση. Σκοπεύω στο μέλλον (κοντινό) να του μπαλώσω μια "τρύπα" που έχει. Αλλά για την ώρα κάνει τη δουλειά του. Μπορείτε να το κάνετε Copy/Paste στον αγαπημένο σας editor από εδώ:

Lets see the script that questions the router for our current IP address. This script is written in python. Since I'm not a python programmer, I don't expect this script to be the most perfect one all over the world (well... matter of speaking!). It is my first python script so any experiences python programmer will find some defects. Also, this is a first version. In the future (near future) I intend to patch a "whole" in there. But for now it does what it promises. You can Copy/Paste it in your favourite editor from here:

#!/usr/bin/python
# THE SOFTWARE IS LICENSED UNDER GPL2 GENERAL PUBLIC LICENSE v2.
# IN ORDER TO OBTAIN A COPY OF THIS LICENSE PLEASE VISIT THE FOLLOWING
# ADDRESS:
# http://www.gnu.org/licenses/gpl-2.0.html

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#First import the necessary python libraries
import sys
import os
import telnetlib

#Setup the default values, before using the configuration file
ConfFile = "/etc/GetInetIP.conf"
RouterIP = "192.168.1.1"
UserName = "admin"
Password = "admin"
NetComm = "ifconfig ppp0"
AddrPrefix = "inet "
ExpTime = 0.0001

#If the user has entered a CLI argument, use it as the configuration file to be used
InArgs = len(sys.argv)
if InArgs > 2:
        sys.stderr.write("ERROR: Only one input argument expexted!\n")
        sys.stderr.write("Usage: GetInetIP [configuration_filename]\n");
        exit(1)
elif InArgs == 2:
        ConfFile = sys.argv[1]
        #Need to check if the user specified filename exists. If not => Error
        if not(os.path.exists(ConfFile)):
                sys.stderr.write("ERROR: Configuration file" + ConfFile + "not found\n")
                exit(2)

#Check for the existance of the configuration file

#Time to import the configuration file's values. If the user specified a configuration file
# then it really exists. But if not, we must try to load the configuration file from
# the default path. If this file doesnot exist, then we ave to use our hardcoded values
#NOTE:The execfile command should be changed for security reasons... A more secure approach
# is to parse the configuration file, and not execute it... (no time to fixit right now)
if os.path.exists(ConfFile):
        execfile(ConfFile)

#Connect to the specified router using telnet protocol
Conn = ""
try:
        Conn = telnetlib.Telnet(RouterIP)
except:
        sys.stderr.write("ERROR: Cannot communicate with" + RouterIP +"\n")
        exit(3)

#Send the login credentials
RespStr = "First"
FailCount = 3
while (RespStr != "") and (FailCount > 0):
        RespStr = Conn.read_until("\n",ExpTime)
        LoginFlag = RespStr.find("login:")
        if LoginFlag >= 0:
                Conn.write(UserName + "\n")
                RespStr = Conn.read_until("\n",ExpTime)
        PassFlag = RespStr.find("Password:")
        if PassFlag >= 0:
                Conn.write(Password + "\n")
                RespStr = Conn.read_until("\n",ExpTime)
                FailCount -= 1

if FailCount == 0:
        sys.stderr.write("ERROR: Credentials failed 3 times\n")
        exit(4)

#Now the telnet awaits commands! Issue the command that announces the IP needed
Conn.write(NetComm +"\n")
RespStr = Conn.read_until("\n",ExpTime)
InetFlag = False
while (RespStr != "") and not(InetFlag):
        InetStrt = RespStr.find(AddrPrefix)
        if InetStrt >= 0:
                InetFlag = True
                InetStrt += len(AddrPrefix)
                InetEnd = RespStr.find(" ",InetStrt)
        else:
                RespStr = Conn.read_until("\n",ExpTime)
Conn.write("exit\n")
DummyStr = Conn.read_all()
Conn.close()
if not(InetFlag):
        sys.stderr.write("ERROR: IP Address not found\n")
        exit(5)
OutStr = RespStr[InetStrt:InetEnd]
print(OutStr.strip())
exit(0)

Αυτό το script το σώζουμε με το όνομα GetInetIP.py μέσα στον κατάλογο που φτιάξαμε. Για να λειτουργήσει χρειάζεται και ένα configuration αρχείο μέσα στον κατάλογο /etc. Προσοχή χρειάζεται στο ότι στο configuration αρχείο βρίσκονται στοιχεία που δεν πρέπει να φτάσουν στα μάτια τρίτων! Κοινώς, το password του router... Το αρχείο θα πρέπει να ονομάζεται GetInetIP.conf:

We save it with the name GetInetIP.py under the directory we created earlier. For the script to function properly it needs a configuration file in /etc directory. Caution is needed as in this configuration file some strings are for your eyes only! More specifically, your router's password... The file must be named GetInetIP.conf:

Configuration για router TP-Link

Configuration for TP-Link router

RouterIP = "192.168.1.1"
Password = "MyCrazyPassword"
NetComm = "show wan status"
AddrPrefix = "Ip = "
ExpTime = 0.07

Ας δούμε λίγο τα περιεχόμενα του configuration αρχείου:

Lets see the bones of this configuration file:

  • RouterIP: Δηλώνει τη διεύθυνση IP που έχει το router μαςSpecifies the router's IP address
  • UserName: Το όνομα χρήστη για το router. Αν δεν δηλώνεται εδώ χρησιμοποιείται η default τιμή που είναι το admin. Κάποια από τα routers δε ζητάνε το username. Σε αυτή την περίπτωση το script το καταλαβαίνει και δεν το στέλνει.The username of the router's administrator. If it not specified it takes the default value which is "admin". Some routers do not ask for a username. In that case the script is smart enough to understand it and it does not send a username at all.
  • Password: Το password που χρειαζόμαστε για να κάνουμε login στο routerThe password we need to login to router
  • NetComm: Η εντολή που εκτελούμε, όταν κάνουμε telnet στο router που μας δείχνει τη διεύθυνση IP που έχει στο WAN. Για το TP-Link είναι show wan status, ενώ για ένα router που τρέχει busybox ή γενικά εκτελεί εντολές Linux είναι κάτι του στυλ ifconfig ppp0The command we need to execute, using telnet, that announces the IP address of the WAN. For TP-Link it is show wan status, while for a router that runs busybox or executes Linux commands it is something like ifconfig ppp0
  • AddrPrefix: Όποια και αν είναι η εντολή που μας δίνει την επιθυμητή πληροφορία, δεν δίνει απλά ένα ξερό νούμερο, αλλά ολόκληρο κείμενο. Εδώ περιγράφεται τι περιμένει το script για να βρει ακριβώς πριν τη διεύθυνση IP που μας ενδιαφέρει. Αμέσως μετά από αυτό το κείμενο αναφέρεται η πληροφορία μας.Whichever the command is to get our IP address, it does not return only this number, but a whole text containing the desired information. This value describes what the script expects just before the IP address we need to filter. Just after that text placed in this variable the IP address appears.
  • ExpTime: Είναι ο χρόνος μέσα στον οποίο αν το router δεν στείλει κάποιο χαρακτήρα, θεωρείται πώς δεν έχει κάτι άλλο να στείλει. Προσοχή, σε άλλα router είναι μικρότερος και σε άλλα μεγαλύτερος. Γενικά, ο μεγάλος χρόνος κάνει για όλα τα router, αλλά καθυστερεί την εξέλιξη του script.This is the time that the script expects the router to sent a character. If there is no character during this time interval, the script assumes there is nothing more coming from the router as an answer. Be careful, at some routers this time is small while at others it is larger. In general, a bigger value is suitable for all routers but it slows down the performance of the script.

Αν μπούμε χειροκίνητα σε ένα TP-Link router και δώσουμε την εντολή show wan status τότε θα δούμε τα ακόλουθα:

If we manually connect to a TP-Link router and execute the command show wan status then it will return the following:

eliaschr@orion:~> telnet router
Trying 192.168.1.1...
Connected to router.
Escape character is '^]'.

Password: *********************
Copyright (c) 2001 - 2011 TP-LINK TECHNOLOGIES CO., LTD.
TP-LINK> show wan status
PVC-0 
        Status = Up
PVC-1 
        Status = Up
PVC-2 
        Status = Up
PVC-3 
        Status = Up
PVC-4 
        Status = Up
        Ip = 94.71.120.43
PVC-5 
        Status = Up
PVC-6 
        Status = Up
PVC-7 
        Status = Down
TP-LINK> 

Βλέπουμε ότι ακριβώς πριν την IP που μας ενδιαφέρει βρίσκεται το Ip = . Αυτό είναι που βάζουμε στο AddrPrefix.

We can see that just before the IP address we need to read is Ip = . This is what we set AddrPrefix to.

Στην περίπτωση του ZTE και του AirTies ένα configuration αρχείο σαν αυτό που ακολουθεί κάνει τη δουλειά:

In case of a ZTE or an AirTies a configuration file like the following does the necessary job:

Configuration για router ZTE - AirTies - Linux style commands

Configuration for ZTE - AirTies routers - Linux style commands

RouterIP = "192.168.1.1"
Password = "MyCrazyPassword"
NetComm = "ifconfig ppp0"
AddrPrefix = "inet addr:"
ExpTime = 0.05

Φυσικά, όποιο και να είναι το configuration που θα επιλέξετε, το μόνο σίγουρο είναι πως δεν πρέπει να το διαβάζει κανένας, παρά μόνο ο root!:

Of course, whichever is the configuration you choose, it is mandatory to be accessible by no one, except root!:

pi@raspberrypi:/usr/scripts > chown root:root /etc/GetInetIP.conf
pi@raspberrypi:/usr/scripts > chmod 0600 /etc/GetInetIP.conf
pi@raspberrypi:/usr/scripts > 

Dynamic Updater Client script

Ωραία! Αφού φτιάξαμε το script που μας διαβάζει την IP από το router μας, μένει να φτιάξουμε και το script που θα κάνει τη δουλειά του NoIP DUC. Δημιουργήστε το αρχείο IP-Updater.sh στον κατάλογο /usr/scripts που φτιάξαμε πριν. Το αρχείο αυτό θα πρέπει να το κάνετε Copy/Paste από εδώ:

Great! After we created the script that reads our router's WAN IP address, we have to create a new script that will simulate NoIP DUC. Just create the file named IP-Updater.sh under the directory /usr/scripts as earlier. You can Copy/Paste the source of the script in question from here:

#!/bin/bash
# THE SOFTWARE IS LICENSED UNDER GPL2 GENERAL PUBLIC LICENSE v2.
# IN ORDER TO OBTAIN A COPY OF THIS LICENSE PLEASE VISIT THE FOLLOWING
# ADDRESS:
# http://www.gnu.org/licenses/gpl-2.0.html

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# The script uses a configuration file that is called /etc/IP-Updater.conf
# Valid lines in the script are:
# NoIPUser=the_username_of_NoIP_login_account
# NoIPPass=the_password_of_the_account
# NoIPHost=the_host_to_which_the_IP_is_going_to_be_updated
# FindIPComm=/path/to/the_command_line_to_be_executed_to_get_the_IP
# FailIPDelay=Delay in seconds before retrying to find out the new
#       IP address, after a failure
# RepeatDelay=Delay in seconds before trying to refresh the IP after a
#       successful update
# FailDelay=Delay in seconds if there is a fatal error in NoIP. (Only if PreventExit=true)
# WGetDelay=Delay in seconds if there is a failure in wget to NoIP
# WGetLongFailDelay=Delay in seconds after Maximum number of failures reached
# WGetMaxFails=Maximum wget sequensial failures that make the daemon exit (unless
#       PreventExit=true
# MaxFailIPCnt=Maximum number of retries to fetch IP address from router
#       while it seems to have connection to the internet
# PreventExit={true/false} If true, then upon fatal error the script never ends
# SuccessCmd=Command to execute after a successful update to No-IP
# FailCmd=Command to execute after a failed NoIP update
# ExitCmd=Command to execute if the program must exit because of Fatal Error

# In ExitCmd, FailCmd and SuccessCmd you can use the following values as input:
#       $NoIPResp: if you want to include the responce from NoIP after the 
#               request of IP Address updating
#       $NewIP: the new IP that found and sent to NoIP
#       $CurrIP: the old IP that the system had before the update
#       $CurDate: the time that the execution of the IP update happened

PIDFILE=/var/run/IP-Updater.pid
PID=$BASHPID
LOGME="logger -ist 'NoIP-Updater'"
TEMPFILE="/tmp/CurrentIP"
TEMPWGET="/tmp/CurrentIP.wget"
CONFFILE="/etc/IP-Updater.conf"
USERAGENT="WGet from Raspberry Pi Linux/1.0 eliaschr@gmail.com"
NoIPUser=""
NoIPPass=""
NoIPHost=""
FindIPComm="echo 'ERROR: No router IP fetch program specified' >&2; exit 4;"
PreventExit=true
FailDelay=1800
MaxFailIPCnt=3
FailIPDelay=15
RepeatDelay=60
WGetDelay=5
WGetLongFailDelay=60
WGetMaxFails=10
SuccessCmd=""
FailCmd=""
ExitCmd=""
WGetErrMess[1]="Generic error code"
WGetErrMess[2]="Parse error"
WGetErrMess[3]="File I/O error"
WGetErrMess[4]="Network failure"
WGetErrMess[5]="SSL verification failure"
WGetErrMess[6]="Username/password authentication failure"
WGetErrMess[7]="Protocol errors"
WGetErrMess[8]="Server issued an error response"

CurrIP="x"

test -r $CONFFILE || ( echo "Failed to load the configuration file $CONFFILE..." >&2;
        exit 1 )

source $CONFFILE

touch $TEMPFILE
echo "" > $TEMPFILE

touch $PIDFILE
echo $PID > $PIDFILE

while true
do
        source $TEMPFILE
        FailCounter=$MaxFailIPCnt
        NextDelay=$RepeatDelay
        NewIP=`$FindIPComm`
        Status=$?

        while (( $Status != 0 ))
        do
                CurDate=`date +%T`
                $LOGME "$CurDate WARNING: Could not read the IP address"
                if (( $FailCounter == 0 ))
                then
                        $LOGME "Too many failures... Abording"
                        rm $PIDFILE
                        exit 2
                fi
                sleep $FailIPDelay
                PingStat=`ping -c1 www.no-ip.com > /dev/null; echo $?`
                if [[ "$PingStat" == "0" ]]
                then
                        NewIP=`$FindIPComm`
                        Status=$?
                        FailCounter=$((FailCounter -1))
                fi
        done

        if [[ "$CurrIP" != "$NewIP" ]]
        then
                CurDate=`date +%T`
                WGetStatus=`wget -qO $TEMPWGET -U "$USERAGENT" --user=$NoIPUser --password=$NoIPPass "http://dynupdate.no-ip.com/nic/update?hostname=${NoIPHost}&myip=$NewIP"; echo $?`
                NoIPResp=`cat $TEMPWGET`
                if (( $WGetStatus == 0 ))
                then
                        WGetFails=$WGetMaxFails
                        case ${NoIPResp/ */} in
                                "good")
                                        FailIPResult=0
                                        echo "CurrIP=$NewIP" > $TEMPFILE
                                        $LOGME "IP Address successfuly updated"
                                        ;;
                                "nochg")
                                        FailIPResult=1
                                        HostIPResp=`host $NoIPHost`
                                        if (( $? == 0 ))
                                        then
                                                HostIP=${HostIPResp/* /}
                                                if [[ "$HostIP" == "$NewIP" ]]
                                                then
                                                        FailIPResult=0
                                                        Comment="successfuly"
                                                        echo "CurrIP=$HostIP" > $TEMPFILE
                                                else
                                                        Comment="but it seems different than expected..."
                                                        NextDelay=$FailIPDelay
                                                fi
                                        else
                                                Comment="but could not resolve $NoIPHost"
                                                NextDelay=$FailIPDelay
                                        fi
                                        $LOGME "$CurDate WARNING: The IP in NoIP was updated by another process $Comment"
                                        ;;
                                "nohost")
                                        FailIPResult=1
                                        $LOGME "$CurDate ERROR: Hostname supplied does not exist under specified account"
                                        ;;
                                "badauth")
                                        FailIPResult=1
                                        $LOGME "$CurDate ERROR: Invalid username password combination"
                                        ;;
                                "badagent")
                                        FailIPResult=2
                                        $LOGME "$CurDate ERROR: Client disabled. Client should exit and not perform any more updates without user intervention"
                                        ;;
                                "\!donator")
                                        $LOGME "$CurDate ERROR: An update request was sent including a feature that is not available to that particular user"
                                        FailIPResult=1
                                        ;;
                                "abuse")
                                        $LOGME "$CurDate ERROR: Username is blocked due to abuse. Either for not following our update specifications or disabled due to violation of the No-IP terms of service"
                                        FailIPResult=1
                                        ;;
                                "911")
                                        $LOGME "$CurDate FATAL ERROR: NoIP.com error such as a database outage. Retry the update no sooner 30 minutes"
                                        FailIPResult=1
                                        NextDelay=1800
                                        ;;
                                *)
                                        $LOGME "$CurDate ERROR: Unknown responce from NoIP.com - $NoIPResp"
                                        FailIPResult=1
                                        ;;
                        esac

                        case $FailIPResult in
                                0)
                                        eval $SuccessCmd
                                        ;;
                                1)
                                        eval $FailCmd
                                        ;;
                                *)
                                        if $PreventExit
                                        then
                                                NextDelay=$FailDelay
                                                eval $FailCmd
                                        else
                                                eval $ExitCmd
                                                rm $PIDFILE
                                                exit 3
                                        fi
                                        ;;
                        esac
                else
                        $LOGME "$CurDate ERROR: Wget exited with error status $WGetStatus: ${WGetErrMess[$WGetStatus]}"
                        WGetFails=$((WGetFails -1))
                        if (( $WGetFails == 0 ))
                        then
                                WGetFails=1
                                NextDelay=$WGetLongFailDelay
                        else
                                NextDelay=$WGetDelay
                        fi
                fi
        fi

        sleep $NextDelay
done

Αυτό το script είναι γραμμένο σε bash shell. Και αυτό για να λειτουργήσει χρειάζεται το configuration αρχείο του, /etc/IP-Updater.conf. Ένα δείγμα του εν λόγω αρχείου φαίνεται ακολούθως:

The is a bash shell script. It also needs its configuration file, /etc/IP-Updater.conf. A sample of this file follows:

NoIPUser=noipusermail@myisp.gr
NoIPPass="MyUltraFancyPassword!"
NoIPHost=myhostname.no-ip.biz
FindIPComm="/usr/scripts/GetInetIP.py"
FailIPDelay=15
RepeatDelay=60
FailDelay=1800
MaxFailIPCnt=3
PreventExit=true
SuccessCmd="/usr/bin/ttytter -keyf=/etc/ttytter/Servertty -silent -status=\"\`date\` IP Updated successfully. NoIP Response is: \$NoIPResp\""
FailCmd="/usr/bin/ttytter  -keyf=/etc/ttytter/Servertty -silent -status=\"\`date\` IP Update failed. NoIP Response is: \$NoIPResp\""
ExitCmd="/usr/bin/ttytter  -keyf=/etc/ttytter/Servertty -silent -status=\"\`date\` Fatal Error during IP Update. NoIP response is: \$NoIPResp. Exiting...\""

Ας δουμε μια λίστα με πιθανές παραμέτρους του αρχείου:

Lets see a list of the possible parameters that can be specified in this file:

  • NoIPUser: Δηλώνει τη διεύθυνση mail του λογαριασμού του χρήστη που έχουμε φτιάξει στο NoIPSpecifies the e-mail address of the NoIP user's account we have created
  • NoIPPass: Το password που έχουμε στο NoIP λογαριασμόThe password for this account
  • NoIPHost: Το όνομα του domain που έχουμε καταχωρήσειThe domain name we own (or the one from the default that NoIP provides).
  • FindIPComm: Δηλώνεται το script που όταν τρέξει μας δίνει τη διεύθυνση IP του router. Κοινώς, το script που φτιάξαμε πριν σε pythonThe script name (command) that returns the IP address from our router. It is the script in python, we created earlier
  • FailIPDelay: Όταν δεν καταφέρει να διαβάσει την IP από το router, όπως σε περίπτωση που το router δεν έχει πάρει ακόμα IP μετά από διακοπή ρεύματος, ή το router είναι σβηστό, το script περιμένει όσα δευτερόλεπτα ορίζονται σε αυτή την παράμετρο, πριν ξαναπροσπαθήσειWhen the script fails to read the WAN IP from the router, as in case the router still waits for the ISP to send its IP after a power failure, or even if the router is powered off, the script sleeps for as many seconds as specified in this parameter before retrying
  • RepeatDelay: Όταν διαβάσει σωστά μια IP διεύθυνση, δηλώνει σε πόσο χρόνο θα ξαναπροσπαθήσει να δει αν υπάρχει αλλαγή τηςWhen the script fetches the IP address correctly, this parameter defines how much time it will wait until it checks again for an IP change
  • FailDelay: Όταν υπάρξει πρόβλημα στην ενημέρωση της καινούργιας IP, όπως σε περίπτωση που το NoIP επιστρέψει "911", το script περιμένει όσο χρόνο δηλώνεται εδώ σε δευτερόλεπτα πριν ξαναπροσπαθήσει την ενημέρωση του NoIP DNSWhen there is a failure during NoIP address update request, as in case NoIP server answers with "911", the script waits for such time as described in here, in seconds before retrying
  • MaxFailIPCnt: Ο μέγιστος αριθμός προσπαθειών στις οποίες δε θα μπορέσει να διαβάσει την IP διεύθυνση από το router, ενώ πιστοποιείται πως υπάρχει σύνδεση με το internet. Αυτή η κατάσταση κανονικά δεν πρέπει να υπάρξει ποτέ.The maximum number of retries that will fail to read the IP address from the router, while there is certified internet connection. Normally, this state will never be reached.
  • PreventExit:true ήor false. Αν είναι true τότε το script δε σταματάει ποτέ, ούτε σε περίπτωση λάθους όπως π.χ. username/password στο NoIPIf it is true the script never ends, not even in case of a failure like ie. wrong NoIP username/password
  • SuccessCmd: Εντολή που θα εκτελείται όταν έχουμε μια σωστή απάντηση από το NoIP όσον αφορά την ανανέωση της IP μας. Στο παράδειγμα φαίνεται να τρέχει το ttytter με τέτοιο τρόπο ώστε να μπορεί να στέλνει tweet την κατάστασή του. Δώστε βάση στον τρόπο με τον οποίο δηλώνονται μεταβλητές και εσωτερικές εκτελέσεις εντολών, όπως η dateCommand to be executed when there is a correct answer from NoIP for our IP address updating to DNS tables. In this example ttytter is executed in a way to tweet us the DNS status. Pay attention to the way the variables are specified in this command and interna command execution, like date
  • FailCmd: Το ίδιο, αλλά για την περίπτωση όπου υπάρξει απάντηση σφάλματος από το NoIPSame thing for the case of a NoIP answer for failure
  • ExitCmd: Ομοίως για την περίπτωση όπου υπάρξει τέτοιο σφάλμα όπου θα πρέπει το script να τερματιστείAnd again same thing in case the failure is severe and the script must be terminated
  • WGetDelay: Το script χρησιμοποιεί τη wget για να στείλει το request στο NoIP server. Σε περίπτωση που υπάρξει σφάλμα περιμένει όσο χρόνο του ορίζει αυτή η μεταβλητήThis script uses wget to send a request to NoIP server. In case of request failure it wait for as long as this parameter specifies
  • WGetMaxFails: Όταν υπάρξουν τόσα σφάλματα στη σειρά από τη wget όσα ορίζονται σε αυτή την παράμετρο, τότε ο χρόνος αναμονής μέχρι το επόμενο wget που θα επιχειρηθεί ορίζεται από την WGetLongFailDelayWhen there are as many wget failures as this parameter specifies, then the delay until any further wget request will be as WGetLongFailDelay specifies.
  • WGetLongFailDelay: Μετά από WGetMaxFails αποτυχίες της wget ο χρόνος που περιμένει πριν ξαναδοκιμάσει να στείλει request ορίζεται από αυτή την παράμετροAfter WGetMaxFails wget failures, the delay interval till next retry is specified by this parameter

Το script μας δίνει τη δυνατότητα να εκτελέσουμε κάποιες εντολές, ανάλογα με το αν ήταν επιτυχής η αλλαγή της IP, αν υπήρξε κάποιο σφάλμα, ή αν υπήρξε μια ανεπίτρεπτη κατάσταση που οδηγεί το script σε τερματισμό. Οι εντολές αυτές ορίζονται στις παραμέτρους SuccessCmd, FailCmd και ExitCmd. Σε αυτές τις εντολές, μπορεί να θέλουμε να χρησιμοποιήσουμε κάποιες πληροφορίες από το script. Οι δυνατές περιπτώσεις είναι οι:

The script provides us the ability to execute some commands depending on the case of success or failure of the IP update request, or even if there was a severe error that forced the script to terminate. The commands in question are specified in SuccessCmd, FailCmd and ExitCmd parameters. In these commands we may need to use some information coming from the script. The possible script variables are:

  • $NoIPResp: Περιέχει την απάντηση από τον server της NoIPContains the answer of the NoIP server
  • $NewIP: Περιέχει την καινούργια IP διεύθυνσηContains the new IP address
  • $CurrIP: Περιέχει την παλιά IP διεύθυνσηContains the old IP address
  • $CurDate: Περιέχει την ώρα που ανιχνεύτηκε η αλλαγή της IPContains the time value the IP change was detected

Και φυσικά, μιας και σε αυτό το αρχείο περιέχονται ευαίσθητες πληροφορίες, θα πρέπει να μπορεί να το διαβάσει μόνο ο root:

Well, of course in this file there are also sensitive information that only root should be able to read:

pi@raspberrypi:/usr/scripts > chown root:root /etc/IP-Updater.conf
pi@raspberrypi:/usr/scripts > chmod 0600 /etc/IP-Updater.conf
pi@raspberrypi:/usr/scripts > 

Τέλος, μιας και αυτά τα δύο scripts είναι βασικά για τη λειτουργία του συστήματός μας, καλό είναι και αυτά να τα κάνουμε μη προσβάσιμα από τους χρήστες (εκτός του root):

Finally, since both scripts are mandatory for the functionality of our system, it is a good practice not to let any user access them (unless, of course root):

pi@raspberrypi:/usr/scripts > sudo chown -R root:root /usr/scripts
pi@raspberrypi:/usr/scripts > sudo chmod -R 0700 /usr/scripts
pi@raspberrypi:/usr/scripts > 

Μετατροπή του καινούργιου client σε υπηρεσία

Conversion of the new client to a system service

Το τελικό στάδιο είναι να εκτελείται το script που φτιάξαμε ως υπηρεσία του συστήματος. Κάποιος θα μπορούσε πολύ απλά να το τρέξει μέσα από τον cron. Αυτό όμως δεν θεωρείται και τόσο καλή ιδέα. Είναι σαφώς καλύτερα να λειτουργεί όπως όλες οι υπηρεσίες του συστήματος, να ενεργοποιείται και να απενεργοποιείται ανάλογα με το runlevel κ.λ.π. Για να το πετύχουμε αυτό θα πρέπει να φτιάξουμε ένα ακόμα script. Μια παρόμοια εργασία κάναμε και σε προηγούμενο άρθρο. Θα ακολουθήσουμε, λοιπόν, την ίδια τακτική.

The final stage is to let the script we created run as a system service. Somebody would let it start through cron. But this is not considered a good practice. It is far better to let it act as any other system service, start or stop depending on the runlevel of the system, etc. To reach our goal we must create another one script. A similar process we followed in an earlier article. We will follow the same tactics.

Κάντε Copy/Paste τον ακόλουθο κώδικα στον αγαπημένο σας editor:

Again, you can Copy/Paste the following source code in your favourite editor:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          IP-Updater
# Required-Start:    $local_fs $syslog $network
# Required-Stop:     $local_fs $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: IP Updater for NoIP Dynamic DNS service
# Description:       Start IP Updater to check the IP that the ISP has given to router.
#       When the IP is changed, the updater informs NoIP and updates the chosen domain name
#       to point to the new IP.
### END INIT INFO

# Author: Elias Chrysocheris 
#

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="IP Updating client service for NoIP (http://www.noip.com/)"
NAME=IP-Updater
DAEMON=/usr/scripts/IP-Updater.sh
SCRIPTNAME=/etc/init.d/$NAME
PIDFILE=/var/run/IP-Updater.pid

# Exit if the package is not installed
[ -e "$DAEMON" ] || { echo "Cannot find IP-Updater executable in $DAEMON. Perhaps it is not installed...";
        if [ "$1" = "stop" ]; then exit 0;
        else exit 5; fi; }

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --test --start --quiet --pidfile $PIDFILE --exec $DAEMON > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON &
        RETVAL="$?"
        [ "$RETVAL" > 2 ] && return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
        [ "$?" = 2 ] && return 2
        rm $PIDFILE
        return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        #
        # If the daemon can reload its configuration without
        # restarting (for example, when it is sent a SIGHUP),
        # then implement that here.
        #
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
        return 0
}

case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
  #reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        #log_daemon_msg "Reloading $DESC" "$NAME"
        #do_reload
        #log_end_msg $?
        #;;
  restart|force-reload)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac

Το script βασίστηκε για άλλη μια φορά στο κλασικό skeleton που υπάρχει στον κατάλογο /etc/init.d/. Εκεί θα το σώσουμε σαν root με το όνομα IP-Updater. Αν δεν έχουμε τη δυνατότητα να το κάνουμε απ' ευθείας, μπορούμε σαν χρήστες να το σώσουμε στον κατάλογό μας και στη συνέχεια να το αντιγράψουμε ως root στην κατάλληλη θέση. Στη συνέχεια αλλάζουμε τα δικαιώματα και το κάνουμε εκτελέσιμο και ενημερώνουμε το σύστημα για την καινούργια υπηρεσία:

This script is based once more in the clasical skeleton that lives under /etc/init.d/. There we eill save it as root user under the name IP-Updater. If you cannot do it straight forward, you can save it under your home directory and then you can copy it as root to the correct place. Then you can change its access rights making it executable and inform the system for the new service:

pi@raspberrypi:/usr/scripts > cd /etc/init.d/
pi@raspberrypi:/etc/init.d > sudo cp /home/pi/IP-Updater .
pi@raspberrypi:/etc/init.d > sudo chmod 0755 IP-Updater
pi@raspberrypi:/etc/init.d > sudo update-rc.d IP-Updater defaults
pi@raspberrypi:/etc/init.d > 

Μετά από αυτό μπορούμε να εκκινήσουμε την υπηρεσία μας. Όμως, για να δοκιμάσουμε ότι όλα βαίνουν καλώς, καλό είναι να δούμε αν ενεργοποιείται αυτόματα με μια επανεκκίνηση:

After that we can start the new service. But first we have to make sure everything is fine and the service starts automatically after a reboot:

pi@raspberrypi:/usr/scripts > sudo shutdown -r now
pi@raspberrypi:/etc/init.d > 

Αποτελέσματα

Conclusions

Εαν όλα έχουν πάει καλά, τότε μπορείτε να παρακολουθήσετε τα μηνύματα στο /var/log/messages και να δείτε τη λειτουργία του script. Αν προσέξατε, στο configuration αρχείο του IP-Updater, χρησιμοποιείται το ttytter που είναι ένας client για το twitter. Μέσω αυτού βλέπουμε on-line τη λειτουργία της αλλαγής διεύθυνσης

If everything is done correctly then you could see the information in /var/log/messages and watch the service in action. You must have noticed, in the IP-Updater configuration file, ttytter is used which is a command line twitter client. Using that we can observe on-line the function of IP updating

Αυτό φυσικά προϋποθέτει να έχετε στήσει το ttytter και να το έχετε ρυθμίσει.

For this you are supposed to have ttytter installed and configured.

Έπειτα από αρκετό καιρό που λειτουργεί η υπηρεσία του NoIP στο Raspberry Pi δεν έχω πετύχει κατάσταση που να μη μπορώ να έχω πρόσβαση μέσω του δικτύου, απομακρυσμένα. Η απόκριση του συστήματος είναι παραπάνω από ικανοποιητική.

Using NoIP service system in Raspberry Pi for enough time now, I never had that awkward moment of not having access to my system through the internet. The system's response is more than satisfactory.


Ηλίας Χρυσοχέρης

Elias Chrysocheris