From 33bba3927e23454d8c663b3fcae217d98e92b521 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:04 +0700
Subject: [PATCH 001/522] New translations 1-berkenalan-dengan-golang.md
(English)
---
en/content-en/1-berkenalan-dengan-golang.md | 29 +++++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 en/content-en/1-berkenalan-dengan-golang.md
diff --git a/en/content-en/1-berkenalan-dengan-golang.md b/en/content-en/1-berkenalan-dengan-golang.md
new file mode 100644
index 000000000..e416a159d
--- /dev/null
+++ b/en/content-en/1-berkenalan-dengan-golang.md
@@ -0,0 +1,29 @@
+# Belajar Golang (Gratis!)
+
+**[Golang](https://golang.org/)** (atau biasa disebut dengan **Go**) adalah bahasa pemrograman yang dikembangkan di **Google** oleh **[Robert Griesemer](https://github.com/griesemer)**, **[Rob Pike](https://en.wikipedia.org/wiki/Rob_Pike)**, dan **[Ken Thompson](https://en.wikipedia.org/wiki/Ken_Thompson)** pada tahun 2007 dan mulai diperkenalkan ke publik tahun 2009.
+
+Penciptaan bahasa Go didasari bahasa **C** dan **C++**, oleh karena itu gaya sintaksnya mirip.
+
+#### Kelebihan Go
+
+Go memiliki kelebihan dibanding bahasa lainnya, beberapa di antaranya:
+
+* Mendukung konkurensi di level bahasa dengan pengaplikasian cukup mudah
+* Mendukung pemrosesan data dengan banyak prosesor dalam waktu yang bersamaan *(pararel processing)*
+* Memiliki *garbage collector*
+* Proses kompilasi sangat cepat
+* Bukan bahasa pemrograman yang hirarkial dan bukan *strict* OOP, memberikan kebebasan ke developer perihal bagaimana cara penulisan kode.
+* Dependensi dan *tooling* yang disediakan terbilang lengkap.
+* Dukungan komunitas sangat bagus. Banyak tools yang tersedia secara gratis dan *open source* yang bisa langsung dimanfaatkan.
+
+Sudah banyak industri dan perusahaan yg menggunakan Go sampai level production, termasuk di antaranya adalah Google sendiri, dan juga tempat di mana penulis bekerja 😁
+
+---
+
+Pada buku ini (terutama semua serial chapter A) kita akan belajar tentang dasar pemrograman Go, mulai dari 0, dan gratis.
+
+
+
+---
+
+
From cb38707e3c62b1f90157da06465e94381f0fb477 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:05 +0700
Subject: [PATCH 002/522] New translations 2-instalasi-golang.md (English)
---
en/content-en/2-instalasi-golang.md | 106 ++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
create mode 100644 en/content-en/2-instalasi-golang.md
diff --git a/en/content-en/2-instalasi-golang.md b/en/content-en/2-instalasi-golang.md
new file mode 100644
index 000000000..88e8ee6c3
--- /dev/null
+++ b/en/content-en/2-instalasi-golang.md
@@ -0,0 +1,106 @@
+# A.2. Instalasi Golang (Stable & Unstable)
+
+Hal pertama yang perlu dilakukan sebelum bisa menggunakan Go adalah meng-*install*-nya terlebih dahulu. Panduan instalasi sebenarnya sudah disediakan di situs resmi Go [http://golang.org/doc/install#install](http://golang.org/doc/install#install).
+
+Di sini penulis mencoba meringkas petunjuk instalasi pada *link* di atas, agar lebih mudah untuk diikuti terutama untuk pembaca yang baru belajar.
+
+> Go yang digunakan adalah versi **1.20**, direkomendasikan menggunakan versi tersebut.
+
+URL untuk mengunduh *installer* Go: https://golang.org/dl/. Silakan langsung unduh dari *link* tersebut lalu lakukan proses instalasi, atau bisa mengikuti petunjuk pada chapter ini.
+
+## A.2.1. Instalasi Go *Stable*
+
+#### • Instalasi Go di Windows
+
+ 1. Download terlebih dahulu *installer*-nya di [https://golang.org/dl/](https://golang.org/dl/). Pilih *installer* untuk sistem operasi Windows sesuai jenis bit yang digunakan.
+
+ 2. Setelah ter-*download*, jalankan *installer*, klik *next* hingga proses instalasi selesai. *By default* jika anda tidak merubah path pada saat instalasi, Go akan ter-*install* di `C:\go`. *Path* tersebut secara otomatis akan didaftarkan dalam `PATH` *environment variable*.
+
+ 3. Buka *Command Prompt* / *CMD*, eksekusi perintah berikut untuk mengecek versi Go.
+
+ ```bash
+ go version
+ ```
+
+ 4. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil.
+
+> Sering terjadi, command `go version` tidak bisa dijalankan meskipun instalasi sukses. Solusinya bisa dengan restart CMD (tutup CMD, kemudian buka lagi). Setelah itu coba jalankan ulang command di atas.
+
+#### • Instalasi Go di MacOS
+
+Cara termudah instalasi Go di MacOS adalah menggunakan [Homebrew](http://brew.sh/).
+
+ 1. *Install* terlebih dahulu Homebrew (jika belum ada), caranya jalankan perintah berikut di **terminal**.
+
+ ```bash
+ $ ruby -e "$(curl -fsSL http://git.io/pVOl)"
+ ```
+
+ 2. *Install* Go menggunakan command `brew`.
+
+ ```bash
+ $ brew install go
+ ```
+
+ 3. Tambahkan path binary Go ke `PATH` *environment variable*.
+
+ ```bash
+ $ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bash_profile
+ $ source ~/.bash_profile
+ ```
+
+ 4. Jalankan perintah berikut mengecek versi Go.
+
+ ```bash
+ go version
+ ```
+
+ 5. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil.
+
+#### • Instalasi Go di Linux
+
+ 1. Unduh arsip *installer* dari [https://golang.org/dl/](https://golang.org/dl/), pilih installer untuk Linux yang sesuai dengan jenis bit komputer anda. Proses download bisa dilakukan lewat CLI, menggunakan `wget` atau `curl`.
+
+ ```bash
+ $ wget https://storage.googleapis.com/golang/go1...
+ ```
+
+ 2. Buka terminal, *extract* arsip tersebut ke `/usr/local`.
+
+ ```bash
+ $ tar -C /usr/local -xzf go1...
+ ```
+
+ 3. Tambahkan path binary Go ke `PATH` *environment variable*.
+
+ ```bash
+ $ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc
+ $ source ~/.bashrc
+ ```
+
+ 4. Selanjutnya, eksekusi perintah berikut untuk mengetes apakah Go sudah terinstal dengan benar.
+
+ ```bash
+ go version
+ ```
+
+ 5. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil.
+
+## A.2.2. Variabel `GOROOT`
+
+*By default*, setelah proses instalasi Go selesai, secara otomatis akan muncul *environment variable* `GOROOT`. Isi dari variabel ini adalah lokasi di mana Go ter-*install*.
+
+Sebagai contoh di Windows, ketika Go di-*install* di `C:\go`, maka path tersebut akan menjadi isi dari `GOROOT`.
+
+Silakan gunakan command `go env` untuk melihat informasi konfigurasi *environment* yang ada.
+
+## A.2.3. Instalasi Go *Unstable*/*Development*
+
+Jika pembaca tertarik untuk mencoba versi development Go, ingin mencoba fitur yang belum dirilis secara official, ada beberapa cara:
+
+- Instalasi dengan *build from source* https://go.dev/doc/install/source
+- Gunakan command `go install`, contohnya seperti `go install golang.org/dl/go1.18beta1@latest`. Untuk melihat versi unstable yang bisa di-install silakan merujuk ke https://go.dev/dl/#unstable
+
+---
+
+
From a8023e5fb4a16449e61706fa026f8a6f6f91a633 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:06 +0700
Subject: [PATCH 003/522] New translations a-array.md (English)
---
en/content-en/A-array.md | 206 +++++++++++++++++++++++++++++++++++++++
1 file changed, 206 insertions(+)
create mode 100644 en/content-en/A-array.md
diff --git a/en/content-en/A-array.md b/en/content-en/A-array.md
new file mode 100644
index 000000000..0bc7abfd1
--- /dev/null
+++ b/en/content-en/A-array.md
@@ -0,0 +1,206 @@
+# A.15. Array
+
+Array adalah kumpulan data bertipe sama, yang disimpan dalam sebuah variabel. Array memiliki kapasitas yang nilainya ditentukan pada saat pembuatan, menjadikan elemen/data yang disimpan di array tersebut jumlahnya tidak boleh melebihi yang sudah dialokasikan. Default nilai tiap elemen array pada awalnya tergantung dari tipe datanya. Jika `int` maka tiap element zero value-nya adalah `0`, jika `bool` maka `false`, dan seterusnya. Setiap elemen array memiliki indeks berupa angka yang merepresentasikan posisi urutan elemen tersebut. Indeks array dimulai dari 0.
+
+Contoh penerapan array:
+
+```go
+var names [4]string
+names[0] = "trafalgar"
+names[1] = "d"
+names[2] = "water"
+names[3] = "law"
+
+fmt.Println(names[0], names[1], names[2], names[3])
+```
+
+Variabel `names` dideklarasikan sebagai `array string` dengan alokasi elemen `4` slot. Cara mengisi slot elemen array bisa dilihat di kode di atas, yaitu dengan langsung mengakses elemen menggunakan indeks, lalu mengisinya.
+
+
+
+## A.15.1. Pengisian Elemen Array yang Melebihi Alokasi Awal
+
+Pengisian elemen array pada indeks yang tidak sesuai dengan alokasi menghasilkan error. Contoh sederhana, jika array memiliki 4 slot, maka pengisian nilai slot 5 seterusnya adalah tidak valid.
+
+```go
+var names [4]string
+names[0] = "trafalgar"
+names[1] = "d"
+names[2] = "water"
+names[3] = "law"
+names[4] = "ez" // baris kode ini menghasilkan error
+```
+
+Solusi dari masalah di atas adalah dengan menggunakan keyword `append`, yang nantinya pada chapter selanjutnya ([A.16. Slice](/A-slice.html)) akan kita bahas.
+
+## A.15.2. Inisialisasi Nilai Awal Array
+
+Pengisian elemen array bisa dilakukan pada saat deklarasi variabel. Caranya dengan menuliskan data elemen dalam kurung kurawal setelah tipe data, dengan pembatas antar elemen adalah tanda koma (`,`).
+
+```go
+var fruits = [4]string{"apple", "grape", "banana", "melon"}
+
+fmt.Println("Jumlah element \t\t", len(fruits))
+fmt.Println("Isi semua element \t", fruits)
+```
+
+Penggunaan fungsi `fmt.Println()` pada data array tanpa mengakses indeks tertentu, akan menghasilkan output dalam bentuk string dari semua array yang ada. Teknik ini biasa digunakan untuk *debugging* data array.
+
+
+
+Fungsi `len()` dipakai untuk menghitung jumlah elemen sebuah array.
+
+## A.15.3. Inisialisasi Nilai Array Dengan Gaya Vertikal
+
+Elemen array bisa dituliskan dalam bentuk horizontal (seperti yang sudah dicontohkan di atas) ataupun dalam bentuk vertikal.
+
+```go
+var fruits [4]string
+
+// cara horizontal
+fruits = [4]string{"apple", "grape", "banana", "melon"}
+
+// cara vertikal
+fruits = [4]string{
+ "apple",
+ "grape",
+ "banana",
+ "melon",
+}
+```
+
+Khusus untuk deklarasi array dengan cara vertikal, tanda koma wajib dituliskan setelah elemen, termasuk elemen terakhir. Jika tidak, maka akan muncul error.
+
+## A.15.4. Inisialisasi Nilai Awal Array Tanpa Jumlah Elemen
+
+Deklarasi array yang nilainya diset di awal, boleh tidak dituliskan jumlah lebar array-nya, cukup ganti dengan tanda 3 titik (`...`). Jumlah elemen akan di kalkulasi secara otomatis menyesuaikan data elemen yang diisikan.
+
+```go
+var numbers = [...]int{2, 3, 2, 4, 3}
+
+fmt.Println("data array \t:", numbers)
+fmt.Println("jumlah elemen \t:", len(numbers))
+```
+
+Variabel `numbers` akan secara otomatis memiliki jumlah elemen `5`, karena pada saat deklarasi disiapkan 5 buah elemen.
+
+
+
+## A.15.5. Array Multidimensi
+
+Array multidimensi adalah array yang tiap elemennya juga berupa array (dan bisa seterusnya, tergantung ke dalaman dimensinya).
+
+Cara deklarasi array multidimensi secara umum sama dengan cara deklarasi array biasa, dengan cara menuliskan data array dimensi selanjutnya sebagai elemen array dimensi sebelumnya.
+
+Khusus untuk array yang merupakan sub dimensi atau elemen, boleh tidak dituliskan jumlah datanya. Contohnya bisa dilihat pada deklarasi variabel `numbers2` di kode berikut.
+
+```go
+var numbers1 = [2][3]int{[3]int{3, 2, 3}, [3]int{3, 4, 5}}
+var numbers2 = [2][3]int{{3, 2, 3}, {3, 4, 5}}
+
+fmt.Println("numbers1", numbers1)
+fmt.Println("numbers2", numbers2)
+```
+
+Kedua array di atas memiliki elemen yang sama.
+
+
+
+## A.15.6. Perulangan Elemen Array Menggunakan Keyword `for`
+
+Keyword `for` dan array memiliki hubungan yang sangat erat. Dengan memanfaatkan perulangan menggunakan keyword ini, elemen-elemen dalam array bisa didapat.
+
+Ada beberapa cara yang bisa digunakan untuk me-looping data array, yg pertama adalah dengan memanfaatkan variabel iterasi perulangan untuk mengakses elemen berdasarkan indeks-nya. Contoh:
+
+```go
+var fruits = [4]string{"apple", "grape", "banana", "melon"}
+
+for i := 0; i < len(fruits); i++ {
+ fmt.Printf("elemen %d : %s\n", i, fruits[i])
+}
+```
+
+Perulangan di atas dijalankan sebanyak jumlah elemen array `fruits` (bisa diketahui dari kondisi `i < len(fruits`). Di tiap perulangan, elemen array diakses lewat variabel iterasi `i`.
+
+
+
+## A.15.7. Perulangan Elemen Array Menggunakan Keyword `for` - `range`
+
+Ada cara yang lebih sederhana me-looping data array, dengan menggunakan keyword `for` - `range`. Contoh pengaplikasiannya bisa dilihat di kode berikut.
+
+```go
+var fruits = [4]string{"apple", "grape", "banana", "melon"}
+
+for i, fruit := range fruits {
+ fmt.Printf("elemen %d : %s\n", i, fruit)
+}
+```
+
+Array `fruits` diambil elemen-nya secara berurutan. Nilai tiap elemen ditampung variabel oleh `fruit` (tanpa huruf s), sedangkan indeks nya ditampung variabel `i`.
+
+Output program di atas, sama dengan output program sebelumnya, hanya cara yang digunakan berbeda.
+
+## A.15.8. Penggunaan Variabel Underscore `_` Dalam `for` - `range`
+
+Kadang kala ketika *looping* menggunakan `for` - `range`, ada kemungkinan di mana data yang dibutuhkan adalah elemen-nya saja, indeks-nya tidak. Sedangkan kode di atas, `range` mengembalikan 2 data, yaitu indeks dan elemen.
+
+Seperti yang sudah diketahui, bahwa di Go tidak memperbolehkan adanya variabel yang menganggur atau tidak dipakai. Jika dipaksakan, error akan muncul, contohnya seperti kode berikut.
+
+```go
+var fruits = [4]string{"apple", "grape", "banana", "melon"}
+
+for i, fruit := range fruits {
+ fmt.Printf("nama buah : %s\n", fruit)
+}
+```
+
+Hasil dari kode program di atas:
+
+
+
+Di sinilah salah satu kegunaan variabel pengangguran, atau underscore (`_`). Tampung saja nilai yang tidak ingin digunakan ke underscore.
+
+```go
+var fruits = [4]string{"apple", "grape", "banana", "melon"}
+
+for _, fruit := range fruits {
+ fmt.Printf("nama buah : %s\n", fruit)
+}
+```
+
+Pada kode di atas, yang sebelumnya adalah variabel `i` diganti dengan `_`, karena kebetulan variabel `i` tidak digunakan.
+
+
+
+Jika yang dibutuhkan hanya indeks elemen-nya saja, bisa gunakan 1 buah variabel setelah keyword `for`.
+
+```go
+for i, _ := range fruits { }
+// atau
+for i := range fruits { }
+```
+
+## A.15.9. Alokasi Elemen Array Menggunakan Keyword `make`
+
+Deklarasi sekaligus alokasi data array juga bisa dilakukan lewat keyword `make`.
+
+```go
+var fruits = make([]string, 2)
+fruits[0] = "apple"
+fruits[1] = "manggo"
+
+fmt.Println(fruits) // [apple manggo]
+```
+
+Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diinginkan, parameter kedua adalah jumlah elemennya. Pada kode di atas, variabel `fruits` tercetak sebagai array string dengan alokasi 2 slot.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From c00250bd25920cae60eda80dfbfe376eb7438f8a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:07 +0700
Subject: [PATCH 004/522] New translations a-buffered-channel.md (English)
---
en/content-en/A-buffered-channel.md | 77 +++++++++++++++++++++++++++++
1 file changed, 77 insertions(+)
create mode 100644 en/content-en/A-buffered-channel.md
diff --git a/en/content-en/A-buffered-channel.md b/en/content-en/A-buffered-channel.md
new file mode 100644
index 000000000..cdac3e3dd
--- /dev/null
+++ b/en/content-en/A-buffered-channel.md
@@ -0,0 +1,77 @@
+# A.32. Buffered Channel
+
+Proses transfer data pada channel secara default dilakukan dengan cara **un-buffered**, atau tidak di-buffer di memori. Ketika terjadi proses kirim data via channel dari sebuah goroutine, maka harus ada goroutine lain yang menerima data dari channel yang sama, dengan proses serah-terima yang bersifat blocking. Maksudnya, baris kode setelah kode pengiriman dan penerimaan data tidak akan di proses sebelum proses serah-terima itu sendiri selesai.
+
+Buffered channel sedikit berbeda. Pada channel jenis ini, ditentukan angka jumlah buffer-nya. Angka tersebut menjadi penentu jumlah data yang bisa dikirimkan bersamaan. Selama jumlah data yang dikirim tidak melebihi jumlah buffer, maka pengiriman akan berjalan **asynchronous** (tidak blocking).
+
+Ketika jumlah data yang dikirim sudah melewati batas buffer, maka pengiriman data hanya bisa dilakukan ketika salah satu data yang sudah terkirim adalah sudah diambil dari channel di goroutine penerima, sehingga ada slot channel yang kosong.
+
+Proses pengiriman data pada buffered channel adalah *asynchronous* ketika jumlah data yang dikirim tidak melebihi batas buffer. Namun pada bagian channel penerimaan data selalu bersifat *synchronous*.
+
+
+
+## A.32.1. Penerapan Buffered Channel
+
+Penerapan buffered channel pada dasarnya mirip seperti channel biasa. Perbedaannya hanya pada penulisan deklarasinya, perlu ditambahkan angka buffer sebagai argumen `make()`.
+
+Berikut adalah contoh penerapan buffered channel. Program di bawah ini merupakan pembuktian bahwa pengiriman data lewat buffered channel adalah asynchronous selama jumlah data yang sedang di-buffer oleh channel tidak melebihi kapasitas buffer.
+
+```go
+package main
+
+import (
+ "fmt"
+ "runtime"
+ "time"
+)
+
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ messages := make(chan int, 3)
+
+ go func() {
+ for {
+ i := <-messages
+ fmt.Println("receive data", i)
+ }
+ }()
+
+ for i := 0; i < 5; i++ {
+ fmt.Println("send data", i)
+ messages <- i
+ }
+
+ time.Sleep(1 * time.Second)
+}
+
+```
+
+Pada kode di atas, parameter kedua fungsi `make()` adalah representasi jumlah buffer. Perlu diperhatikan bahwa nilai buffered channel dimulai dari `0`. Ketika nilainya adalah **3** berarti jumlah buffer maksimal ada **4**.
+
+Bisa dilihat terdapat IIFE goroutine yang isinya proses penerimaan data dari channel `messages`, untuk kemudian datanya ditampilkan. Setelah goroutine tersebut dieksekusi, perulangan dijalankan dengan di-masing-masing perulangan dilakukan pengiriman data. Total ada 5 data dikirim lewat channel `messages` secara sekuensial.
+
+
+
+Bisa dilihat output di atas, pada proses pengiriman data ke-4, diikuti dengan proses penerimaan data; yang kedua proses tersebut berlangsung secara blocking.
+
+Pengiriman data indeks ke 0, 1, 2 dan 3 akan berjalan secara asynchronous, hal ini karena channel ditentukan nilai buffer-nya sebanyak 3 (ingat, jika nilai buffer adalah 3, maka 4 data yang akan di-buffer). Pengiriman selanjutnya (indeks 5) hanya akan terjadi jika ada salah satu data dari ke-empat data yang sebelumnya telah dikirimkan sudah diterima (dengan serah terima data yang bersifat blocking). Setelahnya, pengiriman data kembali dilakukan secara asynchronous (karena sudah ada slot buffer ada yang kosong).
+
+Karena pengiriman dan penerimaan data via buffered channel terjadi tidak selalu sycnrhonous (tergantung jumlah buffer-nya), maka ada kemungkinan dimana eksekusi program selesai namun tidak semua data diterima via channel `messages`. Karena alasan ini pada bagian akhir ditambahkan statement `time.Sleep(1 * time.Second)` agar ada jeda 1 detik sebelum program selesai.
+
+#### • Fungsi `time.Sleep()`
+
+Fungsi ini digunakan untuk menambahkan delay sebelum statement berikutnya dieksekusi. Durasi delay ditentukan oleh parameter, misal `1 * time.Second` maka durasi delay adalah 1 detik.
+
+Lebih detailnya mengenai fungsi `time.Sleep()` dan `time.Second` dibahas pada chapter terpisah, yaitu [Time Duration](https://dasarpemrogramangolang.novalagung.com/A-time-duration.html).
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 6a7fd360046171d6766646125a0479c9214766fd Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:07 +0700
Subject: [PATCH 005/522] New translations a-channel-range-close.md (English)
---
en/content-en/A-channel-range-close.md | 81 ++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
create mode 100644 en/content-en/A-channel-range-close.md
diff --git a/en/content-en/A-channel-range-close.md b/en/content-en/A-channel-range-close.md
new file mode 100644
index 000000000..1387beb16
--- /dev/null
+++ b/en/content-en/A-channel-range-close.md
@@ -0,0 +1,81 @@
+# A.34. Channel - Range dan Close
+
+Proses *retrieving* data dari banyak channel bisa lebih mudah dilakukan dengan memanfaatkan kombinasi keyword `for` - `range`.
+
+`for` - `range` jika diterapkan pada channel berfungsi untuk handle penerimaan data. Setiap kali ada pengiriman data via channel, maka akan men-trigger perulangan `for` - `range`. Perulangan akan berlangsung terus-menerus seiring pengiriman data ke channel yang dipergunakan. Dan perulangan hanya akan berhenti jika channel yang digunakan tersebut di **close** atau di non-aktifkan. Fungsi `close` digunakan utuk me-non-aktifkan channel.
+
+Channel yang sudah di-close tidak bisa digunakan lagi baik untuk menerima data ataupun untuk mengirim data, itulah mengapa perulangan `for` - `range` juga berhenti.
+
+## A.34.1. Penerapan `for` - `range` - `close` Pada Channel
+
+Berikut adalah contoh program menggunakan `for` - `range` untuk menerima data dari channel.
+
+Ok, pertama siapkan fungsi `sendMessage()` yang tugasnya mengirim data via channel. Di dalam fungsi ini dijalankan perulangan sebanyak 20 kali, ditiap perulangannya data dikirim ke channel. Channel di-close setelah semua data selesai dikirim.
+
+```go
+func sendMessage(ch chan<- string) {
+ for i := 0; i < 20; i++ {
+ ch <- fmt.Sprintf("data %d", i)
+ }
+ close(ch)
+}
+```
+
+Siapkan juga fungsi `printMessage()` untuk handle penerimaan data. Di dalam fungsi tersebut, channel di-looping menggunakan `for` - `range`. Di tiap looping, data yang diterima dari channel ditampilkan.
+
+```go
+func printMessage(ch <-chan string) {
+ for message := range ch {
+ fmt.Println(message)
+ }
+}
+```
+
+Buat channel baru dalam fungsi `main()`, jalankan `sendMessage()` sebagai goroutine (dengan ini 20 data yang berada dalam fungsi tersebut dikirimkan via goroutine baru). Tak lupa jalankan juga fungsi `printMessage()`.
+
+```go
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var messages = make(chan string)
+ go sendMessage(messages)
+ printMessage(messages)
+}
+```
+
+Setelah 20 data sukses dikirim dan diterima, channel `ch` di-non-aktifkan (`close(ch)`). Membuat perulangan data channel dalam `printMessage()` juga akan berhenti.
+
+
+
+---
+
+Berikut adalah penjelasan tambahan mengenai channel.
+
+## A.34.1.1. Channel Direction
+
+Ada yang unik dengan fitur parameter channel yang disediakan oleh Go. Level akses channel bisa ditentukan, apakah hanya sebagai penerima, pengirim, atau penerima sekaligus pengirim. Konsep ini disebut dengan **channel direction**.
+
+Cara pemberian level akses adalah dengan menambahkan tanda `<-` sebelum atau setelah keyword `chan`. Untuk lebih jelasnya bisa dilihat di list berikut.
+
+| Sintaks | Penjelasan |
+|:--------------------- |:---------------------------------------------------------------------- |
+| `ch chan string` | Parameter `ch` bisa digunakan untuk **mengirim** dan **menerima** data |
+| `ch chan<- string` | Parameter `ch` hanya bisa digunakan untuk **mengirim** data |
+| `ch <-chan string` | Parameter `ch` hanya bisa digunakan untuk **menerima** data |
+
+Pada kode di atas bisa dilihat bahwa secara default channel akan memiliki kemampuan untuk mengirim dan menerima data. Untuk mengubah channel tersebut agar hanya bisa mengirim atau menerima saja, dengan memanfaatkan simbol `<-`.
+
+Sebagai contoh fungsi `sendMessage(ch chan<- string)` yang parameter `ch` dideklarasikan dengan level akses untuk pengiriman data saja. Channel tersebut hanya bisa digunakan untuk mengirim, contohnya: `ch <- fmt.Sprintf("data %d", i)`.
+
+Dan sebaliknya pada fungsi `printMessage(ch <-chan string)`, channel `ch` hanya bisa digunakan untuk menerima data saja.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 206bf1f42637ab80c5e0a59fc516e4c40c6a9f07 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:08 +0700
Subject: [PATCH 006/522] New translations a-channel-select.md (English)
---
en/content-en/A-channel-select.md | 90 +++++++++++++++++++++++++++++++
1 file changed, 90 insertions(+)
create mode 100644 en/content-en/A-channel-select.md
diff --git a/en/content-en/A-channel-select.md b/en/content-en/A-channel-select.md
new file mode 100644
index 000000000..4ea4c87ad
--- /dev/null
+++ b/en/content-en/A-channel-select.md
@@ -0,0 +1,90 @@
+# A.33. Channel - Select
+
+Disediakannya channel membuat engineer menjadi mudah dalam me-manage goroutine. Namun perlu di-ingat, meskipun lewat channel manajemen goroutine menjadi mudah, fungsi utama channel bukan untuk kontrol, melainkan untuk sharing data antar goroutine.
+
+> Nantinya pada chapter [A.59. sync.WaitGroup](/A-waitgroup.html) akan dibahas secara komprehensif bagaimana cara optimal mengontrol goroutine.
+
+Ada kalanya kita butuh tak hanya satu channel saja untuk melakukan komunikasi data antar goroutine. Tergantung jenis kasusnya, sangat mungkin lebih dari satu channel dibutuhkan. Nah, di situlah kegunaan dari `select`. Select ini mempermudah kontrol komunikasi data lewat satu ataupun banyak channel.
+
+Cara penggunaan `select` untuk kontrol channel sama seperti penggunaan `switch` untuk seleksi kondisi.
+
+## A.33.1. Penerapan Keyword `select`
+
+Program berikut merupakan contoh sederhana penerapan select dalam channel. Dipersiapkan 2 buah goroutine, satu untuk pencarian rata-rata, dan satu untuk nilai tertinggi. Hasil operasi di masing-masing goroutine dikirimkan ke fungsi `main()` via channel (ada dua channel). Di fungsi `main()` sendiri, data tersebut diterima dengan memanfaatkan keyword `select`.
+
+Ok, langsung saja kita praktek. Pertama, siapkan 2 fungsi yang sudah dibahas di atas. Fungsi pertama digunakan untuk mencari rata-rata, dan fungsi kedua untuk penentuan nilai tertinggi dari sebuah slice.
+
+```go
+package main
+
+import "fmt"
+import "runtime"
+
+func getAverage(numbers []int, ch chan float64) {
+ var sum = 0
+ for _, e := range numbers {
+ sum += e
+ }
+ ch <- float64(sum) / float64(len(numbers))
+}
+
+func getMax(numbers []int, ch chan int) {
+ var max = numbers[0]
+ for _, e := range numbers {
+ if max < e {
+ max = e
+ }
+ }
+ ch <- max
+}
+```
+
+Kedua fungsi tersebut dijalankan sebagai goroutine. Di akhir blok masing-masing fungsi, hasil kalkulasi dikirimkan via channel yang sudah dipersiapkan, yaitu `ch1` untuk menampung data rata-rata, `ch2` untuk data nilai tertinggi.
+
+Ok lanjut, buat implementasinya pada fungsi `main()`.
+
+```go
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var numbers = []int{3, 4, 3, 5, 6, 3, 2, 2, 6, 3, 4, 6, 3}
+ fmt.Println("numbers :", numbers)
+
+ var ch1 = make(chan float64)
+ go getAverage(numbers, ch1)
+
+ var ch2 = make(chan int)
+ go getMax(numbers, ch2)
+
+ for i := 0; i < 2; i++ {
+ select {
+ case avg := <-ch1:
+ fmt.Printf("Avg \t: %.2f \n", avg)
+ case max := <-ch2:
+ fmt.Printf("Max \t: %d \n", max)
+ }
+ }
+}
+```
+
+Pada kode di atas, pengiriman data pada channel `ch1` dan `ch2` dikontrol menggunakan `select`. Terdapat 2 buah `case` kondisi penerimaan data dari kedua channel tersebut.
+
+ - Kondisi `case avg := <-ch1` akan terpenuhi ketika ada penerimaan data dari channel `ch1`, yang kemudian akan ditampung oleh variabel `avg`.
+ - Kondisi `case max := <-ch2` akan terpenuhi ketika ada penerimaan data dari channel `ch2`, yang kemudian akan ditampung oleh variabel `max`.
+
+Karena ada 2 buah channel, maka perlu disiapkan perulangan 2 kali sebelum penggunaan keyword `select`.
+
+
+
+Cukup mudah bukan?
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 021a50c6419bc84f1c29613787125c1005c78901 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:09 +0700
Subject: [PATCH 007/522] New translations a-channel-timeout.md (English)
---
en/content-en/A-channel-timeout.md | 77 ++++++++++++++++++++++++++++++
1 file changed, 77 insertions(+)
create mode 100644 en/content-en/A-channel-timeout.md
diff --git a/en/content-en/A-channel-timeout.md b/en/content-en/A-channel-timeout.md
new file mode 100644
index 000000000..4ee5d50bc
--- /dev/null
+++ b/en/content-en/A-channel-timeout.md
@@ -0,0 +1,77 @@
+# A.35. Channel - Timeout
+
+Teknik channel timeout digunakan untuk mengontrol penerimaan data dari channel mengacu ke kapan waktu diterimanya data, dengan durasi timeout bisa kita tentukan sendiri.
+
+Ketika tidak ada aktivitas penerimaan data dalam durasi yang sudah ditentukan, maka blok timeout dieksekusi.
+
+## A.35.1. Penerapan Channel Timeout
+
+Berikut adalah program sederhana contoh pengaplikasian timeout pada channel. Sebuah goroutine baru dijalankan dengan tugas mengirimkan data setiap interval tertentu, dengan durasi interval-nya sendiri adalah acak/random.
+
+```go
+package main
+
+import "fmt"
+import "math/rand"
+import "runtime"
+import "time"
+
+func sendData(ch chan<- int) {
+ randomizer := rand.New(rand.NewSource(time.Now().Unix()))
+
+ for i := 0; true; i++ {
+ ch <- i
+ time.Sleep(time.Duration(randomizer.Int()%10+1) * time.Second)
+ }
+}
+```
+
+Selanjutnya, disiapkan perulangan tanpa henti, yang di setiap perulangan ada seleksi kondisi channel menggunakan `select`.
+
+```go
+func retreiveData(ch <-chan int) {
+ loop:
+ for {
+ select {
+ case data := <-ch:
+ fmt.Print(`receive data "`, data, `"`, "\n")
+ case <-time.After(time.Second * 5):
+ fmt.Println("timeout. no activities under 5 seconds")
+ break loop
+ }
+ }
+}
+```
+
+Ada 2 blok kondisi pada `select` tersebut.
+
+ - Kondisi `case data := <-messages:`, akan terpenuhi ketika ada serah terima data pada channel `messages`.
+ - Kondisi `case <-time.After(time.Second * 5):`, akan terpenuhi ketika tidak ada aktivitas penerimaan data dari channel dalam durasi 5 detik. Blok inilah yang kita sebut sebagai blok timeout.
+
+Terakhir, kedua fungsi tersebut dipanggil di `main()`.
+
+```go
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var messages = make(chan int)
+
+ go sendData(messages)
+ retreiveData(messages)
+}
+```
+
+Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika tidak ada aktivitas penerimaan dari channel dalam durasi 5 detik, perulangan pengecekkan channel diberhentikan.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 82cdea958bf2148249a476dcf4752e021540509b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:10 +0700
Subject: [PATCH 008/522] New translations a-channel.md (English)
---
en/content-en/A-channel.md | 168 +++++++++++++++++++++++++++++++++++++
1 file changed, 168 insertions(+)
create mode 100644 en/content-en/A-channel.md
diff --git a/en/content-en/A-channel.md b/en/content-en/A-channel.md
new file mode 100644
index 000000000..d63fd9c2b
--- /dev/null
+++ b/en/content-en/A-channel.md
@@ -0,0 +1,168 @@
+# A.31. Channel
+
+**Channel** digunakan untuk menghubungkan goroutine satu dengan goroutine lain. Mekanisme yang dilakukan adalah serah-terima data lewat channel tersebut. Dalam komunikasinya, sebuah channel difungsikan sebagai pengirim di sebuah goroutine, dan juga sebagai penerima di goroutine lainnya. Pengiriman dan penerimaan data pada channel bersifat **blocking** atau **synchronous**.
+
+
+
+Pada chapter ini kita akan belajar mengenai pemanfaatan channel.
+
+## A.31.1. Penerapan Channel
+
+Channel merupakan sebuah variabel, dibuat dengan menggunakan kombinasi keyword `make` dan `chan`. Variabel channel memiliki satu tugas, menjadi pengirim, atau penerima data.
+
+Program berikut adalah contoh implementasi channel. 3 buah goroutine dieksekusi, di masing-masing goroutine terdapat proses pengiriman data lewat channel. Data tersebut akan diterima 3 kali di goroutine utama `main`.
+
+```go
+package main
+
+import "fmt"
+import "runtime"
+
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var messages = make(chan string)
+
+ var sayHelloTo = func(who string) {
+ var data = fmt.Sprintf("hello %s", who)
+ messages <- data
+ }
+
+ go sayHelloTo("john wick")
+ go sayHelloTo("ethan hunt")
+ go sayHelloTo("jason bourne")
+
+ var message1 = <-messages
+ fmt.Println(message1)
+
+ var message2 = <-messages
+ fmt.Println(message2)
+
+ var message3 = <-messages
+ fmt.Println(message3)
+}
+```
+
+Pada kode di atas, variabel `messages` dideklarasikan bertipe channel `string`. Cara pembuatan channel yaitu dengan menuliskan keyword `make` dengan isi keyword `chan` diikuti dengan tipe data channel yang diinginkan.
+
+```go
+var messages = make(chan string)
+```
+
+Selain itu disiapkan juga closure `sayHelloTo` yang tugasnya membuat sebuah pesan string yang kemudian dikirim via channel. Pesan string tersebut dikirim lewat channel `messages`. Tanda `<-` jika dituliskan di sebelah kiri nama variabel, berarti sedang berlangsung proses pengiriman data dari variabel yang berada di kanan lewat channel yang berada di kiri (pada konteks ini, variabel `data` dikirim lewat channel `messages`).
+
+```go
+var sayHelloTo = func(who string) {
+ var data = fmt.Sprintf("hello %s", who)
+ messages <- data
+}
+```
+
+Fungsi `sayHelloTo` dieksekusi tiga kali sebagai goroutine berbeda. Menjadikan tiga proses ini berjalan secara **asynchronous** atau tidak saling tunggu.
+
+> Sekali lagi perlu diingat bahwa eksekusi goroutine adalah *asynchronous*, sedangkan serah-terima data antar channel adalah *synchronous*.
+
+```go
+go sayHelloTo("john wick")
+go sayHelloTo("ethan hunt")
+go sayHelloTo("jason bourne")
+```
+
+Dari ketiga fungsi tersebut, goroutine yang selesai paling awal akan mengirim data lebih dulu, datanya kemudian diterima variabel `message1`. Tanda `<-` jika dituliskan di sebelah kiri channel, menandakan proses penerimaan data dari channel yang di kanan, untuk disimpan ke variabel yang di kiri.
+
+```go
+var message1 = <-messages
+fmt.Println(message1)
+```
+
+Penerimaan channel bersifat blocking. Artinya statement `var message1 = <-messages` hingga setelahnya tidak akan dieksekusi sebelum ada data yang dikirim lewat channel.
+
+Ke semua data yang dikirim dari tiga goroutine berbeda tersebut datanya akan diterima secara berurutan oleh `message1`, `message2`, `message3`; untuk kemudian ditampilkan.
+
+
+
+Dari screenshot output di atas bisa dilihat bahwa text yang dikembalikan oleh `sayHelloTo` tidak selalu berurutan, meskipun penerimaan datanya adalah berurutan. Hal ini dikarenakan, pengiriman data adalah dari 3 goroutine yang berbeda, yang kita tidak tau mana yang prosesnya selesai lebih dulu. Goroutine yang dieksekusi lebih awal belum tentu selesai lebih awal, yang jelas proses yang selesai lebih awal datanya akan diterima lebih awal.
+
+Karena pengiriman dan penerimaan data lewat channel bersifat **blocking**, tidak perlu memanfaatkan sifat blocking dari fungsi sejenis `fmt.Scanln()` atau lainnya, untuk mengantisipasi goroutine utama `main` selesai sebelum ketiga goroutine di atas selesai.
+
+## A.31.2. Channel Sebagai Tipe Data Parameter
+
+Variabel channel bisa di-pass ke fungsi lain sebagai parameter. Cukup tambahkan keyword `chan` pada deklarasi parameter agar operasi pass channel variabel bisa dilakukan.
+
+Siapkan fungsi `printMessage` dengan parameter adalah channel. Lalu ambil data yang dikirimkan lewat channel tersebut untuk ditampilkan.
+
+```go
+func printMessage(what chan string) {
+ fmt.Println(<-what)
+}
+```
+
+Setelah itu ubah implementasi di fungsi `main`.
+
+```go
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var messages = make(chan string)
+
+ for _, each := range []string{"wick", "hunt", "bourne"} {
+ go func(who string) {
+ var data = fmt.Sprintf("hello %s", who)
+ messages <- data
+ }(each)
+ }
+
+ for i := 0; i < 3; i++ {
+ printMessage(messages)
+ }
+}
+```
+
+Output program di atas sama dengan program sebelumnya.
+
+Parameter `what` fungsi `printMessage` bertipe channel `string`, bisa dilihat dari kode `chan string` pada cara deklarasinya. Operasi serah-terima data akan bisa dilakukan pada variabel tersebut, dan akan berdampak juga pada variabel `messages` di fungsi `main`.
+
+Passing data bertipe channel lewat parameter sifatnya **pass by reference**, yang di transferkan adalah pointer datanya, bukan nilai datanya.
+
+
+
+---
+
+Berikut merupakan penjelasan tambahan untuk kode di atas.
+
+#### • Iterasi Data Slice/Array Langsung Pada Saat Inisialisasi
+
+Data slice yang baru di inisialisasi bisa langsung di-iterasi, caranya mudah dengan menuliskannya langsung setelah keyword `range`.
+
+```go
+for _, each := range []string{"wick", "hunt", "bourne"} {
+ // ...
+}
+```
+
+#### • Eksekusi Goroutine Pada IIFE
+
+Eksekusi goroutine tidak harus pada fungsi atau closure yang sudah terdefinisi. Sebuah IIFE juga bisa dijalankan sebagai goroutine baru. Caranya dengan langsung menambahkan keyword `go` pada waktu deklarasi-eksekusi IIFE-nya.
+
+```go
+var messages = make(chan string)
+
+go func(who string) {
+ var data = fmt.Sprintf("hello %s", who)
+ messages <- data
+}("wick")
+
+var message = <-messages
+fmt.Println(message)
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From dff2cae3d86a6a67c546e231be8d92f5a11db2c4 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:11 +0700
Subject: [PATCH 009/522] New translations a-client-http-request-simple.md
(English)
---
en/content-en/A-client-http-request-simple.md | 182 ++++++++++++++++++
1 file changed, 182 insertions(+)
create mode 100644 en/content-en/A-client-http-request-simple.md
diff --git a/en/content-en/A-client-http-request-simple.md b/en/content-en/A-client-http-request-simple.md
new file mode 100644
index 000000000..f2c86f211
--- /dev/null
+++ b/en/content-en/A-client-http-request-simple.md
@@ -0,0 +1,182 @@
+# A.55. Simple Client HTTP Request
+
+Pada chapter sebelumnya telah dibahas bagaimana membuat Web Service API yang mem-provide data JSON, pada chapter ini kita akan belajar mengenai cara untuk mengkonsumsi data tersebut.
+
+Pastikan anda sudah mempraktekkan apa-apa yang ada pada chapter sebelumnya ([A.54. Web Service API Server](/A-web-service-api.html)), karena web service yang telah dibuat di situ juga dipergunakan pada chapter ini.
+
+
+
+## A.55.1. Penggunaan HTTP Request
+
+Package `net/http`, selain berisikan tools untuk keperluan pembuatan web, juga berisikan fungsi-fungsi untuk melakukan http request. Salah satunya adalah `http.NewRequest()` yang akan kita bahas di sini.
+
+Sebelumnya, import package yang dibutuhkan. Dan siapkan struct `student` yang nantinya akan dipakai sebagai tipe data reponse dari web API. Struk tersebut skema nya sama dengan yang ada pada chapter ([A.54. Web Service API Server](/A-web-service-api.html)).
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+import "encoding/json"
+
+var baseURL = "http://localhost:8080"
+
+type student struct {
+ ID string
+ Name string
+ Grade int
+}
+```
+
+Setelah itu buat fungsi `fetchUsers()`. Fungsi ini bertugas melakukan request ke [http://localhost:8080/users](http://localhost:8080/users), menerima response dari request tersebut, lalu menampilkannya.
+
+```go
+func fetchUsers() ([]student, error) {
+ var err error
+ var client = &http.Client{}
+ var data []student
+
+ request, err := http.NewRequest("GET", baseURL+"/users", nil)
+ if err != nil {
+ return nil, err
+ }
+
+ response, err := client.Do(request)
+ if err != nil {
+ return nil, err
+ }
+ defer response.Body.Close()
+
+ err = json.NewDecoder(response.Body).Decode(&data)
+ if err != nil {
+ return nil, err
+ }
+
+ return data,nil
+}
+```
+
+Statement `&http.Client{}` menghasilkan instance `http.Client`. Objek ini nantinya diperlukan untuk eksekusi request.
+
+Fungsi `http.NewRequest()` digunakan untuk membuat request baru. Fungsi tersebut memiliki 3 parameter yang wajib diisi.
+
+ 1. Parameter pertama, berisikan tipe request **POST** atau **GET** atau lainnya
+ 2. Parameter kedua, adalah URL tujuan request
+ 3. Parameter ketiga, form data request (jika ada)
+
+Fungsi tersebut menghasilkan instance bertipe `http.Request`. Objek tersebut nantinya disisipkan pada saat eksekusi request.
+
+Cara eksekusi request sendiri adalah dengan memanggil method `Do()` pada instance `http.Client` yang sudah dibuat, dengan parameter adalah instance request-nya. Contohnya seperti pada `client.Do(request)`.
+
+Method tersebut mengembalikan instance bertipe `http.Response`, yang di dalamnya berisikan informasi yang dikembalikan dari web API.
+
+Data response bisa diambil lewat property `Body` dalam bentuk string. Gunakan JSON Decoder untuk mengkonversinya menjadi bentuk JSON. Contohnya bisa dilihat di kode di atas, `json.NewDecoder(response.Body).Decode(&data)`. Setelah itu barulah kita bisa menampilkannya.
+
+Perlu diketahui, data response perlu di-**close** setelah tidak dipakai. Caranya seperti pada kode `defer response.Body.Close()`.
+
+Selanjutnya, eksekusi fungsi `fetchUsers()` dalam fungsi `main()`.
+
+```go
+func main() {
+ var users, err = fetchUsers()
+ if err != nil {
+ fmt.Println("Error!", err.Error())
+ return
+ }
+
+ for _, each := range users {
+ fmt.Printf("ID: %s\t Name: %s\t Grade: %d\n", each.ID, each.Name, each.Grade)
+ }
+}
+```
+
+Ok, terakhir sebelum memulai testing, pastikan telah run aplikasi pada chapter sebelumya ([A.54. Web Service API Server](/A-web-service-api.html)). Kemudian start prompt cmd/terminal baru dan jalankan program yang barusan kita buat pada chapter ini.
+
+
+
+## A.55.2. HTTP Request Dengan Form Data
+
+Untuk menyisipkan data pada sebuah request, ada beberapa hal yang perlu ditambahkan. Yang pertama, import beberapa package lagi, `bytes` dan `net/url`.
+
+```go
+import "bytes"
+import "net/url"
+```
+
+Buat fungsi baru, isinya request ke [http://localhost:8080/user](http://localhost:8080/user) dengan data yang disisipkan adalah `ID`.
+
+```go
+func fetchUser(ID string) (student, error) {
+ var err error
+ var client = &http.Client{}
+ var data student
+
+ var param = url.Values{}
+ param.Set("id", ID)
+ var payload = bytes.NewBufferString(param.Encode())
+
+ request, err := http.NewRequest("POST", baseURL+"/user", payload)
+ if err != nil {
+ return data, err
+ }
+ request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ response, err := client.Do(request)
+ if err != nil {
+ return data, err
+ }
+ defer response.Body.Close()
+
+ err = json.NewDecoder(response.Body).Decode(&data)
+ if err != nil {
+ return data, err
+ }
+
+ return data, nil
+}
+```
+
+Isi fungsi di atas bisa dilihat memiliki beberapa kemiripan dengan fungsi `fetchUsers()` sebelumnya.
+
+Statement `url.Values{}` akan menghasilkan objek yang nantinya digunakan sebagai form data request. Pada objek tersebut perlu di set data apa saja yang ingin dikirimkan menggunakan fungsi `Set()` seperti pada `param.Set("id", ID)`.
+
+Statement `bytes.NewBufferString(param.Encode())` maksudnya, objek form data di-encode lalu diubah menjadi bentuk `bytes.Buffer`, yang nantinya disisipkan pada parameter ketiga pemanggilan fungsi `http.NewRequest()`.
+
+Karena data yang akan dikirim di-encode, maka pada header perlu di set tipe konten request-nya. Kode `request.Header.Set("Content-Type", "application/x-www-form-urlencoded")` artinya tipe konten request di set sebagai `application/x-www-form-urlencoded`.
+
+> Pada konteks HTML, HTTP Request yang di trigger dari tag `` secara default tipe konten-nya sudah di set `application/x-www-form-urlencoded`. Lebih detailnya bisa merujuk ke spesifikasi HTML form [http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1)
+
+Response dari endpoint `/user` bukanlah slice, tetapi berupa objek. Maka pada saat decode perlu pastikan tipe variabel penampung hasil decode data response adalah `student` (bukan `[]student`).
+
+Lanjut ke perkodingan, terakhir, implementasikan `fetchUser()` pada fungsi `main()`.
+
+```go
+func main() {
+ var user1, err = fetchUser("E001")
+ if err != nil {
+ fmt.Println("Error!", err.Error())
+ return
+ }
+
+ fmt.Printf("ID: %s\t Name: %s\t Grade: %d\n", user1.ID, user1.Name, user1.Grade)
+}
+```
+
+Untuk keperluan testing, kita hardcode `ID` nilainya `"E001"`. Jalankan program untuk test apakah data yang dikembalikan sesuai.
+
+
+
+## A.55.3. Secure & Insecure HTTP Request
+
+Kita telah mempelajari bagaimana cara membuat http request sederhana untuk kirim data dan juga ambil data. Nantinya pada chapter [C.27. Secure & Insecure Client HTTP Request](/C-secure-insecure-client-http-request.html) kita akan belajar cara membuat http client request yang lebih *njlimet* untuk kasus yang lebih advance, tapi sabar dulu hehe.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From ec609a355b70b2e9bfa7a5ad3a46b26f5fe97259 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:12 +0700
Subject: [PATCH 010/522] New translations a-command-line-args-flag.md
(English)
---
en/content-en/A-command-line-args-flag.md | 141 ++++++++++++++++++++++
1 file changed, 141 insertions(+)
create mode 100644 en/content-en/A-command-line-args-flag.md
diff --git a/en/content-en/A-command-line-args-flag.md b/en/content-en/A-command-line-args-flag.md
new file mode 100644
index 000000000..50a18528b
--- /dev/null
+++ b/en/content-en/A-command-line-args-flag.md
@@ -0,0 +1,141 @@
+# A.48. Arguments & Flag
+
+**Arguments** adalah data opsional yang disisipkan ketika eksekusi program. Sedangkan **flag** merupakan ekstensi dari argument. Dengan flag, penulisan argument menjadi lebih rapi dan terstruktur.
+
+Pada chapter ini kita akan belajar tentang penggunaan arguments dan flag.
+
+## A.48.1. Penggunaan Arguments
+
+Data arguments bisa didapat lewat variabel `os.Args` (package `os` perlu di-import terlebih dahulu). Data tersebut tersimpan dalam bentuk array dengan pemisah adalah tanda spasi.
+
+Berikut merupakan contoh penggunaannya.
+
+```go
+package main
+
+import "fmt"
+import "os"
+
+func main() {
+ var argsRaw = os.Args
+ fmt.Printf("-> %#v\n", argsRaw)
+ // -> []string{".../bab45", "banana", "potato", "ice cream"}
+
+ var args = argsRaw[1:]
+ fmt.Printf("-> %#v\n", args)
+ // -> []string{"banana", "potatao", "ice cream"}
+}
+```
+
+Pada saat eksekusi program disisipkan juga argument-nya. Sebagai contoh disisipkan 3 buah data sebagai argumen, yaitu: `banana`, `potato`, dan `ice cream`.
+
+Untuk eksekusinya sendiri bisa menggunakan `go run` ataupun dengan cara build-execute.
+
+ - Menggunakan `go run`
+
+ ```
+ go run bab45.go banana potato "ice cream"
+ ```
+
+ - Menggunakan `go build`
+
+ ```
+ go build bab45.go
+ $ ./bab45 banana potato "ice cream"
+ ```
+
+Variabel `os.Args` mengembalikan tak hanya arguments saja, tapi juga path file executable (jika eksekusi-nya menggunakan `go run` maka path akan merujuk ke folder temporary). Gunakan `os.Args[1:]` untuk mengambil slice arguments-nya saja.
+
+
+
+Bisa dilihat pada kode di atas, bahwa untuk data argumen yang ada karakter spasi nya (), maka harus diapit tanda petik (`"`), agar tidak dideteksi sebagai 2 argumen.
+
+## A.48.2. Penggunaan Flag
+
+Flag memiliki kegunaan yang sama seperti arguments, yaitu untuk *parameterize* eksekusi program, dengan penulisan dalam bentuk key-value. Berikut merupakan contoh penerapannya.
+
+```go
+package main
+
+import "flag"
+import "fmt"
+
+func main() {
+ var name = flag.String("name", "anonymous", "type your name")
+ var age = flag.Int64("age", 25, "type your age")
+
+ flag.Parse()
+ fmt.Printf("name\t: %s\n", *name)
+ fmt.Printf("age\t: %d\n", *age)
+}
+```
+
+Cara penulisan arguments menggunakan flag:
+
+```
+go run bab45.go -name="john wick" -age=28
+```
+
+Tiap argument harus ditentukan key, tipe data, dan nilai default-nya. Contohnya seperti pada `flag.String()` di atas. Agar lebih mudah dipahami, mari kita bahas kode berikut.
+
+```go
+var dataName = flag.String("name", "anonymous", "type your name")
+fmt.Println(*dataName)
+```
+
+Kode tersebut maksudnya adalah, disiapkan flag bertipe `string`, dengan key adalah `name`, dengan nilai default `"anonymous"`, dan keterangan `"type your name"`. Nilai flag nya sendiri akan disimpan ke dalam variabel `dataName`.
+
+Nilai balik fungsi `flag.String()` adalah string pointer, jadi perlu di-*dereference* terlebih dahulu agar bisa mendapatkan nilai aslinya (`*dataName`).
+
+
+
+Flag yang nilainya tidak di set, secara otomatis akan mengembalikan nilai default.
+
+Tabel berikut merupakan macam-macam fungsi flag yang tersedia untuk tiap jenis tipe data.
+
+| Nama Fungsi | Return Value |
+|:------------------------------------------ |:---------------- |
+| `flag.Bool(name, defaultValue, usage)` | `*bool` |
+| `flag.Duration(name, defaultValue, usage)` | `*time.Duration` |
+| `flag.Float64(name, defaultValue, usage)` | `*float64` |
+| `flag.Int(name, defaultValue, usage)` | `*int` |
+| `flag.Int64(name, defaultValue, usage)` | `*int64` |
+| `flag.String(name, defaultValue, usage)` | `*string` |
+| `flag.Uint(name, defaultValue, usage)` | `*uint` |
+| `flag.Uint64(name, defaultValue, usage)` | `*uint64` |
+
+## A.48.3. Deklarasi Flag Dengan Cara Passing Reference Variabel Penampung Data
+
+Sebenarnya ada 2 cara deklarasi flag yang bisa digunakan, dan cara di atas merupakan cara pertama.
+
+Cara kedua mirip dengan cara pertama, perbedannya adalah kalau di cara pertama nilai pointer flag dikembalikan lalu ditampung variabel. Sedangkan pada cara kedua, nilainya diambil lewat parameter pointer.
+
+Agar lebih jelas perhatikan contoh berikut.
+
+```go
+// cara ke-1
+var data1 = flag.String("name", "anonymous", "type your name")
+fmt.Println(*data1)
+
+// cara ke-2
+var data2 string
+flag.StringVar(&data2, "gender", "male", "type your gender")
+fmt.Println(data2)
+```
+
+Tinggal tambahkan akhiran `Var` pada pemanggilan nama fungsi flag yang digunakan (contoh `flag.IntVar()`, `flag.BoolVar()`, dll), lalu disisipkan referensi variabel penampung flag sebagai parameter pertama.
+
+Kegunaan dari parameter terakhir method-method flag adalah untuk memunculkan hints atau petunjuk arguments apa saja yang bisa dipakai, ketika argument `--help` ditambahkan saat eksekusi program.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 4eb34e740fc3f369e1f843b217d368f2ebabe533 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:12 +0700
Subject: [PATCH 011/522] New translations a-concurrency-pipeline.md (English)
---
en/content-en/A-concurrency-pipeline.md | 549 ++++++++++++++++++++++++
1 file changed, 549 insertions(+)
create mode 100644 en/content-en/A-concurrency-pipeline.md
diff --git a/en/content-en/A-concurrency-pipeline.md b/en/content-en/A-concurrency-pipeline.md
new file mode 100644
index 000000000..1f9805f43
--- /dev/null
+++ b/en/content-en/A-concurrency-pipeline.md
@@ -0,0 +1,549 @@
+# A.62. Concurrency Pattern: Pipeline
+
+Kita sudah membahas beberapa kali tentang *concurrency* atau konkurensi di Go programming. Pada chapter ini kita akan belajar salah satu best practice konkurensi dalam Go, yaitu *pipeline*, yang merupakan satu di antara banyak *concurrency pattern* yang ada di Go.
+
+Go memiliki beberapa API untuk keperluan konkurensi, di antara *goroutine* dan *channel*. Dengan memanfaatkan APIs yang ada kita bisa membentuk sebuah *streaming data pipeline* yang benefitnya adalah efisiensi penggunaan I/O dan efisiensi penggunaan banyak CPU.
+
+## A.62.1. Konsep *Pipeline*
+
+Definisi *pipeline* yang paling mudah versi penulis adalah **beberapa/banyak proses yang berjalan secara konkuren yang masing-masing proses merupakan bagian dari serangkaian tahapan proses yang berhubungan satu sama lain**.
+
+Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang di mana database yang di backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut:
+
+1. Kita perlu data *list* dari semua database yang harus di-backup, beserta alamat akses dan kredensial-nya.
+2. Kita jalankan proses backup, bisa secara sekuensial (setelah `db1` selesai, lanjut `db2`, lanjut `db3`, dst), atau secara paralel (proses backup `db1`, `db2`, `db3`, dan lainnya dijalankan secara bersamaan).
+3. Di masing-masing proses backup database sendiri ada beberapa proses yang dijalankan:
+
+ * A. Lakukan operasi *dump* terhadap database, outputnya berupa banyak file disimpan ke sebuah folder.
+ * B. File-file hasil dump kemudian di-*archive* ke bentuk `.zip` atau `.tar.gz` (misalnya).
+ * C. File archive di-kirim ke server backup, sebagai contoh AWS S3.
+
+Kalau diperhatikan pada kasus di atas, mungkin akan lebih bagus dari segi performansi kalau proses backup banyak database tersebut dilakukan secara parallel. Dan untuk ini penulis setuju.
+
+Dan akan lebih bagus lagi, jika di masing-masing proses backup database tersebut, proses A, B, dan C dijalankan secara konkuren. Dengan menjadikan ketiga proses tersebut (A, B, C) sebagai proses konkuren, maka I/O akan lebih efisien. Nantinya antara proses A, B, dan C eksekusinya akan tetap berurutan (karena memang harus berjalan secara urut. Tidak boleh kalau misal B lebih dulu dieksekusi kemudian A); akan tetapi, ketika goroutine yang bertanggung jawab untuk eksekusi proses A selesai, kita bisa lanjut dengan eksekusi proses B (yang memang *next stage*-nya proses A) plus eksekusi proses A lainnya (database lain) secara paralel. Jadi goroutine yang handle A ini ga sampai menganggur.
+
+Silakan perhatikan visualisasi berikut. Kolom merupakan representasi dari goroutine yang berjalan secara bersamaan. Tapi karena ketiga goroutine tersebut merupakan serangkaian proses, sehingga eksekusinya harus secara berurut. Sedangkan baris/row representasi dari *sequence* atau urutan.
+
+| sequence | pipeline A | pipeline B | pipeline C |
+|:--------:|:----------:|:----------:|:----------:|
+| 1 | db1 | - | - |
+| 2 | db2 | db1 | - |
+| 3 | db3 | db1 | - |
+| 4 | db4 | db2 | db1 |
+| 5 | db5 | db3 | db2 |
+| 6 | db5 | db4 | db3 |
+| 7 | db6 | db5 | db4 |
+| ... | ... | ... | ... |
+
+Di Go, umumnya proses yang berupa goroutine yang eksekusinya adalah concurrent tapi secara flow adalah harus berurutan, itu disebut dengan **pipeline**. Jadi untuk sementara anggap saja pipeline A sebuah goroutine untuk proses A, pipeline B adalah goroutine proses B, dst.
+
+Untuk mempermudah memahami tabel di atas silakan ikuti penjelasan beruntun berikut:
+
+1. Sequence 1: pipeline A akan melakukan proses dump dari dari `db1`. Pada waktu yang sama, pipeline B dan C menganggur.
+2. Sequence 2: proses dump `db1` telah selesai, maka lanjut ke *next stage* yaitu proses archive data dump `db1` yang dilakukan oleh pipeline B. Dan pada waktu yang sama juga, pipeline A menjalankan proses dump `db2`. Pipeline C masih menganggur.
+3. Sequence 3: pipeline A menjalankan proses dump `db3`. Pada waktu yang sama pipeline B belum menjalankan proses archiving `db2` yang sudah di-dump karena archiving `db1` masih belum selesai. Pipeline C masih menganggur.
+4. Sequence 4: proses archiving `db1` sudah selesai, maka lanjut ke *next stage* yaitu kirim archive ke server backup yang prosesnya di-handle oleh pipeline C. Pada saat yang sama, pipeline B mulai menjalankan archiving data dump `db2` dan pipeline A dumping `db4`.
+5. ... dan seterusnya.
+
+Pada contoh ini kita asumsikan pipeline A adalah hanya satu goroutine, pipeline B juga satu goroutine, demikian juga pipeline C. Tapi sebenarnya dalam implementasi *real world* bisa saja ada banyak goroutine untuk masing-masing pipeline (banyak goroutine untuk pipeline A, banyak goroutine untuk pipeline B, banyak goroutine untuk pipeline C).
+
+Semoga cukup jelas ya. Gpp kalau bingung, nanti kita sambil praktek juga jadi bisa saja temen-temen mulai benar-benar pahamnya setelah praktek.
+
+## A.62.2. Skenario Praktek
+
+Ok, penjabaran teori sepanjang sungai `nil` tidak akan banyak membawa penjelasan yang real kalau tidak diiringi dengan praktek. So, mari kita mulai praktek.
+
+Penulis sarankan untuk benar-benar memahami setiap bagian praktek ini, karena topik ini merupakan pembahasan yang cukup berat untuk pemula, tapi masih dalam klasifikasi fundamental kalau di Go programming. Bingung tidak apa, nanti bisa di-ulang-ulang, yang penting tidak sekadar *copy-paste*.
+
+Untuk skenario praktek kita tidak menggunakan analogi backup database di atas ya, karena untuk setup environment-nya butuh banyak *effort*. Skenario praktek yang kita pakai adalah mencari [md5 sum](https://en.wikipedia.org/wiki/Md5sum) dari banyak file, kemudian menggunakan hash dari content-nya sebagai nama file. Jadi file yang lama akan di-rename dengan nama baru yaitu hash dari konten file tersebut.
+
+Agar skenario ini bisa kita eksekusi, kita perlu siapkan dulu sebuah program untuk *generate dummy files*.
+
+## A.62.3. Program 1: Generate Dummy File
+
+Buat project baru dengan nama bebas loss gak reweellll beserta satu buah file bernama `1-dummy-file-generator.go`.
+
+Dalam file tersebut import beberapa hal dan definisikan, yaitu:
+
+1. Konstanta `totalFile` yang isinya jumlah file yang ingin di-generate.
+1. Variabel `contentLength` yang isinya panjang karakter random yang merupakan isi dari masing-masing *generated* file.
+1. Variabel `tempPath` yang mengarah ke [temporary folder](https://en.wikipedia.org/wiki/Temporary_folder).
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+const totalFile = 3000
+const contentLength = 5000
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.59-pipeline-temp")
+```
+
+Kemudian siapkan fungsi `main()` yang isinya statement pemanggilan fungsi `generate()`. Selain itu juga ada beberapa statement untuk keperluan *benchmark* performa dari sisi *execution time*.
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ generateFiles()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+Sekarang siapkan fungsi `randomString()`-nya:
+
+```go
+func randomString(length int) string {
+ randomizer := rand.New(rand.NewSource(time.Now().Unix()))
+ letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = letters[randomizer.Intn(len(letters))]
+ }
+
+ return string(b)
+}
+```
+
+Lalu siapkan fungsi `generateFiles()`-nya. isinya kurang lebih adalah generate banyak file sejumlah `totalFile`. Lalu di tiap-tiap file di-isi dengan *random string* dengan lebar sepanjang `contentLength`. Untuk nama file-nya sendiri, formatnya adalah `file-.txt`.
+
+```go
+func generateFiles() {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ for i := 0; i < totalFile; i++ {
+ filename := filepath.Join(tempPath, fmt.Sprintf("file-%d.txt", i))
+ content := randomString(contentLength)
+ err := ioutil.WriteFile(filename, []byte(content), os.ModePerm)
+ if err != nil {
+ log.Println("Error writing file", filename)
+ }
+
+ if i%100 == 0 && i > 0 {
+ log.Println(i, "files created")
+ }
+ }
+
+ log.Printf("%d of total files created", totalFile)
+}
+```
+
+O iya untuk logging pembuatan file saya tampilkan setiap 100 file di-generate, agar tidak mengganggu performa, karena printing output ke `stdout` atau CMD/terminal itu cukup *costly*.
+
+Oke, generator sudah siap, jalankan.
+
+
+
+Bisa dilihat sebanyak 3000 dummy file di-generate pada folder temporary os, di sub folder `chapter-A.59-pipeline-temp`.
+
+## A.62.4. Program 2: Baca Semua Files, Cari MD5 Hash-nya, Lalu Gunakan Hash Untuk Rename File
+
+Sesuai judul sub bagian, kita akan buat satu file program lagi, yang isinya kurang lebih adalah melakukan pembacaan terhadap semua dummy file yang sudah di-generate untuk kemudian dicari *hash*-nya, lalu menggunakan value hash tersebut sebagai nama file baru masing-masing file.
+
+Pada bagian ini kita belum masuk ke aspek konkurensi-nya ya. Sabar dulu. Saya akan coba sampaikan dengan penjabaran yang bisa diterima oleh banyak pembaca termasuk yang masih junior.
+
+Siapkan file `2-find-md5-sum-of-file-then-rename-it.go`, import beberapa *packages* dan siapkan definisi variabel `tempPath`.
+
+```go
+package main
+
+import (
+ "crypto/md5"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.59-pipeline-temp")
+```
+
+Lanjut siapkan fungsi `main()` dengan isi memanggil fungsi `proceed()`.
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ proceed()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+Isi dari fungsi `proceed()` sendiri adalah bisnis logic dari aplikasi yang akan kita buat, yaitu membaca file, mencari md5 hash, lalu rename file.
+
+```go
+func proceed() {
+ counterTotal := 0
+ counterRenamed := 0
+ err := filepath.Walk(tempPath, func(path string, info os.FileInfo, err error) error {
+
+ // if there is an error, return immediatelly
+ if err != nil {
+ return err
+ }
+
+ // if it is a sub directory, return immediatelly
+ if info.IsDir() {
+ return nil
+ }
+
+ counterTotal++
+
+ // read file
+ buf, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ // sum it
+ sum := fmt.Sprintf("%x", md5.Sum(buf))
+
+ // rename file
+ destinationPath := filepath.Join(tempPath, fmt.Sprintf("file-%s.txt", sum))
+ err = os.Rename(path, destinationPath)
+ if err != nil {
+ return err
+ }
+
+ counterRenamed++
+ return nil
+ })
+ if err != nil {
+ log.Println("ERROR:", err.Error())
+ }
+
+ log.Printf("%d/%d files renamed", counterRenamed, counterTotal)
+}
+```
+
+Cukup panjang isi fungsi ini, tetapi isinya cukup *straightforward* kok.
+
+* Pertama kita siapkan `counterTotal` sebagai counter jumlah file yang ditemukan dalam `$TEMP/chapter-A.59-pipeline-temp`. Idealnya jumlahnya adalah sama dengan isi variabel `totalFile` pada program pertama, kecuali ada error.
+* Kedua, kita siapkan `counterRenamed` sebagai counter jumlah file yang berhasil di-rename. Untuk ini juga idealnya sama dengan nilai pada `counterTotal`, kecuali ada error
+* Kita gunakan `filepath.Walk` untuk melakukan pembacaan semua file yang ada dalam folder `$TEMP/chapter-A.59-pipeline-temp`.
+* File akan dibaca secara sekuensial, di tiap pembacaan jika ada error dan ditemukan sebuah direktori, maka kita ignore kemudian lanjut pembacaan file selanjutnya.
+* File dibaca menggunakan `ioutil.ReadFile()`, kemudian lewat fungsi `md5.Sum()` kita cari md5 hash sum dari konten file.
+* Setelahnya, kita rename file dengan nama `file-.txt`.
+
+Semoga cukup jelas. Kalo iya, jalankan programnya.
+
+
+
+Selesai dalam waktu **1,17 detik**, lumayan untuk eksekusi proses sekuensial.
+
+Ok, aplikasi sudah siap. Selanjutnya kita akan refactor aplikasi tersebut ke bentuk konkuren menggunakan metode *pipeline*.
+
+## A.62.5. Program 3: Lakukan Proses Secara Concurrent Menggunakan Pipeline
+
+Pada bagian ini kita akan re-write ulang program 2, isinya masih sama persis kalau dilihat dari perspektif bisnis logic, tapi metode yang kita terapkan dari sisi engineering berbeda. Di sini kita akan terapkan *pipeline*. Bisnis logic akan dipecah menjadi 3 dan seluruhnya dieksekusi secara konkuren, yaitu:
+
+* Proses baca file
+* Proses perhitungan md5 hash sum
+* Proses rename file
+
+Kenapa kita pecah, karena ketiga proses tersebut bisa dijalankan bersama secara konkuren, dalam artian misalnya ketika file1 sudah selesai dibaca, perhitungan md5 sum nya bisa dijalankan secara bersama dengan pembacaan file2. Begitu juga untuk proses rename-nya, misalnya, proses rename file24 bisa dijalnkan secara konkuren bersamaan dengan proses hitung md5 sum file22 dan bersamaan dengan proses baca file28.
+
+#### • Basis Kode Program
+
+Mungkin agar lebih terlihat perbandingannya nanti di akhir, kita siapkan file terpisah saja untuk program ini. Siapkan file baru bernama `3-find-md5-sum-of-file-then-rename-it-concurrently.go`.
+
+Isi file tersebut dengan kode berikut.
+
+```go
+package main
+
+import (
+ "crypto/md5"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.59-pipeline-temp")
+
+type FileInfo struct {
+ FilePath string // file location
+ Content []byte // file content
+ Sum string // md5 sum of content
+ IsRenamed bool // indicator whether the particular file is renamed already or not
+}
+```
+
+Kurang lebih sama seperti sebelumnya, hanya saja ada beberapa packages lain yg di-import dan ada struct `FileInfo`. Struct ini digunakan sebagai metadata tiap file. Karena nantinya proses read file, md5sum, dan rename file akan dipecah menjadi 3 goroutine berbeda, maka perlu ada metadata untuk mempermudah tracking file, agar nanti ketika dapat md5 sum nya tidak salah simpan, dan ketika rename tidak salah file.
+
+#### • Pipeline 1: Baca File
+
+Siapkan fungsi main, lalu panggil fungsi `readFiles()`.
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ // pipeline 1: loop all files and read it
+ chanFileContent := readFiles()
+
+ // ...
+}
+```
+
+Fungsi `readFiles()` isinya adalah pembacaan semua file. Fungsi ini mengembalikan variabel `channel` bernama `chanFileContent`. Lanjut siapkan fungsi tersebut.
+
+```go
+func readFiles() <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ err := filepath.Walk(tempPath, func(path string, info os.FileInfo, err error) error {
+
+ // if there is an error, return immediatelly
+ if err != nil {
+ return err
+ }
+
+ // if it is a sub directory, return immediatelly
+ if info.IsDir() {
+ return nil
+ }
+
+ buf, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ chanOut <- FileInfo{
+ FilePath: path,
+ Content: buf,
+ }
+
+ return nil
+ })
+ if err != nil {
+ log.Println("ERROR:", err.Error())
+ }
+
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Bisa dilihat isi fungsi `readFiles()`. Di fungsi tersebut ada sebuah channel bernama `chanOut` tipenya channel `FileInfo`, yang variabel channel ini langsung dijadikan nilai balik dari fungsi `readFiles()`.
+
+Di dalam fungsi `readFiles()` juga ada proses lain yang berjalan secara *asynchronous* dan *concurrent* yaitu goroutine yang isinya pembacaan file. Dalam blok kode baca file, informasi `path` dan konten file dibungkus dalam objek baru dengan tipe `FileInfo` kemudian dikirim ke channel `chanOut`.
+
+Karena proses utama dalam fungsi `readFiles` berada dalam goroutine, maka di `main()`, ketika statement `chanFileContent := readFiles()` selesai dieksekusi, bukan berarti proses pembacaan file selesai, malah mungkin baru saja dimulai. Ini karena proses baca file dijalankan dalam goroutine di dalam fungsi `readFiles()` tersebut.
+
+Mengenai channel `chanOut` sendiri, akan di-close ketika dipastikan **semua file sudah dikirim datanya ke channel tersebut** (silakan lihat statement `close(chanOut)` di akhir goroutine).
+
+Ok lanjut, karena di sini ada channel yang digunakan sebagai media pengiriman data (`FileInfo`), maka juga harus ada penerima data channel-nya dong. Yups.
+
+#### • Pipeline 2: MD5 Hash Konten File
+
+Tepat di bawah pipeline 1, tambahkan pemanggilan fungsi `getSum()` sebanyak 3x, bisa lebih banyak sih sebenarnya, bebas. Kemudian jadikan nilai balik pemanggilan fungsi tersebut sebagai variadic argument pemanggilan fungsi `mergeChanFileInfo()`.
+
+```go
+func main() {
+ // ...
+
+ // pipeline 2: calculate md5sum
+ chanFileSum1 := getSum(chanFileContent)
+ chanFileSum2 := getSum(chanFileContent)
+ chanFileSum3 := getSum(chanFileContent)
+ chanFileSum := mergeChanFileInfo(chanFileSum1, chanFileSum2, chanFileSum3)
+
+ // ...
+}
+```
+
+Fungsi `getSum()` isinya adalah perhitungan md5hash untuk konten yang datanya dikirim via channel `chanFileContent` hasil kembalian statement `readFiles()`. Fungsi `getSum()` ini juga mengembalikan channel. Karena kita menjalankan `getSum()` tiga kali, maka akan ada 3 channel. Nah ketiga channel tersebut nantinya kita merge ke satu channel saja via fungsi `mergeChanFileInfo()`.
+
+Fungsi `getSum()` menerima channel dan akan secara aktif memantau dan membaca data yang dikirim via channel tersebut hingga channel itu sendiri di-close. Fungsi seperti ini biasa disebut dengan **Fan-out function**. Fungsi fan-out digunakan untuk pendistribusian job ke banyak worker. channel `chanFileContent` di situ merupakan media untuk distribusi job, sedangkan pemanggil fungsi `getSum()` ini sendiri merepresentasikan satu worker. Jadi bisa dibilang, pada contoh di atas, **kita membuat 3 buah worker untuk melakukan operasi perhitungan sum MD5 terhadap data konten yang dikirim via channel `chanFileContent`**.
+
+Nah, karena di sini kita punya 3 worker yang jelasnya menghasilkan 3 buah channel baru, kita perlu sebuah mekanisme untuk menggabung channel tersebut, agar nanti mudah untuk dikontrol ([SSoT](https://en.wikipedia.org/wiki/Single_source_of_truth)). Di sinilah peran fungsi `mergeChanFileInfo()`.
+
+Fungsi `mergeChanFileInfo()` digunakan untuk *multiplexing* atau menggabung banyak channel ke satu channel saja, yang di mana channel ini juga akan ***otomatis di-close ketika channel input (`chanFileContent`) adalah ***closed******. Fungsi jenis seperti ini biasa disebut dengan **Fan-in function**.
+
+Jadi TL;DR nya:
+
+* Fungsi Fan-out digunakan untuk pembuatan worker, untuk distribusi job, yang proses distribusinya sendiri akan berhenti ketika channel inputan di-close.
+* Fungsi Fan-in digunakan untuk *multiplexing* atau menggabung banyak worker ke satu channel saja, yang di mana channel baru ini juga otomatis di-close ketika channel input adalah closed.
+
+Lanjut buat fungsi `getSum()`-nya.
+
+```go
+func getSum(chanIn <-chan FileInfo) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for fileInfo := range chanIn {
+ fileInfo.Sum = fmt.Sprintf("%x", md5.Sum(fileInfo.Content))
+ chanOut <- fileInfo
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Bisa dilihat, di situ channel inputan `chanIn` di-listen dan setiap ada penerimaan data (via channel tersebut) dilanjut ke proses kalkulasi md5 hash. Hasil hash-nya di tambahkan ke data `FileInfo` kemudian dikirim lagi ke channel `chanOut` yang di mana channel ini merupakan nilai balik fungsi `getSum()`.
+
+Ketika `chanIn` closed, maka bisa diasumsikan semua data sudah dikirim. Jika memang iya dan data-data tersebut sudah di proses (pencarian md5hash-nya), maka channel `chanOut` juga di-close.
+
+Next, buat fungsi merger-nya.
+
+```go
+func mergeChanFileInfo(chanInMany ...<-chan FileInfo) <-chan FileInfo {
+ wg := new(sync.WaitGroup)
+ chanOut := make(chan FileInfo)
+
+ wg.Add(len(chanInMany))
+ for _, eachChan := range chanInMany {
+ go func(eachChan <-chan FileInfo) {
+ for eachChanData := range eachChan {
+ chanOut <- eachChanData
+ }
+ wg.Done()
+ }(eachChan)
+ }
+
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Fungsi di atas digunakan untuk merging banyak channel ke satu channel. Memang sedikit susah di awal untuk dipahami, tapi nanti lama-kelamaan akan paham. Fungsi ini saya buat sama dengan skema fungsi Fan-in pada [Go Concurrency Patterns: Pipeline](https://blog.golang.org/pipelines).
+
+Secara garis besar, pada fungsi ini terjadi beberapa proses:
+
+* Dispatch goroutine baru untuk masing-masing channel yang dikirim via variadic argument/parameter fungsi ini.
+* Di dalam goroutine tersebut, append data yang diterima oleh masing-masing channel ke satu buah channel baru yaitu `chanOut`.
+* Channel `chanOut` ini dijadikan sebagai nilai balik fungsi.
+* Di situ kita gunakan `sync.WaitGroup` untuk kontrol goroutine. Kita akan tunggu hingga semua channel input adalah closed, setelah itu barulah kita close channel `chanOut` ini.
+
+#### • Pipeline 3: Rename file
+
+Tambahkan statement pipeline ketiga, yaitu pemanggilan fungsi Fan-out `rename()`, lalu panggil fungsi Fan-in `mergeChanFileInfo()` untuk multiplex channel kembalian fungsi `rename()`.
+
+```go
+func main() {
+ // ...
+
+ // pipeline 3: rename files
+ chanRename1 := rename(chanFileSum)
+ chanRename2 := rename(chanFileSum)
+ chanRename3 := rename(chanFileSum)
+ chanRename4 := rename(chanFileSum)
+ chanRename := mergeChanFileInfo(chanRename1, chanRename2, chanRename3, chanRename4)
+
+ // ...
+}
+```
+
+Di atas bisa dilihat ada 4 buah worker untuk rename di-*dispatch*. Fungsi rename ini sendiri tugasnya adalah me-rename file yang sudah kita baca isinya ke nama baru dengan format `file-.txt`.
+
+Tulis definisi fungsi `rename()`-nya. Secara garis besar semua penulisan fungsi Fan-out pasti mirip, yang beda hanya isi bisnis logic-nya saja. Kalau dalam `getSum()` isinya statement untuk kalkulasi hash, pada `rename()` ini isinya ya statements untuk rename file.
+
+```go
+func rename(chanIn <-chan FileInfo) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for fileInfo := range chanIn {
+ newPath := filepath.Join(tempPath, fmt.Sprintf("file-%s.txt", fileInfo.Sum))
+ err := os.Rename(fileInfo.FilePath, newPath)
+ fileInfo.IsRenamed = err == nil
+ chanOut <- fileInfo
+ }
+
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Bisa dilihat di atas kita rename file asli yang informasi path-nya ada di `FileInfo.FilePath`. Jika proses rename berhasil, maka `FileInfo.IsRenamed` di-set ke `true`.
+
+Setelah semua file berhasil di-rename, maka channel `chanOut` di-close.
+
+#### • Pipeline 4 / Output
+
+Serangkaian proses yang sudah kita setup punya ketergantungan tinggi satu sama lain, dan eksekusinya harus berurutan meskipun *concurrently*. Ini secara langsung juga mempermudah kita dalam mengolah output hasil pipeline. Kita cukup fokus ke channel hasil Fan-in yang paling terakhir, yaitu channel `chanRename`.
+
+```go
+func main() {
+ // ...
+
+ // print output
+ counterRenamed := 0
+ counterTotal := 0
+ for fileInfo := range chanRename {
+ if fileInfo.IsRenamed {
+ counterRenamed++
+ }
+ counterTotal++
+ }
+
+ log.Printf("%d/%d files renamed", counterRenamed, counterTotal)
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+Kita lakukan perulangan terhadap channel tersebut, lalu hitung jumlah file yang ditemukan vs jumlah file yang berhasil di-rename. Idealnya keduanya nilainya adalah sama, yaitu `3000`.
+
+Ok, sekarang program sudah siap. Mari kita jalankan untuk melihat hasilnya.
+
+
+
+Bisa dilihat bedanya, untuk rename 3000 file menggunakan cara sekuensial membutuhkan waktu `1.17` detik, sedangkan dengan metode pipeline butuh hanya `0.72` detik. Bedanya hampir **40%**! dan ini hanya 3000 file saja, bayangkan kalau jutaan file, mungkin lebih terasa perbandingan performnya.
+
+## A.62.6. Kesimpulan
+
+Pipeline concurrency pattern sangat bagus untuk diterapkan pada kasus yang proses-nya bisa di-klasifikasi menjadi sub-proses kecil-kecil yang secara I/O tidak saling tunggu (tapi secara flow harus berurutan).
+
+Untuk banyak kasus, metode pipeline ini sangat tepat guna. Kita bisa dengan mudah mengontrol penggunaan resource seperti **CPU** dengan cara menentukan angka ideal jumlah worker untuk masing-masing pipeline, tapi untuk bagian ini butuh *test and try*, karena tidak selalu banyak worker itu menghasilkan proses yang lebih cepat. Bisa jadi karena terlalu banyak worker malah lebih lambat. Jadi silakan lakukan testing saja, sesuaikan dengan spesifikasi CPU laptop/komputer/server yang digunakan.
+
+Ok sekian untuk chapter panjang ini.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From ac991cbc189ad0c270850b76e3935d70a8d0afd6 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:13 +0700
Subject: [PATCH 012/522] New translations a-data-type-conversion.md (English)
---
en/content-en/A-data-type-conversion.md | 254 ++++++++++++++++++++++++
1 file changed, 254 insertions(+)
create mode 100644 en/content-en/A-data-type-conversion.md
diff --git a/en/content-en/A-data-type-conversion.md b/en/content-en/A-data-type-conversion.md
new file mode 100644
index 000000000..aaf9ee53e
--- /dev/null
+++ b/en/content-en/A-data-type-conversion.md
@@ -0,0 +1,254 @@
+# A.43. Konversi Antar Tipe Data
+
+Pada chapter sebelum-sebelumnyanya kita sudah mengaplikasikan beberapa cara konversi data, contohnya seperti konversi `string` ↔ `int` menggunakan `strconv`, dan `time.Time` ↔ `string`. Pada chapter ini kita akan belajar lebih banyak.
+
+## A.43.1. Konversi Menggunakan `strconv`
+
+Package `strconv` berisi banyak fungsi yang sangat membantu kita untuk melakukan konversi. Berikut merupakan beberapa fungsi yang dalam package tersebut.
+
+#### • Fungsi `strconv.Atoi()`
+
+Fungsi ini digunakan untuk konversi data dari tipe `string` ke `int`. `strconv.Atoi()` menghasilkan 2 buah nilai kembalian, yaitu hasil konversi dan `error` (jika konversi sukses, maka `error` berisi `nil`).
+
+```go
+package main
+
+import "fmt"
+import "strconv"
+
+func main() {
+ var str = "124"
+ var num, err = strconv.Atoi(str)
+
+ if err == nil {
+ fmt.Println(num) // 124
+ }
+}
+```
+
+#### • Fungsi `strconv.Itoa()`
+
+Merupakan kebalikan dari `strconv.Atoi`, berguna untuk konversi `int` ke `string`.
+
+```go
+var num = 124
+var str = strconv.Itoa(num)
+
+fmt.Println(str) // "124"
+```
+
+#### • Fungsi `strconv.ParseInt()`
+
+Digunakan untuk konversi `string` berbentuk numerik dengan basis tertentu ke tipe numerik non-desimal dengan lebar data bisa ditentukan.
+
+Pada contoh berikut, string `"124"` dikonversi ke tipe numerik dengan ketentuan basis yang digunakan `10` dan lebar datanya mengikuti tipe `int64` (lihat parameter ketiga).
+
+```go
+var str = "124"
+var num, err = strconv.ParseInt(str, 10, 64)
+
+if err == nil {
+ fmt.Println(num) // 124
+}
+```
+
+Contoh lainnya, string `"1010"` dikonversi ke basis 2 (biner) dengan tipe data hasil adalah `int8`.
+
+```go
+var str = "1010"
+var num, err = strconv.ParseInt(str, 2, 8)
+
+if err == nil {
+ fmt.Println(num) // 10
+}
+```
+
+#### • Fungsi `strconv.FormatInt()`
+
+Berguna untuk konversi data numerik `int64` ke `string` dengan basis numerik bisa ditentukan sendiri.
+
+```go
+var num = int64(24)
+var str = strconv.FormatInt(num, 8)
+
+fmt.Println(str) // 30
+```
+
+#### • Fungsi `strconv.ParseFloat()`
+
+Digunakan untuk konversi `string` ke numerik desimal dengan lebar data bisa ditentukan.
+
+```go
+var str = "24.12"
+var num, err = strconv.ParseFloat(str, 32)
+
+if err == nil {
+ fmt.Println(num) // 24.1200008392334
+}
+```
+
+Pada contoh di atas, string `"24.12"` dikonversi ke float dengan lebar tipe data `float32`. Hasil konversi `strconv.ParseFloat` adalah sesuai dengan standar [IEEE Standard for Floating-Point Arithmetic](https://en.wikipedia.org/wiki/IEEE_floating_point).
+
+#### • Fungsi `strconv.FormatFloat()`
+
+Berguna untuk konversi data bertipe `float64` ke `string` dengan format eksponen, lebar digit desimal, dan lebar tipe data bisa ditentukan.
+
+```go
+var num = float64(24.12)
+var str = strconv.FormatFloat(num, 'f', 6, 64)
+
+fmt.Println(str) // 24.120000
+```
+
+Pada kode di atas, Data `24.12` yang bertipe `float64` dikonversi ke string dengan format eksponen `f` atau tanpa eksponen, lebar digit desimal 6 digit, dan lebar tipe data `float64`.
+
+Ada beberapa format eksponen yang bisa digunakan. Detailnya bisa dilihat di tabel berikut.
+
+| Format Eksponen | Penjelasan |
+|:--------------------:|:--------------------------------------------------------------------------------- |
+| `b` | -ddddp±ddd, a, eksponen biner (basis 2) |
+| `e` | -d.dddde±dd, a, eksponen desimal (basis 10) |
+| `E` | -d.ddddE±dd, a, eksponen desimal (basis 10) |
+| `f` | -ddd.dddd, tanpa eksponen |
+| `g` | Akan menggunakan format eksponen `e` untuk eksponen besar dan `f` untuk selainnya |
+| `G` | Akan menggunakan format eksponen `E` untuk eksponen besar dan `f` untuk selainnya |
+
+#### • Fungsi `strconv.ParseBool()`
+
+Digunakan untuk konversi `string` ke `bool`.
+
+```go
+var str = "true"
+var bul, err = strconv.ParseBool(str)
+
+if err == nil {
+ fmt.Println(bul) // true
+}
+```
+
+#### • Fungsi `strconv.FormatBool()`
+
+Digunakan untuk konversi `bool` ke `string`.
+
+```go
+var bul = true
+var str = strconv.FormatBool(bul)
+
+fmt.Println(str) // true
+```
+
+## A.43.2. Konversi Data Menggunakan Teknik Casting
+
+Keyword tipe data bisa digunakan untuk casting, atau konversi antar tipe data. Cara penggunaannya adalah dengan menuliskan tipe data tujuan casting sebagai fungsi, lalu menyisipkan data yang akan dikonversi sebagai parameter fungsi tersebut.
+
+```go
+var a float64 = float64(24)
+fmt.Println(a) // 24
+
+var b int32 = int32(24.00)
+fmt.Println(b) // 24
+```
+
+## A.43.3. Casting `string` ↔ `byte`
+
+String sebenarnya adalah slice/array `byte`. Di Go sebuah karakter biasa (bukan unicode) direpresentasikan oleh sebuah elemen slice byte. Tiap elemen slice berisi data `int` dengan basis desimal, yang merupakan kode ASCII dari karakter dalam string.
+
+Cara mendapatkan slice byte dari sebuah data string adalah dengan meng-casting-nya ke tipe `[]byte`.
+
+```go
+var text1 = "halo"
+var b = []byte(text1)
+
+fmt.Printf("%d %d %d %d \n", b[0], b[1], b[2], b[3])
+// 104 97 108 111
+```
+
+Pada contoh di atas, string dalam variabel `text1` dikonversi ke `[]byte`. Tiap elemen slice byte tersebut kemudian ditampilkan satu-per-satu.
+
+Contoh berikut ini merupakan kebalikan dari contoh di atas, data bertipe `[]byte` akan dicari bentuk `string`-nya.
+
+```go
+var byte1 = []byte{104, 97, 108, 111}
+var s = string(byte1)
+
+fmt.Printf("%s \n", s)
+// halo
+```
+
+Pada contoh di-atas, beberapa kode byte dituliskan dalam bentuk slice, ditampung variabel `byte1`. Lalu, nilai variabel tersebut di-cast ke `string`, untuk kemudian ditampilkan.
+
+Selain itu, tiap karakter string juga bisa di-casting ke bentuk `int`, hasilnya adalah sama yaitu data byte dalam bentuk numerik basis desimal, dengan ketentuan literal string yang digunakan adalah tanda petik satu (').
+
+Juga berlaku sebaliknya, data numerik jika di-casting ke bentuk string dideteksi sebagai kode ASCII dari karakter yang akan dihasilkan.
+
+```go
+var c int64 = int64('h')
+fmt.Println(c) // 104
+
+var d string = string(104)
+fmt.Println(d) // h
+```
+
+## A.43.4. Type Assertions Pada Interface Kosong (`interface{}`)
+
+**Type assertions** merupakan teknik untuk mengambil tipe data konkret dari data yang terbungkus dalam `interface{}`. Jadi bisa disimpulkan bahwa teknik type assertions hanya bisa dilakukan pada data bertipe `interface{}`. Lebih jelasnya silakan cek contoh berikut.
+
+Variabel `data` disiapkan bertipe `map[string]interface{}`, map tersebut berisikan beberapa item dengan tipe data value-nya berbeda satu sama lain, sementara tipe data untuk key-nya sama yaitu `string`.
+
+```go
+var data = map[string]interface{}{
+ "nama": "john wick",
+ "grade": 2,
+ "height": 156.5,
+ "isMale": true,
+ "hobbies": []string{"eating", "sleeping"},
+}
+
+fmt.Println(data["nama"].(string))
+fmt.Println(data["grade"].(int))
+fmt.Println(data["height"].(float64))
+fmt.Println(data["isMale"].(bool))
+fmt.Println(data["hobbies"].([]string))
+```
+
+Statement `data["nama"].(string)` maksudnya adalah, nilai `data["nama"]` yang bertipe `interface{}` diambil nilai konkretnya dalam bentuk string `string`.
+
+Pada kode di atas, tidak akan terjadi panic error, karena semua operasi type assertion adalah dilakukan menggunakan tipe data yang sudah sesuai dengan tipe data nilai aslinya. Seperti `data["nama"]` yang merupakan `string` pasti bisa di-asertasi ke tipe `string`.
+
+Coba lakukan asertasi ke tipe yang tidak sesuai dengan tipe nilai aslinya, seperti `data["nama"].(int)`, pasti akan men-trigger panic error.
+
+Nah, dari penjelasan di atas, terlihat bahwa kita harus tau terlebih dahulu apa tipe data asli dari data yang tersimpan dalam interface. Jika misal tidak tau, maka bisa gunakan teknik di bawah ini untuk pengecekan sukses tidaknya proses asertasi.
+
+Tipe asli data pada variabel `interface{}` bisa diketahui dengan cara meng-casting ke tipe `type`, namun casting ini hanya bisa dilakukan pada `switch`.
+
+```go
+for _, val := range data {
+ switch val.(type) {
+ case string:
+ fmt.Println(val.(string))
+ case int:
+ fmt.Println(val.(int))
+ case float64:
+ fmt.Println(val.(float64))
+ case bool:
+ fmt.Println(val.(bool))
+ case []string:
+ fmt.Println(val.([]string))
+ default:
+ fmt.Println(val.(int))
+ }
+}
+```
+
+Kombinasi `switch` - `case` bisa dimanfaatkan untuk deteksi tipe konkret data yang bertipe `interface{}`, contoh penerapannya seperti pada kode di atas.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From d47222cf06a723c5f4aaeeee254339fc89bf4183 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:14 +0700
Subject: [PATCH 013/522] New translations a-defer-exit.md (English)
---
en/content-en/A-defer-exit.md | 140 ++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
create mode 100644 en/content-en/A-defer-exit.md
diff --git a/en/content-en/A-defer-exit.md b/en/content-en/A-defer-exit.md
new file mode 100644
index 000000000..4182dfe32
--- /dev/null
+++ b/en/content-en/A-defer-exit.md
@@ -0,0 +1,140 @@
+# A.36. Defer & Exit
+
+**Defer** digunakan untuk mengakhirkan eksekusi sebuah statement tepat sebelum blok fungsi selesai. Sedangkan **Exit** digunakan untuk menghentikan program secara paksa (ingat, menghentikan program, tidak seperti `return` yang hanya menghentikan blok kode).
+
+## A.36.1. Penerapan keyword `defer`
+
+Seperti yang sudah dijelaskan secara singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode **dalam skope blok fungsi**. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan.
+
+Defer bisa ditempatkan di mana saja, awal maupun akhir blok. Tetapi tidak mempengaruhi kapan waktu dieksekusinya, akan selalu dieksekusi di akhir.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ defer fmt.Println("halo")
+ fmt.Println("selamat datang")
+}
+```
+
+Output:
+
+
+
+Keyword `defer` di atas akan mengakhirkan ekseusi `fmt.Println("halo")`, efeknya pesan `"halo"` akan muncul setelah `"selamat datang"`.
+
+Statement yang di-defer akan tetap muncul meskipun blok kode diberhentikan ditengah jalan menggunakan `return`. Contohnya seperti pada kode berikut.
+
+```go
+func main() {
+ orderSomeFood("pizza")
+ orderSomeFood("burger")
+}
+
+func orderSomeFood(menu string) {
+ defer fmt.Println("Terimakasih, silakan tunggu")
+ if menu == "pizza" {
+ fmt.Print("Pilihan tepat!", " ")
+ fmt.Print("Pizza ditempat kami paling enak!", "\n")
+ return
+ }
+
+ fmt.Println("Pesanan anda:", menu)
+}
+```
+
+Output:
+
+
+
+Info tambahan, ketika ada banyak statement yang di-defer, maka seluruhnya akan dieksekusi di akhir secara berurutan.
+
+## A.36.2. Kombinasi `defer` dan IIFE
+
+Penulis ingatkan lagi bahwa eksekusi defer adalah di akhir blok fungsi, bukan blok lainnya seperti blok seleksi kondisi.
+
+```go
+func main() {
+ number := 3
+
+ if number == 3 {
+ fmt.Println("halo 1")
+ defer fmt.Println("halo 3")
+ }
+
+ fmt.Println("halo 2")
+}
+```
+
+Output:
+
+```
+halo 1
+halo 2
+halo 3
+```
+
+Pada contoh di atas `halo 3` akan tetap di print setelah `halo 2` meskipun statement defer dipergunakan dalam blok seleksi kondisi `if`. Hal ini karena defer eksekusinya terjadi pada akhir blok fungsi (dalam contoh di atas `main()`), bukan pada akhir blok `if`.
+
+Agar `halo 3` bisa dimunculkan di akhir blok `if`, maka harus dibungkus dengan IIFE. Contoh:
+
+```go
+func main() {
+ number := 3
+
+ if number == 3 {
+ fmt.Println("halo 1")
+ func() {
+ defer fmt.Println("halo 3")
+ }()
+ }
+
+ fmt.Println("halo 2")
+}
+```
+
+Output:
+
+```
+halo 1
+halo 3
+halo 2
+```
+
+Bisa dilihat `halo 3` muncul sebelum `halo 2`, karena dalam blok seleksi kondisi `if` eksekusi defer terjadi dalam blok fungsi anonymous (IIFE).
+
+## A.36.3. Penerapan Fungsi `os.Exit()`
+
+Exit digunakan untuk menghentikan program secara paksa pada saat itu juga. Semua statement setelah exit tidak akan dieksekusi, termasuk juga defer.
+
+Fungsi `os.Exit()` berada dalam package `os`. Fungsi ini memiliki sebuah parameter bertipe numerik yang wajib diisi. Angka yang dimasukkan akan muncul sebagai **exit status** ketika program berhenti.
+
+```go
+package main
+
+import "fmt"
+import "os"
+
+func main() {
+ defer fmt.Println("halo")
+ os.Exit(1)
+ fmt.Println("selamat datang")
+}
+```
+
+Meskipun `defer fmt.Println("halo")` ditempatkan sebelum `os.Exit()`, statement tersebut tidak akan dieksekusi, karena di-tengah fungsi program dihentikan secara paksa.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From fed1227c5ba2b41c087a5fdcbd48508b8951e0d1 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:15 +0700
Subject: [PATCH 014/522] New translations a-encoding-base64.md (English)
---
en/content-en/A-encoding-base64.md | 92 ++++++++++++++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 en/content-en/A-encoding-base64.md
diff --git a/en/content-en/A-encoding-base64.md b/en/content-en/A-encoding-base64.md
new file mode 100644
index 000000000..5accb3b3a
--- /dev/null
+++ b/en/content-en/A-encoding-base64.md
@@ -0,0 +1,92 @@
+# A.46. Encode - Decode Base64
+
+Go memiliki package `encoding/base64`, berisikan fungsi-fungsi untuk kebutuhan **encode** dan **decode** data ke base64 dan sebaliknya. Data yang akan di-encode harus bertipe `[]byte`, perlu dilakukan casting untuk data-data yang belum sesuai tipenya.
+
+Ada beberapa cara yang bisa digunakan untuk encode dan decode data, dan pada chapter ini kita akan mempelajarinya.
+
+## A.46.1. Penerapan Fungsi `EncodeToString()` & `DecodeString()`
+
+Fungsi `EncodeToString()` digunakan untuk encode data dari bentuk string ke base64. Fungsi `DecodeString()` melakukan kebalikan dari `EncodeToString()`. Berikut adalah contoh penerapannya.
+
+```go
+package main
+
+import "encoding/base64"
+import "fmt"
+
+func main() {
+ var data = "john wick"
+
+ var encodedString = base64.StdEncoding.EncodeToString([]byte(data))
+ fmt.Println("encoded:", encodedString)
+
+ var decodedByte, _ = base64.StdEncoding.DecodeString(encodedString)
+ var decodedString = string(decodedByte)
+ fmt.Println("decoded:", decodedString)
+}
+```
+
+Variabel `data` yang bertipe `string`, harus di-casting terlebih dahulu ke dalam bentuk `[]byte` sebelum di-encode menggunakan fungsi `base64.StdEncoding.EncodeToString()`. Hasil encode adalah data base64 bertipe `string`.
+
+Sedangkan pada fungsi decode `base64.StdEncoding.DecodeString()`, data base64 bertipe `string` di-decode kembali ke string aslinya, tapi bertipe `[]byte`. Ekspresi `string(decodedByte)` menjadikan data `[]byte` tersebut berubah menjadi string.
+
+
+
+## A.46.2. Penerapan Fungsi `Encode()` & `Decode()`
+
+Kedua fungsi ini kegunaannya sama dengan fungsi yang sebelumnya kita bahas, salah satu pembedanya adalah data yang akan dikonversi dan hasilnya bertipe `[]byte`. Penggunaan cara ini cukup panjang karena variabel penyimpan hasil encode maupun decode harus disiapkan terlebih dahulu, dan harus memiliki lebar data sesuai dengan hasil yang akan ditampung (yang nilainya bisa dicari menggunakan fungsi `EncodedLen()` dan `DecodedLen()`).
+
+Lebih jelasnya silakan perhatikan contoh berikut.
+
+```go
+var data = "john wick"
+
+var encoded = make([]byte, base64.StdEncoding.EncodedLen(len(data)))
+base64.StdEncoding.Encode(encoded, []byte(data))
+var encodedString = string(encoded)
+fmt.Println(encodedString)
+
+var decoded = make([]byte, base64.StdEncoding.DecodedLen(len(encoded)))
+var _, err = base64.StdEncoding.Decode(decoded, encoded)
+if err != nil {
+ fmt.Println(err.Error())
+}
+var decodedString = string(decoded)
+fmt.Println(decodedString)
+```
+
+Fungsi `base64.StdEncoding.EncodedLen(len(data))` menghasilkan informasi lebar variable `data` ketika sudah di-encode. Nilai tersebut kemudian ditentukan sebagai lebar alokasi tipe `[]byte` pada variabel `encoded` yang nantinya digunakan untuk menampung hasil encoding.
+
+Fungsi `base64.StdEncoding.DecodedLen()` memiliki kegunaan sama dengan `EncodedLen()`, hanya saja digunakan untuk keperluan decoding.
+
+Dibanding 2 fungsi sebelumnya, fungsi `Encode()` dan `Decode()` memiliki beberapa perbedaan. Selain lebar data penampung encode/decode harus dicari terlebih dahulu, terdapat perbedaan lainnya, yaitu pada fungsi ini hasil encode/decode tidak didapat dari nilai kembalian, melainkan dari parameter. Variabel yang digunakan untuk menampung hasil, disisipkan pada parameter fungsi tersebut.
+
+Pada pemanggilan fungsi encode/decode, variabel `encoded` dan `decoded` tidak disisipkan nilai pointer-nya, cukup di-pass dengan cara biasa, tipe datanya sudah dalam bentuk `[]byte`.
+
+## A.46.3. Encode & Decode Data URL
+
+Khusus encode data string yang isinya merupakan URL, lebih efektif menggunakan `URLEncoding` dibandingkan `StdEncoding`.
+
+Cara penerapannya kurang lebih sama, bisa menggunakan metode pertama maupun metode kedua yang sudah dibahas di atas. Cukup ganti `StdEncoding` menjadi `URLEncoding`.
+
+```go
+var data = "https://kalipare.com/"
+
+var encodedString = base64.URLEncoding.EncodeToString([]byte(data))
+fmt.Println(encodedString)
+
+var decodedByte, _ = base64.URLEncoding.DecodeString(encodedString)
+var decodedString = string(decodedByte)
+fmt.Println(decodedString)
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 51368031e0c87a6ac30a5d06a20eabf29519871d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:16 +0700
Subject: [PATCH 015/522] New translations a-error-panic-recover.md (English)
---
en/content-en/A-error-panic-recover.md | 221 +++++++++++++++++++++++++
1 file changed, 221 insertions(+)
create mode 100644 en/content-en/A-error-panic-recover.md
diff --git a/en/content-en/A-error-panic-recover.md b/en/content-en/A-error-panic-recover.md
new file mode 100644
index 000000000..f8340b042
--- /dev/null
+++ b/en/content-en/A-error-panic-recover.md
@@ -0,0 +1,221 @@
+# A.37. Error, Panic, dan Recover
+
+Error merupakan topik yang sangat penting dalam pemrograman Go. Di bagian ini kita akan belajar mengenai pemanfaatan error dan cara membuat custom error sendiri. Selain itu, kita juga akan belajar tentang penggunaan **panic** untuk memunculkan panic error, dan **recover** untuk mengatasinya.
+
+## A.37.1. Pemanfaatan Error
+
+`error` merupakan sebuah tipe. Error memiliki 1 buah property berupa method `Error()`, method ini mengembalikan detail pesan error dalam string. Error termasuk tipe yang isinya bisa `nil`.
+
+Di Go, banyak sekali fungsi yang mengembalikan nilai balik lebih dari satu. Biasanya, salah satu kembalian adalah bertipe `error`. Contohnya seperti pada fungsi `strconv.Atoi()`. Fungsi tersebut digunakan untuk konversi data string menjadi numerik. Fungsi ini mengembalikan 2 nilai balik. Nilai balik pertama adalah hasil konversi, dan nilai balik kedua adalah `error`. Ketika konversi berjalan mulus, nilai balik kedua akan bernilai `nil`. Sedangkan ketika konversi gagal, penyebabnya bisa langsung diketahui dari error yang dikembalikan.
+
+Di bawah ini merupakan contoh program sederhana untuk deteksi inputan dari user, apakah numerik atau bukan. Dari sini kita akan belajar mengenai pemanfaatan error.
+
+```go
+package main
+
+import (
+ "fmt"
+ "strconv"
+)
+
+func main() {
+ var input string
+ fmt.Print("Type some number: ")
+ fmt.Scanln(&input)
+
+ var number int
+ var err error
+ number, err = strconv.Atoi(input)
+
+ if err == nil {
+ fmt.Println(number, "is number")
+ } else {
+ fmt.Println(input, "is not number")
+ fmt.Println(err.Error())
+ }
+}
+```
+
+Jalankan program, maka muncul tulisan `"Type some number: "`. Ketik angka bebas, jika sudah maka enter.
+
+Statement `fmt.Scanln(&input)` dipergunakan untuk men-capture inputan yang diketik oleh user sebelum dia menekan enter, lalu menyimpannya sebagai string ke variabel `input`.
+
+Selanjutnya variabel tersebut dikonversi ke tipe numerik menggunakan `strconv.Atoi()`. Fungsi tersebut mengembalikan 2 data, ditampung oleh `number` dan `err`.
+
+Data pertama (`number`) berisi hasil konversi. Dan data kedua `err`, berisi informasi errornya (jika memang terjadi error ketika proses konversi).
+
+Setelah itu dilakukan pengecekkan, ketika tidak ada error, `number` ditampilkan. Dan jika ada error, `input` ditampilkan beserta pesan errornya.
+
+Pesan error bisa didapat dari method `Error()` milik tipe `error`.
+
+
+
+## A.37.2. Membuat Custom Error
+
+Selain memanfaatkan error hasil kembalian suatu fungsi internal yang tersedia, kita juga bisa membuat objek error sendiri dengan menggunakan fungsi `errors.New()` (harus import package `errors` terlebih dahulu).
+
+Pada contoh berikut ditunjukkan bagaimana cara membuat custom error. Pertama siapkan fungsi dengan nama `validate()`, yang nantinya digunakan untuk pengecekan input, apakah inputan kosong atau tidak. Ketika kosong, maka error baru akan dibuat.
+
+```go
+package main
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+)
+
+func validate(input string) (bool, error) {
+ if strings.TrimSpace(input) == "" {
+ return false, errors.New("cannot be empty")
+ }
+ return true, nil
+}
+```
+
+Selanjutnya di fungsi main, buat proses sederhana untuk capture inputan user. Manfaatkan fungsi `validate()` untuk mengecek inputannya.
+
+```go
+func main() {
+ var name string
+ fmt.Print("Type your name: ")
+ fmt.Scanln(&name)
+
+ if valid, err := validate(name); valid {
+ fmt.Println("halo", name)
+ } else {
+ fmt.Println(err.Error())
+ }
+}
+```
+
+Fungsi `validate()` mengembalikan 2 data. Data pertama adalah nilai `bool` yang menandakan inputan apakah valid atau tidak. Data ke-2 adalah pesan error-nya (jika inputan tidak valid).
+
+Fungsi `strings.TrimSpace()` digunakan untuk menghilangkan karakter spasi sebelum dan sesudah string. Ini dibutuhkan karena user bisa saja menginputkan spasi lalu enter.
+
+Ketika inputan tidak valid, maka error baru dibuat dengan memanfaatkan fungsi `errors.New()`. Selain itu objek error juga bisa dibuat lewat fungsi `fmt.Errorf()`.
+
+
+
+## A.37.3. Penggunaan `panic`
+
+Panic digunakan untuk menampilkan *stack trace* error sekaligus menghentikan flow goroutine (karena `main()` juga merupakan goroutine, maka behaviour yang sama juga berlaku). Setelah ada panic, proses akan terhenti, apapun setelah tidak di-eksekusi kecuali proses yang sudah di-defer sebelumnya (akan muncul sebelum panic error).
+
+Panic menampilkan pesan error di console, sama seperti `fmt.Println()`. Informasi error yang ditampilkan adalah stack trace error, jadi sangat mendetail dan heboh.
+
+Kembali ke koding, pada program yang telah kita buat tadi, ubah `fmt.Println()` yang berada di dalam blok kondisi `else` pada fungsi main menjadi `panic()`, lalu tambahkan `fmt.Println()` setelahnya.
+
+```go
+func main() {
+ var name string
+ fmt.Print("Type your name: ")
+ fmt.Scanln(&name)
+
+ if valid, err := validate(name); valid {
+ fmt.Println("halo", name)
+ } else {
+ panic(err.Error())
+ fmt.Println("end")
+ }
+}
+```
+
+Jalankan program lalu langsung tekan enter, maka panic error muncul dan baris kode setelahnya tidak dijalankan.
+
+
+
+## A.37.4. Penggunaan `recover`
+
+Recover berguna untuk meng-handle panic error. Pada saat panic error muncul, recover men-take-over goroutine yang sedang panic (pesan panic tidak akan muncul).
+
+Ok, mari kita modifikasi sedikit fungsi di-atas untuk mempraktekkan bagaimana cara penggunaan recover. Tambahkan fungsi `catch()`, dalam fungsi ini terdapat statement `recover()` yang dia akan mengembalikan pesan panic error yang seharusnya muncul.
+
+Untuk menggunakan recover, fungsi/closure/IIFE di mana `recover()` berada harus dieksekusi dengan cara di-defer.
+
+```go
+func catch() {
+ if r := recover(); r != nil {
+ fmt.Println("Error occured", r)
+ } else {
+ fmt.Println("Application running perfectly")
+ }
+}
+
+func main() {
+ defer catch()
+
+ var name string
+ fmt.Print("Type your name: ")
+ fmt.Scanln(&name)
+
+ if valid, err := validate(name); valid {
+ fmt.Println("halo", name)
+ } else {
+ panic(err.Error())
+ fmt.Println("end")
+ }
+}
+```
+
+Output:
+
+
+
+## A.37.5. Pemanfaatan `recover` pada IIFE
+
+Contoh penerapan recover pada IIFE:
+
+```go
+func main() {
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println("Panic occured", r)
+ } else {
+ fmt.Println("Application running perfectly")
+ }
+ }()
+
+ panic("some error happen")
+}
+```
+
+Dalam real-world development, ada kalanya recover dibutuhkan tidak dalam blok fungsi terluar, tetapi dalam blok fungsi yg lebih spesifik.
+
+Silakan perhatikan contoh kode recover perulangan berikut. Umumnya, jika terjadi panic error, maka proses proses dalam scope blok fungsi akan terhenti, mengakibatkan perulangan juga akan terhenti secara paksa. Pada contoh berikut kita coba terapkan cara handle panic error tanpa menghentikan perulangan itu sendiri.
+
+```go
+func main() {
+ data := []string{"superman", "aquaman", "wonder woman"}
+
+ for _, each := range data {
+
+ func() {
+
+ // recover untuk IIFE dalam perulangan
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println("Panic occured on looping", each, "| message:", r)
+ } else {
+ fmt.Println("Application running perfectly")
+ }
+ }()
+
+ panic("some error happen")
+ }()
+
+ }
+}
+```
+
+Pada kode di atas, bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk recover panic dan juga ada kode untuk men-trigger panic error secara paksa. Ketika panic error terjadi, maka idealnya perulangan terhenti, tetapi pada contoh di atas tidak, dikarenakan operasi dalam perulangan sudah di bungkus dalam IIFE dan seperti yang kita tau sifat panic error adalah menghentikan proses secara paksa dalam scope blok fungsi.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From d6255095fec7d6b72c953febb890cacc7e8c9b55 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:17 +0700
Subject: [PATCH 016/522] New translations a-exec.md (English)
---
en/content-en/A-exec.md | 62 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 en/content-en/A-exec.md
diff --git a/en/content-en/A-exec.md b/en/content-en/A-exec.md
new file mode 100644
index 000000000..48d9d5b60
--- /dev/null
+++ b/en/content-en/A-exec.md
@@ -0,0 +1,62 @@
+# A.49. Exec
+
+**Exec** digunakan untuk eksekusi perintah command line lewat kode program. Command yang bisa dieksekusi adalah semua command yang bisa dieksekusi di terminal (atau CMD untuk pengguna Windows).
+
+## A.49.1. Penggunaan Exec
+
+Go menyediakan package `exec` berisikan banyak fungsi untuk keperluan eksekusi perintah CLI.
+
+Cara untuk eksekusi command cukup mudah, yaitu dengan menuliskan command dalam bentuk string, diikuti arguments-nya (jika ada) sebagai parameter variadic pada fungsi `exec.Command()`.
+
+```go
+package main
+
+import "fmt"
+import "os/exec"
+
+func main() {
+ var output1, _ = exec.Command("ls").Output()
+ fmt.Printf(" -> ls\n%s\n", string(output1))
+
+ var output2, _ = exec.Command("pwd").Output()
+ fmt.Printf(" -> pwd\n%s\n", string(output2))
+
+ var output3, _ = exec.Command("git", "config", "user.name").Output()
+ fmt.Printf(" -> git config user.name\n%s\n", string(output3))
+}
+```
+
+Fungsi `exec.Command()` digunakan untuk menjalankan command. Fungsi tersebut bisa langsung di-chain dengan method `Output()`, jika ingin mendapatkan outputnya. Output yang dihasilkan berbentuk `[]byte`, gunakan cast ke string untuk mengambil bentuk string-nya.
+
+
+
+## A.49.2. Rekomendasi Penggunaan Exec
+
+Kadang kala, pada saat eksekusi command yang sudah jelas-jelas ada (seperti `ls`, `dir`, atau lainnya) kita menemui error yang mengatakan command not found. Hal itu terjadi karena executable dari command-command tersebut tidak ada. Seperti di windows tidak ada `dir.exe` dan lainnya. Di OS non-windows-pun juga demikian.
+
+Untuk mengatasi masalah ini, tambahkan `bash -c` pada linux/nix command atau `cmd /C` untuk windows.
+
+```go
+if runtime.GOOS == "windows" {
+ output, err = exec.Command("cmd", "/C", "git config user.name").Output()
+} else {
+ output, err = exec.Command("bash", "-c", "git config user.name").Output()
+}
+```
+
+Statement `runtime.GOOS` mengembalikan informasi sistem operasi dalam string.
+
+## A.49.3. Method Exec Lainnya
+
+Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi dengan OS/CLI yang bisa dipergunakan. Detailnya silakan langsung merujuk ke dokumentasi [https://golang.org/pkg/os/exec/](https://golang.org/pkg/os/exec/)
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 8851ee057fb760427848556c7223ebb9a9b9c033 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:17 +0700
Subject: [PATCH 017/522] New translations a-file.md (English)
---
en/content-en/A-file.md | 163 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 163 insertions(+)
create mode 100644 en/content-en/A-file.md
diff --git a/en/content-en/A-file.md b/en/content-en/A-file.md
new file mode 100644
index 000000000..70093aefc
--- /dev/null
+++ b/en/content-en/A-file.md
@@ -0,0 +1,163 @@
+# A.50. File
+
+Ada beberapa cara yang bisa digunakan untuk operasi file di Go. Pada chapter ini kita akan mempelajari teknik yang paling dasar, yaitu dengan memanfaatkan `os.File`.
+
+## A.50.1. Membuat File Baru
+
+Pembuatan file di Go sangatlah mudah, cukup dengan memanggil fungsi `os.Create()` lalu memasukkan path file yang ingin dibuat sebagai parameter. Jika ternyata file yang akan dibuat sudah ada, maka akan ditimpa. Bisa memanfaatkan `os.IsNotExist()` untuk mendeteksi apakah file sudah dibuat atau belum.
+
+Berikut merupakan contoh pembuatan file.
+
+```go
+package main
+
+import "fmt"
+import "os"
+
+var path = "/Users/novalagung/Documents/temp/test.txt"
+
+func isError(err error) bool {
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+
+ return (err != nil)
+}
+
+func createFile() {
+ // deteksi apakah file sudah ada
+ var _, err = os.Stat(path)
+
+ // buat file baru jika belum ada
+ if os.IsNotExist(err) {
+ var file, err = os.Create(path)
+ if isError(err) { return }
+ defer file.Close()
+ }
+
+ fmt.Println("==> file berhasil dibuat", path)
+}
+
+func main() {
+ createFile()
+}
+```
+
+Fungsi `os.Stat()` mengembalikan 2 data, yaitu informasi tetang path yang dicari, dan error (jika ada). Masukkan error kembalian fungsi tersebut sebagai parameter fungsi `os.IsNotExist()`, untuk mendeteksi apakah file yang akan dibuat sudah ada. Jika belum ada, maka fungsi tersebut akan mengembalikan nilai `true`.
+
+Fungsi `os.Create()` digunakan untuk membuat file pada path tertentu. Fungsi ini mengembalikan objek `*os.File` dari file yang bersangkutan. File yang baru terbuat statusnya adalah otomatis **open**, maka dari itu perlu untuk di-**close** menggunakan method `file.Close()` setelah file tidak digunakan lagi.
+
+Membiarkan file terbuka ketika sudah tak lagi digunakan bukan hal yang baik, karena efeknya ke memory dan akses ke file itu sendiri, file akan di-lock sehingga tidak bisa digunakan oleh proses lain selama status file masih open atau belum di-close.
+
+
+
+## A.50.2. Mengedit Isi File
+
+Untuk mengedit file, yang perlu dilakukan pertama adalah membuka file dengan level akses **write**. Setelah mendapatkan objek file-nya, gunakan method `WriteString()` untuk pengisian data. Terakhir panggil method `Sync()` untuk menyimpan perubahan.
+
+```go
+func writeFile() {
+ // buka file dengan level akses READ & WRITE
+ var file, err = os.OpenFile(path, os.O_RDWR, 0644)
+ if isError(err) { return }
+ defer file.Close()
+
+ // tulis data ke file
+ _, err = file.WriteString("halo\n")
+ if isError(err) { return }
+ _, err = file.WriteString("mari belajar golang\n")
+ if isError(err) { return }
+
+ // simpan perubahan
+ err = file.Sync()
+ if isError(err) { return }
+
+ fmt.Println("==> file berhasil di isi")
+}
+
+func main() {
+ writeFile()
+}
+```
+
+Pada program di atas, file dibuka dengan level akses **read** dan **write** dengan kode permission **0664**. Setelah itu, beberapa string diisikan ke dalam file tersebut menggunakan `WriteString()`. Di akhir, semua perubahan terhadap file akan disimpan dengan dipanggilnya `Sync()`.
+
+
+
+## A.50.3. Membaca Isi File
+
+File yang ingin dibaca harus dibuka terlebih dahulu menggunakan fungsi `os.OpenFile()` dengan level akses minimal adalah **read**. Setelah itu, gunakan method `Read()` dengan parameter adalah variabel, yang di mana hasil proses baca akan disimpan ke variabel tersebut.
+
+```go
+// tambahkan di bagian import package io
+import "io"
+
+func readFile() {
+ // buka file
+ var file, err = os.OpenFile(path, os.O_RDONLY, 0644)
+ if isError(err) { return }
+ defer file.Close()
+
+ // baca file
+ var text = make([]byte, 1024)
+ for {
+ n, err := file.Read(text)
+ if err != io.EOF {
+ if isError(err) { break }
+ }
+ if n == 0 {
+ break
+ }
+ }
+ if isError(err) { return }
+
+ fmt.Println("==> file berhasil dibaca")
+ fmt.Println(string(text))
+}
+
+func main() {
+ readFile()
+}
+```
+
+Pada kode di atas `os.OpenFile()` digunakan untuk membuka file. Fungsi tersebut memiliki beberapa parameter.
+
+ 1. Parameter pertama adalah path file yang akan dibuka.
+ 2. Parameter kedua adalah level akses. `os.O_RDONLY` maksudnya adalah **read only**.
+ 3. Parameter ketiga adalah permission file-nya.
+
+Variabel `text` disiapkan bertipe slice `[]byte` dengan alokasi elemen 1024. Variabel tersebut bertugas menampung data hasil statement `file.Read()`. Proses pembacaan file akan dilakukan terus menerus, berurutan dari baris pertama hingga akhir.
+
+Error yang muncul ketika eksekusi `file.Read()` akan di-filter, ketika error tersebut adalah selain `io.EOF` maka proses baca file akan berlanjut. Error `io.EOF` sendiri menandakan bahwa file yang sedang dibaca adalah baris terakhir isi atau **end of file**.
+
+
+
+## A.50.4. Menghapus File
+
+Cara menghapus file sangatlah mudah, cukup panggil fungsi `os.Remove()`, masukan path file yang ingin dihapus sebagai parameter.
+
+```go
+func deleteFile() {
+ var err = os.Remove(path)
+ if isError(err) { return }
+
+ fmt.Println("==> file berhasil di delete")
+}
+
+func main() {
+ deleteFile()
+}
+```
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 3b79b006d5e6111517b3acf2ed6080a00f6d6dff Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:18 +0700
Subject: [PATCH 018/522] New translations a-fungsi-closure.md (English)
---
en/content-en/A-fungsi-closure.md | 179 ++++++++++++++++++++++++++++++
1 file changed, 179 insertions(+)
create mode 100644 en/content-en/A-fungsi-closure.md
diff --git a/en/content-en/A-fungsi-closure.md b/en/content-en/A-fungsi-closure.md
new file mode 100644
index 000000000..86c9d0c2e
--- /dev/null
+++ b/en/content-en/A-fungsi-closure.md
@@ -0,0 +1,179 @@
+# A.21. Fungsi Closure
+
+Definisi **Closure** adalah sebuah fungsi yang bisa disimpan dalam variabel. Dengan menerapkan konsep tersebut, kita bisa membuat fungsi di dalam fungsi, atau bahkan membuat fungsi yang mengembalikan fungsi.
+
+Closure merupakan *anonymous function* atau fungsi tanpa nama. Biasa dimanfaatkan untuk membungkus suatu proses yang hanya dipakai sekali atau dipakai pada blok tertentu saja.
+
+## A.21.1. Closure Disimpan Sebagai Variabel
+
+Sebuah fungsi tanpa nama bisa disimpan dalam variabel. Variabel yang menyimpan closure memiliki sifat seperti fungsi yang disimpannya. Di bawah ini adalah contoh program sederhana untuk mencari nilai terendah dan tertinggi dari suatu array. Logika pencarian dibungkus dalam closure yang ditampung oleh variabel `getMinMax`.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var getMinMax = func(n []int) (int, int) {
+ var min, max int
+ for i, e := range n {
+ switch {
+ case i == 0:
+ max, min = e, e
+ case e > max:
+ max = e
+ case e < min:
+ min = e
+ }
+ }
+ return min, max
+ }
+
+ var numbers = []int{2, 3, 4, 3, 4, 2, 3}
+ var min, max = getMinMax(numbers)
+ fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max)
+}
+```
+
+
+Bisa dilihat pada kode di atas bagaimana sebuah closure dibuat dan dipanggil. Sedikit berbeda memang dibanding pembuatan fungsi biasa. Fungsi ditulis tanpa nama, lalu ditampung dalam variabel.
+
+```go
+var getMinMax = func(n []int) (int, int) {
+ // ...
+}
+```
+
+Cara pemanggilannya, dengan menuliskan nama variabel tersebut sebagai fungsi, seperti pemanggilan fungsi biasa.
+
+```go
+var min, max = getMinMax(numbers)
+```
+
+Output program:
+
+
+
+---
+
+Berikut adalah penjelasan tambahan mengenai kode di atas
+
+## A.21.1.1. Penggunaan Template String `%v`
+
+Template `%v` digunakan untuk menampilkan segala jenis data. Bisa array, int, float, bool, dan lainnya.
+
+```go
+fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max)
+```
+
+Bisa dilihat pada statement di atas, data bertipe array dan numerik ditampilkan menggunakan `%v`. Template ini biasa dimanfaatkan untuk menampilkan sebuah data yang tipe nya bisa dinamis atau belum diketahui. Sangat tepat jika digunakan pada data bertipe `interface{}` yang nantinya akan di bahas pada chapter [A.27. Interface](/A-interface.html).
+
+---
+
+## A.21.2. Immediately-Invoked Function Expression (IIFE)
+
+Closure jenis ini dieksekusi langsung pada saat deklarasinya. Biasa digunakan untuk membungkus proses yang hanya dilakukan sekali, bisa mengembalikan nilai, bisa juga tidak.
+
+Di bawah ini merupakan contoh sederhana penerapan metode IIFE untuk filtering data array.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var numbers = []int{2, 3, 0, 4, 3, 2, 0, 4, 2, 0, 3}
+
+ var newNumbers = func(min int) []int {
+ var r []int
+ for _, e := range numbers {
+ if e < min {
+ continue
+ }
+ r = append(r, e)
+ }
+ return r
+ }(3)
+
+ fmt.Println("original number :", numbers)
+ fmt.Println("filtered number :", newNumbers)
+}
+```
+
+Output program:
+
+
+
+Ciri khas IIFE adalah adanya kurung parameter tepat setelah deklarasi closure berakhir. Jika ada parameter, bisa juga dituliskan dalam kurung parameternya.
+
+```go
+var newNumbers = func(min int) []int {
+ // ...
+}(3)
+```
+
+Pada contoh di atas IIFE menghasilkan nilai balik yang kemudian ditampung `newNumber`. Perlu diperhatikan bahwa yang ditampung adalah **nilai kembaliannya** bukan body fungsi atau **closure**.
+
+> Closure bisa juga dengan gaya manifest typing, caranya dengan menuliskan skema closure-nya sebagai tipe data. Contoh: var closure (func (string, int, []string) int) closure = func (a string, b int, c []string) int { // .. }
+## A.21.3. Closure Sebagai Nilai Kembalian
+
+Salah satu keunikan closure lainnya adalah bisa dijadikan sebagai nilai balik fungsi, cukup aneh memang, tapi pada suatu kondisi teknik ini sangat membantu. Di bawah ini disediakan sebuah fungsi bernama `findMax()`, fungsi ini salah satu nilai kembaliannya berupa closure.
+
+```go
+package main
+
+import "fmt"
+
+func findMax(numbers []int, max int) (int, func() []int) {
+ var res []int
+ for _, e := range numbers {
+ if e <= max {
+ res = append(res, e)
+ }
+ }
+ return len(res), func() []int {
+ return res
+ }
+}
+```
+
+Nilai kembalian ke-2 pada fungsi di atas adalah closure dengan skema `func() []int`. Bisa dilihat di bagian akhir, ada fungsi tanpa nama yang dikembalikan.
+
+```go
+return len(res), func() []int {
+ return res
+}
+```
+
+> Fungsi tanpa nama yang akan dikembalikan boleh disimpan pada variabel terlebih dahulu. Contohnya: var getNumbers = func() []int { return res } return len(res), getNumbers
+Sedikit tentang fungsi `findMax()`, fungsi ini digunakan untuk mencari banyaknya angka-angka yang nilainya di bawah atau sama dengan angka tertentu. Nilai kembalian pertama adalah jumlah angkanya. Nilai kembalian kedua berupa closure yang mengembalikan angka-angka yang dicari. Berikut merupakan contoh implementasi fungsi tersebut.
+
+```go
+func main() {
+ var max = 3
+ var numbers = []int{2, 3, 0, 4, 3, 2, 0, 4, 2, 0, 3}
+ var howMany, getNumbers = findMax(numbers, max)
+ var theNumbers = getNumbers()
+
+ fmt.Println("numbers\t:", numbers)
+ fmt.Printf("find \t: %d\n\n", max)
+
+ fmt.Println("found \t:", howMany) // 9
+ fmt.Println("value \t:", theNumbers) // [2 3 0 3 2 0 2 0 3]
+}
+```
+
+Output program:
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From fe476ea0c7e3c5bb6888282019c9e763ef29ad46 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:19 +0700
Subject: [PATCH 019/522] New translations a-fungsi-multiple-return.md
(English)
---
en/content-en/A-fungsi-multiple-return.md | 108 ++++++++++++++++++++++
1 file changed, 108 insertions(+)
create mode 100644 en/content-en/A-fungsi-multiple-return.md
diff --git a/en/content-en/A-fungsi-multiple-return.md b/en/content-en/A-fungsi-multiple-return.md
new file mode 100644
index 000000000..131ff118a
--- /dev/null
+++ b/en/content-en/A-fungsi-multiple-return.md
@@ -0,0 +1,108 @@
+# A.19. Fungsi Multiple Return
+
+Umumnya fungsi hanya memiliki satu buah nilai balik saja. Jika ada kebutuhan di mana data yang dikembalikan harus banyak, biasanya digunakanlah tipe seperti `map`, slice, atau `struct` sebagai nilai balik.
+
+Go menyediakan kapabilitas bagi programmer untuk membuat fungsi memiliki banyak nilai balik. Pada chapter ini akan dibahas bagaimana penerapannya.
+
+## A.19.1 Penerapan Fungsi Multiple Return
+
+Cara membuat fungsi yang memiliki banyak nilai balik tidaklah sulit. Tinggal tulis saja pada saat deklarasi fungsi semua tipe data nilai yang dikembalikan, dan pada keyword `return` tulis semua data yang ingin dikembalikan. Contoh bisa dilihat pada berikut.
+
+```go
+package main
+
+import "fmt"
+import "math"
+
+func calculate(d float64) (float64, float64) {
+ // hitung luas
+ var area = math.Pi * math.Pow(d / 2, 2)
+ // hitung keliling
+ var circumference = math.Pi * d
+
+ // kembalikan 2 nilai
+ return area, circumference
+}
+```
+
+Fungsi `calculate()` di atas menerima satu buah parameter (`diameter`) yang digunakan dalam proses perhitungan. Di dalam fungsi tersebut ada 2 hal yang dihitung, yaitu nilai **luas** dan **keliling**. Kedua nilai tersebut kemudian dijadikan sebagai return value fungsi.
+
+Cara pendefinisian banyak nilai balik bisa dilihat pada kode di atas, langsung tulis tipe data semua nilai balik dipisah tanda koma, lalu ditambahkan kurung di antaranya.
+
+```go
+func calculate(d float64) (float64, float64)
+```
+
+Tak lupa di bagian penulisan keyword `return` harus dituliskan juga semua data yang dijadikan nilai balik (dengan pemisah tanda koma).
+
+```go
+return area, circumference
+```
+
+Implementasi dari fungsi `calculate()` di atas, bisa dilihat pada kode berikut.
+
+```go
+func main() {
+ var diameter float64 = 15
+ var area, circumference = calculate(diameter)
+
+ fmt.Printf("luas lingkaran\t\t: %.2f \n", area)
+ fmt.Printf("keliling lingkaran\t: %.2f \n", circumference)
+}
+```
+
+Output program:
+
+
+
+Karena fungsi tersebut memiliki banyak nilai balik, maka pada pemanggilannya harus disiapkan juga banyak variabel untuk menampung nilai kembalian yang ada (sesuai jumlah nilai balik fungsi).
+
+```go
+var area, circumference = calculate(diameter)
+```
+
+## A.19.2 Fungsi Dengan Predefined Return Value
+
+Keunikan lainnya yang jarang ditemui di bahasa lain adalah, di Go variabel yang digunakan sebagai nilai balik bisa didefinisikan di awal.
+
+```go
+func calculate(d float64) (area float64, circumference float64) {
+ area = math.Pi * math.Pow(d / 2, 2)
+ circumference = math.Pi * d
+
+ return
+}
+```
+
+Fungsi `calculate` kita modifikasi menjadi lebih sederhana. Bisa dilihat di kode di atas, ada cukup banyak perbedaan dibanding fungsi `calculate` sebelumnya. Perhatikan kode berikut.
+
+```go
+func calculate(d float64) (area float64, circumference float64) {
+```
+
+Fungsi dideklarasikan memiliki 2 buah tipe data, dan variabel yang nantinya dijadikan nilai balik juga dideklarasikan. Variabel `area` yang bertipe `float64`, dan `circumference` bertipe `float64`.
+
+Karena variabel nilai balik sudah ditentukan di awal, untuk mengembalikan nilai cukup dengan memanggil `return` tanpa perlu diikuti variabel apapun. Nilai terakhir `area` dan `circumference` sebelum pemanggilan keyword `return` adalah hasil dari fungsi di atas.
+
+---
+
+Ada beberapa hal baru dari kode di atas yang perlu dibahas, seperti `math.Pow()` dan `math.Pi`. Berikut adalah penjelasannya.
+
+#### • Penggunaan Fungsi `math.Pow()`
+
+Fungsi `math.Pow()` digunakan untuk memangkat nilai. `math.Pow(2, 3)` berarti 2 pangkat 3, hasilnya 8. Fungsi ini berada dalam package `math`.
+
+#### • Penggunaan Konstanta `math.Pi`
+
+`math.Pi` adalah konstanta bawaan `package math` yang merepresentasikan **Pi** atau **22/7**.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 5c2cdb0141cfc333da0c72001114ec011aee3db7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:20 +0700
Subject: [PATCH 020/522] New translations a-fungsi-sebagai-parameter.md
(English)
---
en/content-en/A-fungsi-sebagai-parameter.md | 109 ++++++++++++++++++++
1 file changed, 109 insertions(+)
create mode 100644 en/content-en/A-fungsi-sebagai-parameter.md
diff --git a/en/content-en/A-fungsi-sebagai-parameter.md b/en/content-en/A-fungsi-sebagai-parameter.md
new file mode 100644
index 000000000..90c9fa18b
--- /dev/null
+++ b/en/content-en/A-fungsi-sebagai-parameter.md
@@ -0,0 +1,109 @@
+# A.22. Fungsi Sebagai parameter
+
+Setelah pada chapter sebelumnya kita belajar mengenai fungsi yang mengembalikan nilai balik berupa fungsi, kali ini topiknya tidak kalah unik, yaitu fungsi yang digunakan sebagai parameter.
+
+Di Go, fungsi bisa dijadikan sebagai tipe data variabel. Dari situ sangat memungkinkan untuk menjadikannya sebagai parameter juga.
+
+## A.22.1. Penerapan Fungsi Sebagai Parameter
+
+Cara membuat parameter fungsi adalah dengan langsung menuliskan skema fungsi nya sebagai tipe data. Contohnya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func filter(data []string, callback func(string) bool) []string {
+ var result []string
+ for _, each := range data {
+ if filtered := callback(each); filtered {
+ result = append(result, each)
+ }
+ }
+ return result
+}
+```
+
+Parameter `callback` merupakan sebuah closure yang dideklarasikan bertipe `func(string) bool`. Closure tersebut dipanggil di tiap perulangan dalam fungsi `filter()`.
+
+Fungsi `filter()` sendiri kita buat untuk filtering data array (yang datanya didapat dari parameter pertama), dengan kondisi filter bisa ditentukan sendiri. Di bawah ini adalah contoh pemanfaatan fungsi tersebut.
+
+```go
+func main() {
+ var data = []string{"wick", "jason", "ethan"}
+ var dataContainsO = filter(data, func(each string) bool {
+ return strings.Contains(each, "o")
+ })
+ var dataLenght5 = filter(data, func(each string) bool {
+ return len(each) == 5
+ })
+
+ fmt.Println("data asli \t\t:", data)
+ // data asli : [wick jason ethan]
+
+ fmt.Println("filter ada huruf \"o\"\t:", dataContainsO)
+ // filter ada huruf "o" : [jason]
+
+ fmt.Println("filter jumlah huruf \"5\"\t:", dataLenght5)
+ // filter jumlah huruf "5" : [jason ethan]
+}
+```
+
+Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di atas. Berikut merupakan penjelasannya.
+
+ 1. Data array (yang didapat dari parameter pertama) akan di-looping.
+ 2. Di tiap perulangannya, closure `callback` dipanggil, dengan disisipkan data tiap elemen perulangan sebagai parameter.
+ 3. Closure `callback` berisikan kondisi filtering, dengan hasil bertipe `bool` yang kemudian dijadikan nilai balik dikembalikan.
+ 5. Di dalam fungsi `filter()` sendiri, ada proses seleksi kondisi (yang nilainya didapat dari hasil eksekusi closure `callback`). Ketika kondisinya bernilai `true`, maka data elemen yang sedang diulang dinyatakan lolos proses filtering.
+ 6. Data yang lolos ditampung variabel `result`. Variabel tersebut dijadikan sebagai nilai balik fungsi `filter()`.
+
+
+
+Pada `dataContainsO`, parameter kedua fungsi `filter()` berisikan statement untuk deteksi apakah terdapat substring `"o"` di dalam nilai variabel `each` (yang merupakan data tiap elemen), jika iya, maka kondisi filter bernilai `true`, dan sebaliknya.
+
+pada contoh ke-2 (`dataLength5`), closure `callback` berisikan statement untuk deteksi jumlah karakter tiap elemen. Jika ada elemen yang jumlah karakternya adalah 5, berarti elemen tersebut lolos filter.
+
+Memang butuh usaha ekstra untuk memahami pemanfaatan closure sebagai parameter fungsi. Tapi setelah paham, penerapan teknik ini pada kondisi yang tepat akan sangat membantu proses pembuatan aplikasi.
+
+## A.22.2. Alias Skema Closure
+
+Kita sudah mempelajari bahwa closure bisa dimanfaatkan sebagai tipe parameter, contohnya seperti pada fungsi `filter()`. Pada fungsi tersebut kebetulan skema tipe parameter closure-nya tidak terlalu panjang, hanya ada satu buah parameter dan satu buah nilai balik.
+
+Pada fungsi yang skema-nya cukup panjang, akan lebih baik jika menggunakan alias, apalagi ketika ada parameter fungsi lain yang juga menggunakan skema yang sama. Membuat alias fungsi berarti menjadikan skema fungsi tersebut menjadi tipe data baru. Caranya dengan menggunakan keyword `type`. Contoh:
+
+```go
+type FilterCallback func(string) bool
+
+func filter(data []string, callback FilterCallback) []string {
+ // ...
+}
+```
+
+Skema `func(string) bool` diubah menjadi tipe dengan nama `FilterCallback`. Tipe tersebut kemudian digunakan sebagai tipe data parameter `callback`.
+
+---
+
+Di bawah ini merupakan penjelasan tambahan mengenai fungsi `strings.Contains()`.
+
+## A.22.2.1. Penggunaan Fungsi `string.Contains()`
+
+Inti dari fungsi ini adalah untuk deteksi apakah sebuah substring adalah bagian dari string, jika iya maka akan bernilai `true`, dan sebaliknya. Contoh penggunaannya:
+
+```go
+var result = strings.Contains("Golang", "ang")
+// true
+```
+
+Variabel `result` bernilai `true` karena string `"ang"` merupakan bagian dari string `"Golang"`.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From d8284a6c5c1855c3684e73e2b86401419414db83 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:21 +0700
Subject: [PATCH 021/522] New translations a-fungsi-variadic.md (English)
---
en/content-en/A-fungsi-variadic.md | 156 +++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
create mode 100644 en/content-en/A-fungsi-variadic.md
diff --git a/en/content-en/A-fungsi-variadic.md b/en/content-en/A-fungsi-variadic.md
new file mode 100644
index 000000000..eaf48b82b
--- /dev/null
+++ b/en/content-en/A-fungsi-variadic.md
@@ -0,0 +1,156 @@
+# A.20. Fungsi Variadic
+
+Go mengadopsi konsep **variadic function** atau pembuatan fungsi dengan parameter sejenis yang tak terbatas. Maksud **tak terbatas** di sini adalah jumlah parameter yang disisipkan ketika pemanggilan fungsi bisa berapa saja.
+
+Parameter variadic memiliki sifat yang mirip dengan slice. Nilai dari parameter-parameter yang disisipkan bertipe data sama, dan ditampung oleh sebuah variabel saja. Cara pengaksesan tiap datanya juga sama, dengan menggunakan index.
+
+Pada chapter ini kita akan belajar mengenai cara penerapan fungsi variadic.
+
+## A.20.1. Penerapan Fungsi Variadic
+
+Deklarasi parameter variadic sama dengan cara deklarasi variabel biasa, pembedanya adalah pada parameter jenis ini ditambahkan tanda titik tiga kali (`...`) tepat setelah penulisan variabel (sebelum tipe data). Nantinya semua nilai yang disisipkan sebagai parameter akan ditampung oleh variabel tersebut.
+
+Berikut merupakan contoh penerepannya.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
+ var msg = fmt.Sprintf("Rata-rata : %.2f", avg)
+ fmt.Println(msg)
+}
+
+func calculate(numbers ...int) float64 {
+ var total int = 0
+ for _, number := range numbers {
+ total += number
+ }
+
+ var avg = float64(total) / float64(len(numbers))
+ return avg
+}
+```
+
+Output program:
+
+
+
+Bisa dilihat pada fungsi `calculate()`, parameter `numbers` dideklarasikan dengan disisipkan tanda 3 titik (`...`), menandakan bahwa `numbers` adalah sebuah parameter variadic dengan tipe data `int`.
+
+```go
+func calculate(numbers ...int) float64 {
+```
+
+Pemanggilan fungsi dilakukan seperti biasa, hanya saja jumlah parameter yang disisipkan bisa banyak.
+
+```go
+var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
+```
+
+Nilai tiap parameter bisa diakses seperti cara pengaksesan tiap elemen slice. Pada contoh di atas metode yang dipilih adalah `for` - `range`.
+
+```go
+for _, number := range numbers {
+```
+
+---
+
+Berikut merupakan penjelasan tambahan dari kode yang telah kita tulis.
+
+#### • Penggunaan Fungsi `fmt.Sprintf()`
+
+Fungsi `fmt.Sprintf()` pada dasarnya sama dengan `fmt.Printf()`, hanya saja fungsi ini tidak menampilkan nilai, melainkan mengembalikan nilainya dalam bentuk string. Pada kasus di atas, nilai kembalian `fmt.Sprintf()` ditampung oleh variabel `msg`.
+
+Selain `fmt.Sprintf()`, ada juga `fmt.Sprint()` dan `fmt.Sprintln()`.
+
+#### • Penggunaan Fungsi `float64()`
+
+Sebelumnya sudah dibahas bahwa `float64` merupakan tipe data. Tipe data jika ditulis sebagai fungsi (penandanya ada tanda kurungnya) berguna untuk **casting**. Casting sendiri adalah teknik untuk konversi tipe sebuah data ke tipe lain. Sebagian besar tipe data dasar yang telah dipelajari pada chapter [A.9. Variabel](/A-variabel.html) bisa di-cast. Dan cara penerapannya juga sama, cukup panggil sebagai fungsi, lalu masukan data yang ingin dikonversi sebagai parameter.
+
+Pada contoh di atas, variabel `total` yang tipenya adalah `int`, dikonversi menjadi `float64`, begitu juga `len(numbers)` yang menghasilkan `int` dikonversi ke `float64`.
+
+Variabel `avg` perlu dijadikan `float64` karena penghitungan rata-rata lebih sering menghasilkan nilai desimal.
+
+Operasi bilangan (perkalian, pembagian, dan lainnya) di Go hanya bisa dilakukan jika tipe datanya sejenis. Maka dari itulah perlu adanya casting ke tipe `float64` pada tiap operand.
+
+---
+
+## A.20.2. Pengisian Parameter Fungsi Variadic Menggunakan Data Slice
+
+Slice bisa digunakan sebagai parameter variadic. Caranya dengan menambahkan tanda titik tiga kali, tepat setelah nama variabel yang dijadikan parameter. Contohnya bisa dilihat pada kode berikut.
+
+```go
+var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3}
+var avg = calculate(numbers...)
+var msg = fmt.Sprintf("Rata-rata : %.2f", avg)
+
+fmt.Println(msg)
+```
+
+Pada kode di atas, variabel `numbers` yang merupakan slice int, disisipkan ke fungsi `calculate()` sebagai parameter variadic (bisa dilihat tanda 3 titik setelah penulisan variabel). Teknik ini sangat berguna ketika sebuah data slice ingin difungsikan sebagai parameter variadic.
+
+Perhatikan juga kode berikut ini. Intinya adalah sama, hanya caranya yang berbeda.
+
+```go
+var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3}
+var avg = calculate(numbers...)
+
+// atau
+
+var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
+```
+
+Pada deklarasi parameter fungsi variadic, tanda 3 titik (`...`) dituliskan sebelum tipe data parameter. Sedangkan pada pemanggilan fungsi dengan menyisipkan parameter array, tanda tersebut dituliskan di belakang variabelnya.
+
+## A.20.3. Fungsi Dengan Parameter Biasa & Variadic
+
+Parameter variadic bisa dikombinasikan dengan parameter biasa, dengan syarat parameter variadic-nya harus diposisikan di akhir. Contohnya bisa dilihat pada kode berikut.
+
+```go
+import "fmt"
+import "strings"
+
+func yourHobbies(name string, hobbies ...string) {
+ var hobbiesAsString = strings.Join(hobbies, ", ")
+
+ fmt.Printf("Hello, my name is: %s\n", name)
+ fmt.Printf("My hobbies are: %s\n", hobbiesAsString)
+}
+```
+
+Nilai parameter pertama fungsi `yourHobbies()` akan ditampung oleh `name`, sedangkan nilai parameter kedua dan seterusnya akan ditampung oleh `hobbies` sebagai slice.
+
+Cara pemanggilannya masih sama seperi pada fungsi biasa.
+
+```go
+func main() {
+ yourHobbies("wick", "sleeping", "eating")
+}
+```
+
+Jika parameter kedua dan seterusnya ingin diisi dengan data dari slice, maka gunakan tanda titik tiga kali.
+
+```go
+func main() {
+ var hobbies = []string{"sleeping", "eating"}
+ yourHobbies("wick", hobbies...)
+}
+```
+
+Output program:
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From ee7f1bf0dfd35b569f3032ff91da0916e972ed1d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:22 +0700
Subject: [PATCH 022/522] New translations a-fungsi.md (English)
---
en/content-en/A-fungsi.md | 179 ++++++++++++++++++++++++++++++++++++++
1 file changed, 179 insertions(+)
create mode 100644 en/content-en/A-fungsi.md
diff --git a/en/content-en/A-fungsi.md b/en/content-en/A-fungsi.md
new file mode 100644
index 000000000..6879cfb08
--- /dev/null
+++ b/en/content-en/A-fungsi.md
@@ -0,0 +1,179 @@
+# A.18. Fungsi
+
+Fungsi merupakan aspek penting dalam pemrograman. Definisi fungsi sendiri adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (kependekan dari *don't repeat yourself*), tak perlu menuliskan banyak kode yang kegunaannya berkali-kali, cukup sekali saja lalu panggil sesuai kebutuhan.
+
+Pada chapter ini kita akan belajar tentang penggunaan fungsi di Go.
+
+## A.18.1. Penerapan Fungsi
+
+Sebenarnya tanpa sadar, kita sudah menerapkan fungsi pada pembahasan-pembahasan sebelum ini, yaitu pada fungsi `main`. Fungsi `main` merupakan fungsi yang paling utama pada program Go.
+
+Cara membuat fungsi cukup mudah, yaitu dengan menuliskan keyword `func`, diikuti setelahnya nama fungsi, kurung yang berisikan parameter, dan kurung kurawal untuk membungkus blok kode.
+
+Parameter sendiri adalah variabel yang disisipkan pada saat pemanggilan fungsi.
+
+Silakan lihat dan praktekan kode tentang implementasi fungsi berikut.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func main() {
+ var names = []string{"John", "Wick"}
+ printMessage("halo", names)
+}
+
+func printMessage(message string, arr []string) {
+ var nameString = strings.Join(arr, " ")
+ fmt.Println(message, nameString)
+}
+```
+
+Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`.
+
+Fungsi tersebut dipanggil dalam `main`, dengan disisipkan 2 buah data sebagai parameter, data pertama adalah string `"hallo"` yang ditampung parameter `message`, dan parameter ke 2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`.
+
+Di dalam `printMessage`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`).
+
+
+
+## A.18.2. Fungsi Dengan Return Value / Nilai Balik
+
+Sebuah fungsi bisa dirancang tidak mengembalikan nilai balik (*void*), atau bisa mengembalikan suatu nilai. Fungsi yang memiliki nilai kembalian, harus ditentukan tipe data nilai baliknya pada saat deklarasi.
+
+Program berikut merupakan contoh penerapan fungsi yang memiliki return value.
+
+```go
+package main
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+var randomizer = rand.New(rand.NewSource(time.Now().Unix()))
+
+func main() {
+ var randomValue int
+
+ randomValue = randomWithRange(2, 10)
+ fmt.Println("random number:", randomValue)
+ randomValue = randomWithRange(2, 10)
+ fmt.Println("random number:", randomValue)
+ randomValue = randomWithRange(2, 10)
+ fmt.Println("random number:", randomValue)
+}
+
+func randomWithRange(min, max int) int {
+ var value = randomizer.Int()%(max-min+1) + min
+ return value
+}
+```
+
+Fungsi `randomWithRange` bertugas untuk *generate* angka acak sesuai dengan range yang ditentukan, yang kemudian angka tersebut dijadikan nilai kembalian fungsi.
+
+
+
+Cara menentukan tipe data nilai balik fungsi adalah dengan menuliskan tipe data yang diinginkan setelah kurung parameter. Bisa dilihat pada kode di atas, bahwa `int` merupakan tipe data nilai balik fungsi `randomWithRange`.
+
+```go
+func randomWithRange(min, max int) int
+```
+
+Sedangkan cara untuk mengembalikan nilai itu sendiri adalah dengan menggunakan keyword `return` diikuti data yang ingin dikembalikan. Pada contoh di atas, `return value` artinya nilai variabel `value` dijadikan nilai kembalian fungsi.
+
+Eksekusi keyword `return` akan menjadikan proses dalam blok fungsi berhenti pada saat itu juga. Semua statement setelah keyword tersebut tidak akan dieksekusi.
+
+---
+
+Dari kode di atas mungkin ada beberapa hal yang belum pernah kita lakukan pada pembahasan-pembahasan sebelumnya, kita akan bahas satu-persatu.
+
+## A.18.3. Penggunaan Fungsi `rand.New()`
+
+Fungsi ini digunakan untuk membuat object randomizer, yang dari object tersebut nilai random/acak bisa di-generate. Dalam penerapannya, fungsi `rand.New()` membutuhkan argument yaitu random source seed, yang bisa kita buat lewat statement `rand.NewSource(time.Now().Unix())`.
+
+```go
+var randomizer = rand.New(rand.NewSource(time.Now().Unix()))
+```
+
+> Dalam penggunaan fungsi `rand.NewSource`, argument bisa diisi dengan nilai apapun, salah satunya adalah `time.Now().Unix()`.
+>
+> Lebih detailnya mengenai random dibahas pada chapter [A.39. Random](A-random.html).
+
+Fungsi `rand.New()` berada dalam package `math/rand`, yang harus di-import terlebih dahulu sebelum bisa dimanfaatkan. Package `time` juga perlu di-import karena kita menggunakan fungsi `(time.Now().Unix())` di situ.
+
+## A.18.4. Import Banyak Package
+
+Penulisan keyword `import` untuk banyak package bisa dilakukan dengan dua cara, dengan menuliskannya di tiap package, atau cukup sekali saja, bebas.
+
+```go
+import "fmt"
+import "math/rand"
+import "time"
+
+// atau
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+```
+
+## A.18.5. Deklarasi Parameter Bertipe Data Sama
+
+Khusus untuk fungsi yang tipe data parameternya sama, bisa ditulis dengan gaya yang unik. Tipe datanya dituliskan cukup sekali saja di akhir. Contohnya bisa dilihat pada kode berikut.
+
+```go
+func nameOfFunc(paramA type, paramB type, paramC type) returnType
+func nameOfFunc(paramA, paramB, paramC type) returnType
+
+func randomWithRange(min int, max int) int
+func randomWithRange(min, max int) int
+```
+
+## A.18.6. Penggunaan Keyword `return` Untuk Menghentikan Proses Dalam Fungsi
+
+Selain sebagai penanda nilai balik, keyword `return` juga bisa dimanfaatkan untuk menghentikan proses dalam blok fungsi di mana ia dipakai. Contohnya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ divideNumber(10, 2)
+ divideNumber(4, 0)
+ divideNumber(8, -4)
+}
+
+func divideNumber(m, n int) {
+ if n == 0 {
+ fmt.Printf("invalid divider. %d cannot divided by %d\n", m, n)
+ return
+ }
+
+ var res = m / n
+ fmt.Printf("%d / %d = %d\n", m, n, res)
+}
+```
+
+Fungsi `divideNumber` dirancang tidak memiliki nilai balik. Fungsi ini dibuat untuk membungkus proses pembagian 2 bilangan, lalu menampilkan hasilnya.
+
+Di dalamnya terdapat proses validasi nilai variabel pembagi, jika nilainya adalah 0, maka akan ditampilkan pesan bahwa pembagian tidak bisa dilakukan, lalu proses dihentikan pada saat itu juga (dengan memanfaatkan keyword `return`). Jika nilai pembagi valid, maka proses pembagian diteruskan.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 6070c3e0264d40f25731f2ac96d31c19ae002b28 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:22 +0700
Subject: [PATCH 023/522] New translations a-go-command.md (English)
---
en/content-en/A-go-command.md | 109 ++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
create mode 100644 en/content-en/A-go-command.md
diff --git a/en/content-en/A-go-command.md b/en/content-en/A-go-command.md
new file mode 100644
index 000000000..051c70ecc
--- /dev/null
+++ b/en/content-en/A-go-command.md
@@ -0,0 +1,109 @@
+# A.6. Command
+
+Pengembangan aplikasi Go tak jauh dari hal-hal yang berbau CLI atau *Command Line Interface*. Proses inisialisasi project, kompilasi, testing, eksekusi program, semuanya dilakukan lewat command line.
+
+Go menyediakan command `go`, dan pada chapter ini kita akan mempelajari beberapa di antaranya.
+
+> Pada pembelajaran chapter ini, pembaca tidak harus praktek, cukup pelajari saja untuk tahu. Mengenai praktek sendiri akan dimulai pada chapter selanjutnya, yaitu [A.7. Program Pertama: Hello World](/A-hello-world.html).
+
+## A.6.1. Command `go mod init`
+
+*Command* `go mod init` digunakan untuk inisialisasi project pada Go (menggunakan Go Modules). Untuk nama project bisa menggunakan apapun, tapi umumnya adalah disamakan dengan nama direktori.
+
+Nama project ini penting karena nantinya berpengaruh pada *import path sub packages* yang ada dalam project tersebut.
+
+```
+mkdir
+cd
+go mod init
+```
+
+## A.6.2. Command `go run`
+
+*Command* `go run` digunakan untuk eksekusi file program (file ber-ekstensi `.go`). Cara penggunaannya dengan menuliskan *command* tersebut diikuti argumen nama file.
+
+Berikut adalah contoh penerapan `go run` untuk eksekusi file program `main.go` yang tersimpan di path `project-pertama` yang path tersebut sudah diinisialisasi menggunakan `go mod init`.
+
+```bash
+cd project-pertama
+go run main.go
+```
+
+
+
+*Command* `go run` hanya bisa digunakan pada file yang nama package-nya adalah `main`. Lebih jelasnya dibahas pada chapter selanjutnya ([A.7. Program Pertama: Hello World](/A-hello-world.html)).
+
+Jika ada banyak file yang package-nya `main` dan file-file tersebut berada pada satu direktori level dengan file utama, maka eksekusinya adalah dengan menuliskan semua file sebagai argument *command* `go run`. Contohnya bisa dilihat pada kode berikut.
+
+```bash
+go run main.go library.go
+```
+
+> Lebih jelasnya perihal argument dan flag akan dibahas pada chapter [A.48. Arguments & Flag](/A-command-line-args-flag.html))
+
+## A.6.3. Command `go test`
+
+Go menyediakan package `testing`, berguna untuk keperluan unit test. File yang akan di-test harus memiliki akhiran `_test.go`.
+
+Berikut adalah contoh penggunaan *command* `go test` untuk testing file `main_test.go`.
+
+```bash
+go test main_test.go
+```
+
+
+
+## A.6.4. Command `go build`
+
+*Command* ini digunakan untuk mengkompilasi file program.
+
+Sebenarnya ketika eksekusi program menggunakan `go run`, terjadi proses kompilasi juga. File hasil kompilasi akan disimpan pada folder temporary untuk selanjutnya langsung dieksekusi.
+
+Berbeda dengan `go build`, *command* ini menghasilkan file *executable* atau *binary* pada folder yang sedang aktif. Contohnya bisa dilihat pada kode berikut.
+
+
+
+Pada contoh di atas, project `project-pertama` di-build, menghasilkan file baru pada folder yang sama, yaitu `project-pertama.exe`, yang kemudian dieksekusi. *Default*-nya nama project akan otomatis dijadikan nama *binary*.
+
+Untuk nama executable sendiri bisa diubah menggunakan flag `-o`. Contoh:
+
+```
+go build -o
+go build -o program.exe
+```
+
+> Untuk sistem operasi non-windows, tidak perlu menambahkan akhiran `.exe` pada nama *binary*
+
+## A.6.5. Command `go get`
+
+*Command* `go get` digunakan untuk men-download package. Sebagai contoh saya ingin men-download package Kafka driver untuk Go pada project `project-pertama`.
+
+```bash
+cd project-pertama
+go get github.com/segmentio/kafka-go
+dir
+```
+
+
+
+Pada contoh di atas, `github.com/segmentio/kafka-go` adalah URL package kafka-go. Package yang sudah terunduh tersimpan dalam temporary folder yang ter-link dengan project folder di mana *command* `go get` dieksekusi, menjadikan project tersebut bisa meng-*import* package terunduh.
+
+Untuk mengunduh dependensi versi terbaru, gunakan flag `-u` pada command `go get`, misalnya:
+
+```
+go get -u github.com/segmentio/kafka-go
+```
+
+Command `go get` **harus dijalankan dalam folder project**. Jika dijalankan di-luar project maka akan diunduh ke pada GOPATH.
+
+## A.6.6. Command `go mod tidy`
+
+*Command* `go mod tidy` digunakan untuk memvalidasi dependensi. Jika ada dependensi yang belum ter-download, maka akan otomatis di-download.
+
+## A.6.7. Command `go mod vendor`
+
+Command ini digunakan untuk vendoring. Lebih detailnya akan dibahas di akhir serial chapter A, pada chapter [A.61. Go Vendoring](/A-go-vendoring.html).
+
+---
+
+
From 84930683e0e33d52bb56dcaa4889658d475c2100 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:23 +0700
Subject: [PATCH 024/522] New translations a-go-vendoring.md (English)
---
en/content-en/A-go-vendoring.md | 80 +++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 en/content-en/A-go-vendoring.md
diff --git a/en/content-en/A-go-vendoring.md b/en/content-en/A-go-vendoring.md
new file mode 100644
index 000000000..ec4230926
--- /dev/null
+++ b/en/content-en/A-go-vendoring.md
@@ -0,0 +1,80 @@
+# A.61. Go Vendoring
+
+Pada bagian ini kita akan belajar cara pemanfaatan vendoring untuk menyimpan dependensi di lokal.
+
+## A.61.1. Penjelasan
+
+Vendoring di Go merupakan kapabilitas untuk mengunduh semua dependency atau *3rd party*, untuk disimpan di lokal dalam folder project, dalam folder bernama `vendor`.
+
+Dengan adanya folder tersebut, maka Go tidak akan *lookup* 3rd party ke cache folder, melainkan langsung mempergunakan yang ada dalam folder `vendor`. Jadi tidak perlu download lagi dari internet.
+
+Ok lanjut.
+
+## A.61.2. Praktek Vendoring
+
+Kita akan coba praktekan untuk vendoring sebuah 3rd party bernama [gubrak](https://github.com/novalagung/gubrak/v2).
+
+Buat folder project baru dengan nama `belajar-vendor` dengan isi satu file `main.go`. Lalu go get library gubrak.
+
+```bash
+mkdir belajar-vendor
+cd belajar-vendor
+go mod init belajar-vendor
+go get -u github.com/novalagung/gubrak/v2
+```
+
+Isi `main.go` dengan blok kode berikut, untuk menampilkan angka random dengan range 10-20.
+
+```go
+package main
+
+import (
+ "fmt"
+ gubrak "github.com/novalagung/gubrak/v2"
+)
+
+func main() {
+ fmt.Println(gubrak.RandomInt(10, 20))
+}
+```
+
+Setelah itu jalankan command `go mod vendor` untuk vendoring *3rd party library* yang dipergunakan, dalam contoh ini adlah gubrak.
+
+
+
+Bisa dilihat, sekarang library gubrak *source code*-nya disimpan dalam folder `vendor`. Nah ini juga akan berlaku untuk semua *library* lainnya yg digunakan jika ada.
+
+## A.61.3 Build dan Run Project yang Menerapkan Vendoring
+
+Untuk membuat proses build lookup ke folder vendor, kita tidak perlu melakukan apa-apa, setidaknya jika versi Go yang diinstall adalah 1.14 ke atas. Maka command build maupun run masih sama.
+
+```
+go run main.go
+go build -o executable
+```
+
+Untuk yg menggunakan versi Go di bawah 1.14, penulis sarankan untuk upgrade. Atau bisa gunakan flag `-mod=vendor` untuk memaksa Go lookup ke folder `vendor`.
+
+```
+go run -mod=vendor main.go
+go build -mod=vendor -o executable
+```
+
+## A.61.3. Manfaat Vendoring
+
+Manfaat vendoring adalah pada sisi kompatibilitas dan kestabilan 3rd party. Jadi dengan vendor, misal 3rd party yang kita gunakan di itu ada update yg sifatnya tidak *backward compatible*, maka aplikasi kita tetap aman karena menggunakan yang ada dalam folder `vendor`.
+
+Jika tidak menggunakan vendoring, maka bisa saja saat `go mod tidy` sukses, namun sewaktu build error, karena ada fungsi yg tidak kompatibel lagi misalnya.
+
+Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan saja.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 35082aa04a81ab3d7f9f40b3817cb2b1276397a2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:24 +0700
Subject: [PATCH 025/522] New translations a-golang-generics.md (English)
---
en/content-en/A-golang-generics.md | 283 +++++++++++++++++++++++++++++
1 file changed, 283 insertions(+)
create mode 100644 en/content-en/A-golang-generics.md
diff --git a/en/content-en/A-golang-generics.md b/en/content-en/A-golang-generics.md
new file mode 100644
index 000000000..320f19d5f
--- /dev/null
+++ b/en/content-en/A-golang-generics.md
@@ -0,0 +1,283 @@
+# A.65. Go Generics
+
+Pada chapter ini kita akan belajar tentang Generics di Go.
+
+## A.65.1. Konsep Generic Programming
+
+Generic Programming adalah salah satu metode dalam penulisan kode program, di mana tipe data dalam kode didefinisikan menggunakan tipe data yang tipe pastinya adalah dituliskan belakangan saat kode tersebut di-call atau dieksekusi. Konsep ini sudah cukup umum terutama pada bahasa yang static type.
+
+Di Go, kita punya tipe `interface{}` yang biasa difungsikan sebagai tipe untuk menampung data yang tidak pasti tipe datanya. Generic dan `interface{}` berbeda. Tipe `interface{}` akan membungkus data aslinya atau *underlying value*-nya, dan untuk mengakses data tersebut, kita perlu menerapkan *type assertion*, contohnya `data.(int)`.
+
+Berbeda dibanding `interface{}`, pada penggunaan generic kita perlu mendefinisikan cakupan tipe data yang kompatibel untuk dipakai saat pemanggilan kode, atau bisa juga menggunakan keyword `comparable`, yang artinya tipe data adalah kompatibel dengan tipe apapun.
+
+Ok, mari kita lanjut ke pembahasan yang lebih teknis agar tidak bingung.
+
+## A.65.2. Penerapan Generic pada Fungsi
+
+Mari kita mulai pembelajaran dengan kode di bawah ini:
+
+```go
+package main
+
+import "fmt"
+
+func Sum(numbers []int) int {
+ var total int
+ for _, e := range numbers {
+ total += e
+ }
+ return total
+}
+
+func main() {
+ total1 := Sum([]int{1, 2, 3, 4, 5})
+ fmt.Println("total:", total1)
+}
+```
+
+Pada kode di atas, didefinisikan sebuah fungsi `Sum()` yang tugasnya menghitung total atau *summary* dari data slice numerik yang disisipkan di parameter. Dalam `main()`, kita panggil fungsi tersebut untuk menghitung total dari sejumlah data dengan tipe `[]int`. Saya rasa sampai sini cukup jelas.
+
+Fungsi `Sum()` memiliki satu limitasinya, yaitu hanya bisa digunakan pada data yang tipenya `[]int`, tidak bisa untuk tipe slice numerik lain. Bagaimana jika menggunakan tipe `interface{}`? apakah bisa? bisa saja sebenarnya, tapi pastinya lebih report karena sulit untuk menerapkan *type assertion* kalau tidak tau tipe pasti parameter `numbers` itu apa. Penggunaan `interface{}` perlu dibarengi dengan penerapan [reflection API](/A-reflect.html).
+
+Di sini kita bisa terapkan Generic, kita akan modifikasi fungsi di atas agar bisa menampung tipe data slice numerik lainnya diluar `[]int`.
+
+Ok, sekarang ubah kode fungsi `Sum` menjadi seperti di bawah ini:
+
+```go
+func Sum[V int](numbers []V) V {
+ var total V
+ for _, e := range numbers {
+ total += e
+ }
+ return total
+}
+```
+
+Notasi penulisan di atas mungkin akan cukup asing teruntuk pembaca yang belum pernah menggunakan Generic pada bahasa selain Go. Tidak apa, di sini kita belajar dari awal :-)
+
+Penulisan notasi fungsi dengan Generic kurang lebih sebagai berikut:
+
+```go
+func FuncName[dataType ](params)
+```
+
+Pada kode di atas, tipe data `[]int` kita ganti menjadi tipe data `[]V`, yang di mana tipe `V` dideklarasikan dengan notasi `[V int]`. Tipe data `V` di situ artinya kompatibel atau *comparable* dengan tipe `int`. Bisa diambil kesimpulan kedua fungsi yang telah kita tulis adalah ekuivalen.
+
+```go
+func Sum(numbers []int) int {
+ var total int
+ // ...
+}
+
+func Sum[V int](numbers []V) V {
+ var total V
+ // ...
+}
+```
+
+Ok, sekarang kita sudah mengubah penulisan kode fungsi `Sum` menggunakan Generic, tanpa merubah kegunaan fungsi. Coba jalankan aplikasi untuk mengetes hasilnya.
+
+
+```go
+func Sum[V int](numbers []V) V {
+ var total V
+ for _, e := range numbers {
+ total += e
+ }
+ return total
+}
+
+func main() {
+ total1 := Sum([]int{1, 2, 3, 4, 5})
+ fmt.Println("total: ", total1)
+}
+```
+
+
+
+## A.65.3. Comparable Data Type pada Fungsi Generic
+
+Selanjutnya kita modifikasi lagi fungsi `Sum` agar tipe kompatibel `V` di sini kompatibel dengan tipe numerik lainnya seperti `float64`. Caranya sangat mudah, cukup tambahkan tipe datanya pada statement `V int` dengan delimiter pipe (`|`).
+
+```go
+func Sum[V int | float32 | float64](numbers []V) V {
+ var total V
+ for _, e := range numbers {
+ total += e
+ }
+ return total
+}
+```
+
+Notasi `V int | float32 | float64` artinya tipe `V` adalah kompatibel dengan `int`, `float32`, dan `float64`.
+
+Sekarang coba panggil fungsi tersebut 3 kali dengan 3 parameter berbeda.
+
+```go
+total1 := Sum([]int{1, 2, 3, 4, 5})
+fmt.Println("total:", total1)
+
+total2 := Sum([]float32{2.5, 7.2})
+fmt.Println("total:", total2)
+
+total3 := Sum([]float64{1.23, 6.33, 12.6})
+fmt.Println("total:", total3)
+```
+
+
+
+Nice, hasilnya sesuai harapan. Sampai sini kita sudah paham bagaimana cara pendefinisian tipe kompatibel pada fungsi dan cara pemanfaatannya.
+
+## A.65.4. Tipe Argumen Saat Pemanggilan Fungsi Generic
+
+Ada 2 cara pemanggilan fungsi generic, yang pertama seperti contoh di atas.
+
+```go
+Sum([]int{1, 2, 3, 4, 5})
+Sum([]float32{2.5, 7.2})
+Sum([]float64{1.23, 6.33, 12.6})
+```
+
+Atau bisa juga dengan menuliskan secara eksplisit tipe data kompatibelnya. Seperti contoh berikut:
+
+```go
+Sum[int]([]int{1, 2, 3, 4, 5})
+Sum[float32]([]float32{2.5, 7.2})
+Sum[float64]([]float64{1.23, 6.33, 12.6})
+```
+
+Di case ini (dan banyak case lainnya), tipe data kompatibel tidak perlu dituliskan secara eksplisit karena secara cerdas kompiler bisa mendeteksi tipe yang kompatibel berdasarkan tipe data parameter saat pemanggilan fungsi.
+
+## A.65.5. Keyword `comparable`
+
+Sekarang kita akan belajar kegunaan satu keyword penting, yaitu `comparable`. Keyword tersebut merupakan tipe data yang kompatibel dengan semua tipe yang ada.
+
+Pada kode di atas kita menggunakan `V int | float32 | float64` untuk mendefinisikan tipe yang kompatibel dengan tipe `int`, `float32`, dan `float64`. Jika ingin membuat tipe `V` kompatibel dengan banyak tipe lainnya, tambahkan saja tipe2 yang diinginkan. Atau, jika ingin kompatibel dengan **semua tipe data** maka gunakan `comparable`, penulisannya menjadi `V comparable`.
+
+Ok, mari kita coba terapkan. O iya, sebelum mulai, agar pembaca makin paham perihal fungsi generic, kita siapkan 2 fungsi yang mirip berikut:
+
+```go
+func SumNumbers1(m map[string]int64) int64 {
+ var s int64
+ for _, v := range m {
+ s += v
+ }
+ return s
+}
+
+func SumNumbers2[K comparable, V int64 | float64](m map[K]V) V {
+ var s V
+ for _, v := range m {
+ s += v
+ }
+ return s
+}
+
+func main() {
+ ints := map[string]int64{ "first": 34, "second": 12 }
+ floats := map[string]float64{ "first": 35.98, "second": 26.99 }
+
+ fmt.Printf("Generic Sums with Constraint: %v and %v\n",
+ SumNumbers2(ints),
+ SumNumbers2(floats))
+}
+```
+
+Dua fungsi di atas mirip, tapi memiliki beberapa perbedaan:
+
+1. Penulisan `SumNumbers1` adalah non-generic, sedangkan `SumNumbers2` adalah generic.
+2. Pada `SumNumbers1`, kita menggunakan kombinasi dua tipe data untuk membentuk `map`, yaitu `string` sebagai map key dan `int64` sebagai map value.
+3. Pada `SumNumbers2`, kita breakdown pendefinisian tipe data map menjadi lebih mendetail:
+ - Tipe map key adalah `K` yang tipe datanya kompatibel dengan semua tipe data.
+ - Tipe map value adalah `V` yang tipe datanya kompatibel dengan `int64` dan `float64`.
+ - Yang sebelumnya `map[string]int64` kini menjadi `map[K]V`.
+
+Karena `SumNumbers2` menggunakan generic, maka fungsi ini mendukung sangat banyak tipe data karena menggunakan kombinasi dari tipe `K` yang kompatibel dengan semua tipe; dan tipe `V` yang kompatibel dengan `int64` dan `float64`.
+
+- `map[string]int64`
+- `map[interface{}]int64`
+- `map[string]float64`
+- `map[bool]float64`
+- ... dan banyak tipe lainnya
+
+Jalankan kode, lihat hasilnya.
+
+
+
+## A.65.6. Generic *Type Constraint*
+
+Selanjutnya buat fungsi `SumNumbers3`, isinya kurang lebih sama, hanya saja pada tipe data generic kita tidak menggunakan `V int64 | float64`, yang digunakan adalah `Number` yang merupakan tipe data baru (generic *type constraint*).
+
+```go
+type Number interface {
+ int64 | float64
+}
+
+func SumNumbers3[K comparable, V Number](m map[K]V) V {
+ var s V
+ for _, v := range m {
+ s += v
+ }
+ return s
+}
+```
+
+Cara pendefinisian generic *type constraint* adalah seperti pendefinisan tipe data kustom menggunakan keyword `type`, bedanya adalah di sini `interface{}` dipergunakan sebagai tipe, dan di dalamnya di-embed 2 tipe yang diinginkan untuk menjadi *comparable type*, yaitu `int64` dan `float64`. Dari sini, selanjutnya tipe `Number` bisa dimanfaatkan sebagai tipe data kompatibel dalam generic.
+
+> Perlu diketahui, tipe yang didefinisikan menggunakan *type constraint* ini hanya bisa dimanfaatkan pada generic. Tipe jenis ini tidak bisa digunakan di luar scope kode generic. Sebagai contoh, coba deklarasikan `var s Number` dalam fungsi `main()`, hasilnya akan muncul syntax error.
+
+Ok, sekarang mari ubah pemanggilan fungsi `SumNumbers2` pada main menjadi `SumNumbers3` dan lihat hasilnya, jalan.
+
+## A.65.7. Struct Generic
+
+Generic juga bisa diterapkan pada pendefinisian struct, contohnya seperti berikut:
+
+```go
+type UserModel[T int | float64] struct {
+ Name string
+ Scores []T
+}
+
+func (m *UserModel[int]) SetScoresA(scores []int) {
+ m.Scores = scores
+}
+
+func (m *UserModel[float64]) SetScoresB(scores []float64) {
+ m.Scores = scores
+}
+
+func main() {
+ var m1 UserModel[int]
+ m1.Name = "Noval"
+ m1.Scores = []int{1, 2, 3}
+ fmt.Println("scores:", m1.Scores)
+
+ var m2 UserModel[float64]
+ m2.Name = "Noval"
+ m2.SetScoresB([]float64{10, 11})
+ fmt.Println("scores:", m2.Scores)
+}
+```
+
+Pada penulisan struct, sisipkan notasi generic. Lalu pada deklarasi variabel object, tulis secara eksplisit tipe data untuk variabel kompatibel.
+
+
+
+## A.65.8. Method Generic
+
+Sampai artikel ini ditulis, generic tidak bisa diterapkan pada method (meski bisa diterapkan pada fungsi)
+
+---
+
+Ok, sekian pembahasan mengenai generics. Jika ada update perihal generic API akan penulis update ke chapter ini juga.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 2372fe74d375e2a28a57158d73ab104ff20a776c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:25 +0700
Subject: [PATCH 026/522] New translations a-gopath-dan-workspace.md (English)
---
en/content-en/A-gopath-dan-workspace.md | 44 +++++++++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 en/content-en/A-gopath-dan-workspace.md
diff --git a/en/content-en/A-gopath-dan-workspace.md b/en/content-en/A-gopath-dan-workspace.md
new file mode 100644
index 000000000..e4c9808c4
--- /dev/null
+++ b/en/content-en/A-gopath-dan-workspace.md
@@ -0,0 +1,44 @@
+# A.4. GOPATH Dan Workspace
+
+> PERINGATAN! Setup Go project menggunakan GOPATH kurang dianjurkan untuk Go versi terbaru. Lebih baik gunakan [A.3. Setup Go Modules](/A-setup-go-project-dengan-go-modules.html). Tapi meski demikian, bukan berarti GOPATH tidak berguna sama sekali, jadi silakan ikuti panduan berikut jika mau.
+
+## A.4.1. Variabel `GOPATH`
+
+**GOPATH** adalah variabel yang digunakan oleh Go sebagai rujukan lokasi di mana semua folder project disimpan, kecuali untuk yg diinisialisasi menggunakan Go Modules. GOPATH berisikan 3 buah sub-folder: `src`, `bin`, dan `pkg`.
+
+Project di Go bisa ditempatkan dalam `$GOPATH/src`. Sebagai contoh anda ingin membuat project dengan nama `belajar`, maka **harus** dibuatkan sebuah folder dengan nama `belajar`, ditempatkan dalam `src` (`$GOPATH/src/belajar`).
+
+> Path separator yang digunakan sebagai contoh di buku ini adalah slash `/`. Khusus pengguna Windows, path separator adalah backslash `\`.
+
+## A.4.2. Setup Workspace
+
+Lokasi folder yang akan dijadikan sebagai workspace bisa ditentukan sendiri. Anda bisa menggunakan alamat folder mana saja, bebas, tapi jangan gunakan path tempat di mana Go ter-*install* (tidak boleh sama dengan `GOROOT`). Lokasi tersebut harus didaftarkan dalam path variable dengan nama `GOPATH`. Sebagai contoh, penulis memilih path `$HOME/Documents/go`, maka saya daftarkan alamat tersebut. Caranya:
+
+ - Bagi pengguna **Windows**, tambahkan path folder tersebut ke **path variable** dengan nama `GOPATH`. Setelah variabel terdaftar, cek apakah path sudah terdaftar dengan benar.
+
+ > Sering terjadi `GOPATH` tidak dikenali meskipun variabel sudah didaftarkan. Jika hal seperti ini terjadi, restart CMD, lalu coba lagi.
+
+ - Bagi pengguna Mac OS, export path ke `~/.bash_profile`. Untuk Linux, export ke `~/.bashrc`
+
+ ```bash
+ $ echo "export GOPATH=$HOME/Documents/go" >> ~/.bash_profile
+ $ source ~/.bash_profile
+ ```
+
+ Cek apakah path sudah terdaftar dengan benar.
+
+ 
+
+Setelah `GOPATH` berhasil dikenali, perlu disiapkan 3 buah sub folder di dalamnya, dengan kriteria sebagai berikut:
+
+ - Folder `src`, adalah path di mana project Go disimpan
+ - Folder `pkg`, berisi file hasil kompilasi
+ - Folder `bin`, berisi file executable hasil build
+
+
+
+Struktur di atas merupakan struktur standar workspace Go. Jadi pastikan penamaan dan hirarki folder adalah sama.
+
+---
+
+
From 552a8e30d92c8a39ddd0cd805260734619bc83aa Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:26 +0700
Subject: [PATCH 027/522] New translations a-goroutine.md (English)
---
en/content-en/A-goroutine.md | 96 ++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 en/content-en/A-goroutine.md
diff --git a/en/content-en/A-goroutine.md b/en/content-en/A-goroutine.md
new file mode 100644
index 000000000..5b0941937
--- /dev/null
+++ b/en/content-en/A-goroutine.md
@@ -0,0 +1,96 @@
+# A.30. Goroutine
+
+Goroutine mirip dengan thread, tapi sebenarnya bukan. Sebuah *native thread* bisa berisikan sangat banyak goroutine. Mungkin lebih pas kalau goroutine disebut sebagai **mini thread**. Goroutine sangat ringan, hanya dibutuhkan sekitar **2kB** memori saja untuk satu buah goroutine. Eksekusi goroutine bersifat *asynchronous*, menjadikannya tidak saling tunggu dengan goroutine lain.
+
+> Karena goroutine sangat ringan, maka eksekusi banyak goroutine bukan masalah. Akan tetapi jika jumlah goroutine sangat banyak sekali (contoh 1 juta goroutine dijalankan pada komputer dengan RAM terbatas), memang proses akan jauh lebih cepat selesai, tapi memory/RAM pasti bengkak.
+
+Goroutine merupakan salah satu bagian paling penting dalam *concurrent programming* di Go. Salah satu yang membuat goroutine sangat istimewa adalah eksekusi-nya dijalankan di multi core processor. Kita bisa tentukan berapa banyak core yang aktif, makin banyak akan makin cepat.
+
+Mulai chapter **A.29** ini hingga **A.34**, lalu dilanjut **A.56** dan **A.57**, kita akan membahas tentang fitur-fitur yang disediakan Go untuk kebutuhan *concurrent programming*.
+
+> Concurrency atau konkurensi berbeda dengan paralel. Paralel adalah eksekusi banyak proses secara bersamaan. Sedangkan konkurensi adalah komposisi dari sebuah proses. Konkurensi merupakan struktur, sedangkan paralel adalah bagaimana eksekusinya berlangsung.
+
+## A.30.1. Penerapan Goroutine
+
+Untuk menerapkan goroutine, proses yang akan dieksekusi sebagai goroutine harus dibungkus ke dalam sebuah fungsi. Pada saat pemanggilan fungsi tersebut, ditambahkan keyword `go` di depannya, dengan itu goroutine baru akan dibuat dengan tugas adalah menjalankan proses yang ada dalam fungsi tersebut.
+
+Berikut merupakan contoh implementasi sederhana tentang goroutine. Program di bawah ini menampilkan 10 baris teks, 5 dieksekusi dengan cara biasa, dan 5 lainnya dieksekusi sebagai goroutine baru.
+
+```go
+package main
+
+import "fmt"
+import "runtime"
+
+func print(till int, message string) {
+ for i := 0; i < till; i++ {
+ fmt.Println((i + 1), message)
+ }
+}
+
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ go print(5, "halo")
+ print(5, "apa kabar")
+
+ var input string
+ fmt.Scanln(&input)
+}
+```
+
+Pada kode di atas, Fungsi `runtime.GOMAXPROCS(n)` digunakan untuk menentukan jumlah core yang diaktifkan untuk eksekusi program.
+
+Pembuatan goroutine baru ditandai dengan keyword `go`. Contohnya pada statement `go print(5, "halo")`, di situ fungsi `print()` dieksekusi sebagai goroutine baru.
+
+Fungsi `fmt.Scanln()` mengakibatkan proses jalannya aplikasi berhenti di baris itu (**blocking**) hingga user menekan tombol enter. Hal ini perlu dilakukan karena ada kemungkinan waktu selesainya eksekusi goroutine `print()` lebih lama dibanding waktu selesainya goroutine utama `main()`, mengingat bahwa keduanya sama-sama asnychronous. Jika itu terjadi, goroutine yang belum selesai secara paksa dihentikan prosesnya karena goroutine utama sudah selesai dijalankan.
+
+
+
+Bisa dilihat di output, tulisan `"halo"` dan `"apa kabar"` bermunculan selang-seling. Ini disebabkan karena statement `print(5, "halo")` dijalankan sebagai goroutine baru, menjadikannya tidak saling tunggu dengan `print(5, "apa kabar")`.
+
+Pada gambar di atas, program dieksekusi 2 kali. Hasil eksekusi pertama berbeda dengan kedua, penyebabnya adalah karena kita menggunakan 2 prosesor. Goroutine mana yang dieksekusi terlebih dahulu tergantung kedua prosesor tersebut.
+
+---
+
+Berikut adalah penjelasan tambahan tentang beberapa fungsi yang baru kita pelajari di atas.
+
+## A.30.1.1. Penggunaan Fungsi `runtime.GOMAXPROCS()`
+
+Fungsi ini digunakan untuk menentukan jumlah core atau processor yang digunakan dalam eksekusi program.
+
+Jumlah yang diinputkan secara otomatis akan disesuaikan dengan jumlah asli *logical processor* yang ada. Jika jumlahnya lebih, maka dianggap menggunakan sejumlah prosesor yang ada.
+
+## A.30.1.2. Penggunaan Fungsi `fmt.Scanln()`
+
+Fungsi ini akan meng-capture semua karakter sebelum user menekan tombol enter, lalu menyimpannya pada variabel.
+
+```go
+func Scanln(a ...interface{}) (n int, err error)
+```
+
+Kode di atas merupakan skema fungsi `fmt.Scanln()`. Fungsi tersebut bisa menampung parameter bertipe `interface{}` berjumlah tak terbatas. Tiap parameter akan menampung karakter-karakter inputan user yang sudah dipisah dengan tanda spasi. Agar lebih jelas, silakan perhatikan contoh berikut.
+
+```go
+var s1, s2, s3 string
+fmt.Scanln(&s1, &s2, &s3)
+
+// user inputs: "trafalgar d law"
+
+fmt.Println(s1) // trafalgar
+fmt.Println(s2) // d
+fmt.Println(s3) // law
+```
+
+Bisa dilihat pada kode di atas, untuk menampung inputan text `trafalgar d law`, dibutuhkan 3 buah variabel. Juga perlu diperhatikan bahwa yang disisipkan sebagai parameter pada pemanggilan fungsi `fmt.Scanln()` adalah referensi variabel, bukan nilai aslinya.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 48a19154ac7dca539c0b01e92a086acc07678243 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:26 +0700
Subject: [PATCH 028/522] New translations a-hash-sha1.md (English)
---
en/content-en/A-hash-sha1.md | 107 +++++++++++++++++++++++++++++++++++
1 file changed, 107 insertions(+)
create mode 100644 en/content-en/A-hash-sha1.md
diff --git a/en/content-en/A-hash-sha1.md b/en/content-en/A-hash-sha1.md
new file mode 100644
index 000000000..19db57e11
--- /dev/null
+++ b/en/content-en/A-hash-sha1.md
@@ -0,0 +1,107 @@
+# A.47. Hash SHA1
+
+Hash adalah algoritma enkripsi untuk mengubah text menjadi deretan karakter acak. Jumlah karakter hasil hash selalu sama. Hash termasuk *one-way encryption*, membuat hasil dari hash tidak bisa dikembalikan ke text asli.
+
+SHA1 atau **Secure Hash Algorithm 1** merupakan salah satu algoritma hashing yang sering digunakan untuk enkripsi data. Hasil dari sha1 adalah data dengan lebar **20 byte** atau **160 bit**, biasa ditampilkan dalam bentuk bilangan heksadesimal 40 digit.
+
+Pada chapter ini kita akan belajar tentang pemanfaatan sha1 dan teknik salting dalam hash.
+
+## A.47.1. Penerapan Hash SHA1
+
+Go menyediakan package `crypto/sha1`, berisikan library untuk keperluan *hashing*. Cara penerapannya cukup mudah, contohnya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "crypto/sha1"
+import "fmt"
+
+func main() {
+ var text = "this is secret"
+ var sha = sha1.New()
+ sha.Write([]byte(text))
+ var encrypted = sha.Sum(nil)
+ var encryptedString = fmt.Sprintf("%x", encrypted)
+
+ fmt.Println(encryptedString)
+ // f4ebfd7a42d9a43a536e2bed9ee4974abf8f8dc8
+}
+```
+
+Variabel hasil dari `sha1.New()` adalah objek bertipe `hash.Hash`, memiliki dua buah method `Write()` dan `Sum()`.
+
+ - Method `Write()` digunakan untuk menge-set data yang akan di-hash. Data harus dalam bentuk `[]byte`.
+ - Method `Sum()` digunakan untuk eksekusi proses hash, menghasilkan data yang sudah di-hash dalam bentuk `[]byte`. Method ini membutuhkan sebuah parameter, isi dengan nil.
+
+Untuk mengambil bentuk heksadesimal string dari data yang sudah di-hash, bisa memanfaatkan fungsi `fmt.Sprintf` dengan layout format `%x`.
+
+
+
+## A.47.2. Metode Salting Pada Hash SHA1
+
+Salt dalam konteks kriptografi adalah data acak yang digabungkan pada data asli sebelum proses hash dilakukan.
+
+Hash merupakan enkripsi satu arah dengan lebar data yang sudah pasti, sangat mungkin sekali kalau hasil hash untuk beberapa data adalah sama. Di sinilah kegunaan **salt**, teknik ini berguna untuk mencegah serangan menggunakan metode pencocokan data-data yang hasil hash-nya adalah sama *(dictionary attack)*.
+
+Langsung saja kita praktekkan. Pertama import package yang dibutuhkan. Lalu buat fungsi untuk hash menggunakan salt dari waktu sekarang.
+
+```go
+package main
+
+import "crypto/sha1"
+import "fmt"
+import "time"
+
+func doHashUsingSalt(text string) (string, string) {
+ var salt = fmt.Sprintf("%d", time.Now().UnixNano())
+ var saltedText = fmt.Sprintf("text: '%s', salt: %s", text, salt)
+ fmt.Println(saltedText)
+ var sha = sha1.New()
+ sha.Write([]byte(saltedText))
+ var encrypted = sha.Sum(nil)
+
+ return fmt.Sprintf("%x", encrypted), salt
+}
+```
+
+Salt yang digunakan adalah hasil dari ekspresi `time.Now().UnixNano()`. Hasilnya akan selalu unik setiap detiknya, karena scope terendah waktu pada fungsi tersebut adalah *nano second* atau nano detik.
+
+Selanjutnya test fungsi yang telah dibuat beberapa kali.
+
+```go
+func main() {
+ var text = "this is secret"
+ fmt.Printf("original : %s\n\n", text)
+
+ var hashed1, salt1 = doHashUsingSalt(text)
+ fmt.Printf("hashed 1 : %s\n\n", hashed1)
+ // 929fd8b1e58afca1ebbe30beac3b84e63882ee1a
+
+ var hashed2, salt2 = doHashUsingSalt(text)
+ fmt.Printf("hashed 2 : %s\n\n", hashed2)
+ // cda603d95286f0aece4b3e1749abe7128a4eed78
+
+ var hashed3, salt3 = doHashUsingSalt(text)
+ fmt.Printf("hashed 3 : %s\n\n", hashed3)
+ // 9e2b514bca911cb76f7630da50a99d4f4bb200b4
+
+ _, _, _ = salt1, salt2, salt3
+}
+```
+
+Hasil ekripsi fungsi `doHashUsingSalt` akan selalu beda, karena salt yang digunakan adalah waktu.
+
+
+
+Metode ini sering dipakai untuk enkripsi password user. Salt dan data hasil hash harus disimpan pada database, karena digunakan dalam pencocokan password setiap user melakukan login.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From da0c32f86b30b4d747e95d56c857891e781cb375 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:27 +0700
Subject: [PATCH 029/522] New translations a-hello-world.md (English)
---
en/content-en/A-hello-world.md | 134 +++++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
create mode 100644 en/content-en/A-hello-world.md
diff --git a/en/content-en/A-hello-world.md b/en/content-en/A-hello-world.md
new file mode 100644
index 000000000..9bfde32ba
--- /dev/null
+++ b/en/content-en/A-hello-world.md
@@ -0,0 +1,134 @@
+# A.7. Program Pertama: Hello World
+
+Semua persiapan sudah selesai, saatnya masuk pada sesi programming. Program pertama yang akan kita buat adalah aplikasi kecil yang menampilkan text **Hello world**.
+
+Pada chapter ini akan dijelaskan secara komprehensif *step-by-step* mulai dari awal. Mulai dari pembuatan project, pembuatan file program, sesi penulisan kode (coding), hingga eksekusi program.
+
+## A.7.1. Inisialisasi Project
+
+Buat direktori bernama `hello-world` bebas ditempatkan di mana. Lalu via CLI, masuk ke direktori tersebut dan jalankan *command* untuk inisialisasi project.
+
+```
+mkdir hello-world
+cd hello-world
+go mod init hello-world
+```
+
+
+
+## A.7.2. Load Project Folder ke Editor
+
+Buka editor, di sini penulis menggunakan VSCode. Cari menu untuk menambahkan project, lalu pilih project folder `hello-world`. Untuk beberapa jenis editor, cara load project bisa cukup dengan klik-drag folder tersebut ke editor.
+
+
+
+## A.7.3. Menyiapkan File Program
+
+File program di sini maksudnya adalah file yang isinya *source code* Go. File ini berekstensi `.go`.
+
+Di dalam project yang telah dibuat, siapkan sebuah file dengan nama bebas, yang jelas harus ber-ekstensi `.go`. Pada contoh ini saya menggunakan nama file `main.go`.
+
+Pembuatan file program bisa dilakukan lewat CLI atau browser, atau juga lewat editor. Pastikan file dibuat dalam project folder ya.
+
+
+
+## A.7.4. Program Pertama: Hello Word
+
+Setelah project folder dan file program sudah siap, saatnya untuk *programming*.
+
+Di bawah ini merupakan contoh kode program sederhana untuk memunculkan text **Hello world** ke layar output command prompt. Silakan salin kode berikut ke file program yang telah dibuat. Sebisa mungkin jangan copy paste. Biasakan untuk menulis dari awal, agar cepat terbiasa dan familiar dengan Go.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello world")
+}
+```
+
+Setelah kode disalin, buka terminal (atau CMD bagi pengguna Windows), lalu masuk ke direktori proyek, kemudian jalankan program menggunakan perintah `go run`.
+
+```bash
+cd hello-world
+go run main.go
+```
+
+Hasilnya, muncul tulisan **hello world** di layar console.
+
+
+
+Selamat! Anda telah berhasil membuat program Go!
+
+---
+
+Berikut merupakan pembahasan untuk tiap baris kode yang sudah ditulis di atas.
+
+## A.7.5. Penggunaan Keyword `package`
+
+Setiap file program harus memiliki **package**. Setiap project harus ada minimal satu file dengan nama *package* `main`. File yang ber-*package* `main`, akan dieksekusi pertama kali ketika program di jalankan.
+
+Cara menentukan *package* dengan menggunakan keyword `package`, berikut adalah contoh penulisannya.
+
+```go
+package
+package main
+```
+
+## A.7.6. Penggunaan Keyword `import`
+
+Keyword `import` digunakan untuk meng-*import* atau memasukan *package* lain ke dalam file program, agar isi dari package yang di-*import* bisa dimanfaatkan.
+
+*Package* `fmt` merupakan salah satu *package* bawaan yang disediakan oleh Go, isinya banyak fungsi untuk keperluan **I/O** yang berhubungan dengan text.
+
+Berikut adalah skema penulisan keyword `import`:
+
+```go
+import ""
+import "fmt"
+```
+
+## A.7.7. Penggunaan Fungsi `main()`
+
+Dalam sebuah proyek harus ada file program yang di dalamnya berisi sebuah fungsi bernama `main()`. Fungsi tersebut harus berada di file yang package-nya bernama `main`.
+
+Fungsi `main()` adalah yang dipanggil pertama kali pada saat eksekusi program. Contoh penulisan fungsi `main`:
+
+```go
+func main() {
+
+}
+```
+
+## A.7.8. Penggunaan Fungsi `fmt.Println()`
+
+Fungsi `fmt.Println()` digunakan untuk memunculkan text ke layar (pada konteks ini, terminal atau CMD). Di program pertama yang telah kita buat, fungsi ini memunculkan tulisan **Hello world**.
+
+Skema penulisan keyword `fmt.Println()` bisa dilihat pada contoh berikut.
+
+```go
+fmt.Println("")
+fmt.Println("Hello world")
+```
+
+Fungsi `fmt.Println()` berada dalam package `fmt`, maka untuk menggunakannya perlu package tersebut untuk di-import terlebih dahulu.
+
+Fungsi `fmt.Println()` dapat menampung parameter yang tidak terbatas jumlahnya. Semua data parameter akan dimunculkan dengan pemisah tanda spasi.
+
+```go
+fmt.Println("Hello", "world!", "how", "are", "you")
+```
+
+Contoh statement di atas akan menghasilkan output: **Hello world! how are you**.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 8abe8dea84a255e7ce9956fcdfaf3b6b865b6bd2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:28 +0700
Subject: [PATCH 030/522] New translations a-instalasi-editor.md (English)
---
en/content-en/A-instalasi-editor.md | 50 +++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 en/content-en/A-instalasi-editor.md
diff --git a/en/content-en/A-instalasi-editor.md b/en/content-en/A-instalasi-editor.md
new file mode 100644
index 000000000..89aebec69
--- /dev/null
+++ b/en/content-en/A-instalasi-editor.md
@@ -0,0 +1,50 @@
+# A.5. Instalasi Editor
+
+Proses pembuatan aplikasi menggunakan Go akan lebih maksimal jika didukung oleh editor atau **IDE** yang pas. Ada cukup banyak pilihan bagus yang bisa dipertimbangkan, di antaranya: Brackets, JetBrains GoLand, Netbeans, Atom, Visual Studio Code, Sublime Text, dan lainnya.
+
+Penulis sarankan untuk memilih editor yang paling nyaman digunakan, preferensi masing-masing pastinya berbeda. Penulis sendiri lebih sering menggunakan **Visual Studio Code**. Editor ini sangat ringan, mudah didapat, dan memiliki ekstensi yang bagus untuk bahasa Go. Jika pembaca ingin menggunakan editor yang sama, maka silakan melanjutkan panduan berikut.
+
+Pada chapter ini akan dijelaskan bagaimana cara instalasi editor Visual Studio Code.
+
+## A.5.1. Instalasi Editor Visual Studio Code
+
+ 1. Download Visual Studio Code di [https://code.visualstudio.com/Download](https://code.visualstudio.com/Download), pilih sesuai dengan sistem operasi yang digunakan.
+ 2. Jalankan *installer*.
+ 3. Setelah selesai, jalankan editornya.
+
+
+
+## A.5.2. Instalasi Extensi Go
+
+Dengan meng-*install* Go Extension pada VSCode, maka development akan menjadi lebih menyenangkan dan mudah. Banyak benefit yang didapat dari ekstensi ini, beberapa di antaranya adalah integrasi dengan kompiler Go, auto lint on save, testing with coverage, fasilitas debugging with breakpoints, dan lainnya.
+
+Cara instalasi ekstensi sendiri cukup mudah, klik `View -> Extension` atau klik ikon *Extension Marketplace* di sebelah kiri (silakan lihat gambar berikut, deretan button paling kiri yang dilingkari merah). Setelah itu ketikan **Go** pada inputan search, silakan install ekstensi Go buatan GO Team at Google, biasanya muncul paling atas sendiri.
+
+
+
+## A.5.3. Setup Editorconfig
+
+[Editorconfig](https://editorconfig.org/) membantu kita supaya *coding style* menjadi konsisten untuk dibaca oleh banyak developer, dan juga ketika dimuat pada berbagai macam **IDE**. Instalasinya di VSCode cukup mudah, cari saja *extension*-nya kemudian klik *install* seperti pada gambar berikut.
+
+
+
+Editorconfig pada sebuah proyek (biasanya berada di root direktori proyek tersebut) berupa konfigurasi format file `.editorconfig` yang berisi definisi style penulisan yang menyesuaikan dengan standar penulisan masing-masing bahasa pemrograman. Misalnya untuk [*style guide* **GO**](https://golang.org/doc/effective_go.html) kita bisa mulai dengan menggunakan konfigurasi sederhana sebagai berikut:
+
+```
+root = true
+
+[*]
+insert_final_newline = true
+charset = utf-8
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+
+[{Makefile,go.mod,go.sum,*.go}]
+indent_style = tab
+indent_size = 8
+```
+
+---
+
+
From a2e6d9883e446bce3dfa5aea3104c43ec6082f3d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:29 +0700
Subject: [PATCH 031/522] New translations a-interface-kosong.md (English)
---
en/content-en/A-interface-kosong.md | 157 ++++++++++++++++++++++++++++
1 file changed, 157 insertions(+)
create mode 100644 en/content-en/A-interface-kosong.md
diff --git a/en/content-en/A-interface-kosong.md b/en/content-en/A-interface-kosong.md
new file mode 100644
index 000000000..519cbce00
--- /dev/null
+++ b/en/content-en/A-interface-kosong.md
@@ -0,0 +1,157 @@
+# A.28. Interface Kosong (Any)
+
+Interface kosong atau *empty interface* yang dinotasikan dengan `interface{}` atau `any`, merupakan tipe data yang sangat spesial. Variabel bertipe ini bisa menampung segala jenis data, bahkan array, pointer, apapun. Tipe data dengan konsep ini biasa disebut dengan **dynamic typing**.
+
+## A.28.1. Penggunaan `interface{}`
+
+`interface{}` merupakan tipe data, sehingga cara penggunaannya sama seperti pada tipe data lainnya, hanya saja nilai yang diisikan bisa apa saja. Contoh:
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var secret interface{}
+
+ secret = "ethan hunt"
+ fmt.Println(secret)
+
+ secret = []string{"apple", "manggo", "banana"}
+ fmt.Println(secret)
+
+ secret = 12.4
+ fmt.Println(secret)
+}
+```
+
+Keyword `interface` seperti yang kita tau, digunakan untuk pembuatan interface. Tetapi ketika ditambahkan kurung kurawal (`{}`) di belakang-nya (menjadi `interface{}`), maka kegunaannya akan berubah, yaitu sebagai tipe data.
+
+
+
+Agar tidak bingung, coba perhatikan kode berikut.
+
+```go
+var data map[string]interface{}
+
+data = map[string]interface{}{
+ "name": "ethan hunt",
+ "grade": 2,
+ "breakfast": []string{"apple", "manggo", "banana"},
+}
+```
+
+Pada kode di atas, disiapkan variabel `data` dengan tipe `map[string]interface{}`, yaitu sebuah koleksi dengan key bertipe `string` dan nilai bertipe interface kosong `interface{}`.
+
+Kemudian variabel tersebut di-inisialisasi, ditambahkan lagi kurung kurawal setelah keyword deklarasi untuk kebutuhan pengisian data, `map[string]interface{}{ /* data */ }`.
+
+Dari situ terlihat bahwa `interface{}` bukanlah sebuah objek, melainkan tipe data.
+
+## A.28.2. Type Alias `Any`
+
+Tipe `any` merupakan alias dari `interface{}`, keduanya adalah sama.
+
+```go
+var data map[string]any
+
+data = map[string]any{
+ "name": "ethan hunt",
+ "grade": 2,
+ "breakfast": []string{"apple", "manggo", "banana"},
+}
+```
+
+## A.28.3. Casting Variabel Interface Kosong
+
+Variabel bertipe `interface{}` bisa ditampilkan ke layar sebagai `string` dengan memanfaatkan fungsi print, seperti `fmt.Println()`. Tapi perlu diketahui bahwa nilai yang dimunculkan tersebut bukanlah nilai asli, melainkan bentuk string dari nilai aslinya.
+
+Hal ini penting diketahui, karena untuk melakukan operasi yang membutuhkan nilai asli pada variabel yang bertipe `interface{}`, diperlukan casting ke tipe aslinya. Contoh seperti pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func main() {
+ var secret interface{}
+
+ secret = 2
+ var number = secret.(int) * 10
+ fmt.Println(secret, "multiplied by 10 is :", number)
+
+ secret = []string{"apple", "manggo", "banana"}
+ var gruits = strings.Join(secret.([]string), ", ")
+ fmt.Println(gruits, "is my favorite fruits")
+}
+```
+
+Pertama, variabel `secret` menampung nilai bertipe numerik. Ada kebutuhan untuk mengalikan nilai yang ditampung variabel tersebut dengan angka `10`. Maka perlu dilakukan casting ke tipe aslinya, yaitu `int`, setelahnya barulah nilai bisa dioperasikan, yaitu `secret.(int) * 10`.
+
+Pada contoh kedua, `secret` berisikan array string. Kita memerlukan string tersebut untuk digabungkan dengan pemisah tanda koma. Maka perlu di-casting ke `[]string` terlebih dahulu sebelum bisa digunakan di `strings.Join()`, contohnya pada `strings.Join(secret.([]string), ", ")`.
+
+
+
+Teknik casting pada interface disebut dengan **type assertions**.
+
+## A.28.4. Casting Variabel Interface Kosong Ke Objek Pointer
+
+Variabel `interface{}` bisa menyimpan data apa saja, termasuk data objek, pointer, ataupun gabungan keduanya. Di bawah ini merupakan contoh penerapan interface untuk menampung data objek pointer.
+
+```go
+type person struct {
+ name string
+ age int
+}
+
+var secret interface{} = &person{name: "wick", age: 27}
+var name = secret.(*person).name
+fmt.Println(name)
+```
+
+Variabel `secret` dideklarasikan bertipe `interface{}` menampung referensi objek cetakan struct `person`. Cara casting dari `interface{}` ke struct pointer adalah dengan menuliskan nama struct-nya dan ditambahkan tanda asterisk (`*`) di awal, contohnya seperti `secret.(*person)`. Setelah itu barulah nilai asli bisa diakses.
+
+
+
+## A.28.5. Kombinasi Slice, `map`, dan `interface{}`
+
+Tipe `[]map[string]interface{}` adalah salah satu tipe yang paling sering digunakan (menurut saya), karena tipe data tersebut bisa menjadi alternatif tipe slice struct.
+
+Pada contoh berikut, variabel `person` dideklarasikan berisi data slice `map` berisikan 2 item dengan key adalah `name` dan `age`.
+
+```go
+var person = []map[string]interface{}{
+ {"name": "Wick", "age": 23},
+ {"name": "Ethan", "age": 23},
+ {"name": "Bourne", "age": 22},
+}
+
+for _, each := range person {
+ fmt.Println(each["name"], "age is", each["age"])
+}
+```
+
+Dengan memanfaatkan slice dan `interface{}`, kita bisa membuat data array yang isinya adalah bisa apa saja. Silakan perhatikan contoh berikut.
+
+```go
+var fruits = []interface{}{
+ map[string]interface{}{"name": "strawberry", "total": 10},
+ []string{"manggo", "pineapple", "papaya"},
+ "orange",
+}
+
+for _, each := range fruits {
+ fmt.Println(each)
+}
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 041272312c4fbda57ceb8cbf8fba36e80f239493 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:30 +0700
Subject: [PATCH 032/522] New translations a-interface.md (English)
---
en/content-en/A-interface.md | 186 +++++++++++++++++++++++++++++++++++
1 file changed, 186 insertions(+)
create mode 100644 en/content-en/A-interface.md
diff --git a/en/content-en/A-interface.md b/en/content-en/A-interface.md
new file mode 100644
index 000000000..b98b17d53
--- /dev/null
+++ b/en/content-en/A-interface.md
@@ -0,0 +1,186 @@
+# A.27. Interface
+
+Interface adalah kumpulan definisi method yang tidak memiliki isi (hanya definisi saja), yang dibungkus dengan nama tertentu.
+
+Interface merupakan tipe data. Nilai objek bertipe interface zero value-nya adalah `nil`. Interface mulai bisa digunakan jika sudah ada isinya, yaitu objek konkret yang memiliki definisi method minimal sama dengan yang ada di interface-nya.
+
+## A.27.1. Penerapan Interface
+
+Yang pertama perlu dilakukan untuk menerapkan interface adalah menyiapkan interface beserta definisi method nya. Keyword `type` dan `interface` digunakan untuk pendefinisian interface.
+
+```go
+package main
+
+import "fmt"
+import "math"
+
+type hitung interface {
+ luas() float64
+ keliling() float64
+}
+```
+
+Pada kode di atas, interface `hitung` memiliki 2 definisi method, `luas()` dan `keliling()`. Interface ini nantinya digunakan sebagai tipe data pada variabel, di mana variabel tersebut akan menampung objek bangun datar hasil dari struct yang akan kita buat.
+
+Dengan memanfaatkan interface `hitung`, perhitungan luas dan keliling bangun datar bisa dilakukan, tanpa perlu tahu jenis bangun datarnya sendiri itu apa.
+
+Siapkan struct bangun datar `lingkaran`, struct ini memiliki method yang beberapa di antaranya terdefinisi di interface `hitung`.
+
+```go
+type lingkaran struct {
+ diameter float64
+}
+
+func (l lingkaran) jariJari() float64 {
+ return l.diameter / 2
+}
+
+func (l lingkaran) luas() float64 {
+ return math.Pi * math.Pow(l.jariJari(), 2)
+}
+
+func (l lingkaran) keliling() float64 {
+ return math.Pi * l.diameter
+}
+```
+
+Struct `lingkaran` di atas memiliki tiga method, `jariJari()`, `luas()`, dan `keliling()`.
+
+Selanjutnya, siapkan struct bangun datar `persegi`.
+
+```go
+type persegi struct {
+ sisi float64
+}
+
+func (p persegi) luas() float64 {
+ return math.Pow(p.sisi, 2)
+}
+
+func (p persegi) keliling() float64 {
+ return p.sisi * 4
+}
+```
+
+Perbedaan struct `persegi` dengan `lingkaran` terletak pada method `jariJari()`. Struct `persegi` tidak memiliki method tersebut. Tetapi meski demikian, variabel objek hasil cetakan 2 struct ini akan tetap bisa ditampung oleh variabel cetakan interface `hitung`, karena dua method yang ter-definisi di interface tersebut juga ada pada struct `persegi` dan `lingkaran`, yaitu `luas()` dan `keliling()`.
+
+Buat implementasi perhitungan di `main`.
+
+```go
+func main() {
+ var bangunDatar hitung
+
+ bangunDatar = persegi{10.0}
+ fmt.Println("===== persegi")
+ fmt.Println("luas :", bangunDatar.luas())
+ fmt.Println("keliling :", bangunDatar.keliling())
+
+ bangunDatar = lingkaran{14.0}
+ fmt.Println("===== lingkaran")
+ fmt.Println("luas :", bangunDatar.luas())
+ fmt.Println("keliling :", bangunDatar.keliling())
+ fmt.Println("jari-jari :", bangunDatar.(lingkaran).jariJari())
+}
+```
+
+Perhatikan kode di atas. Variabel objek `bangunDatar` bertipe interface `hitung`. Variabel tersebut digunakan untuk menampung objek konkrit buatan struct `lingkaran` dan `persegi`.
+
+Dari variabel tersebut, method `luas()` dan `keliling()` diakses. Secara otomatis Golang akan mengarahkan pemanggilan method pada interface ke method asli milik struct yang bersangkutan.
+
+
+
+Method `jariJari()` pada struct `lingkaran` tidak akan bisa diakses karena tidak terdefinisi dalam interface `hitung`. Pengaksesannya dengan paksa akan menyebabkan error.
+
+Untuk mengakses method yang tidak ter-definisi di interface, variabel-nya harus di-casting terlebih dahulu ke tipe asli variabel konkritnya (pada kasus ini tipenya `lingkaran`), setelahnya method akan bisa diakses.
+
+Cara casting objek interface sedikit unik, yaitu dengan menuliskan nama tipe tujuan dalam kurung, ditempatkan setelah nama interface dengan menggunakan notasi titik (seperti cara mengakses property, hanya saja ada tanda kurung nya). Contohnya bisa dilihat di kode berikut. Statement `bangunDatar.(lingkaran)` adalah contoh casting pada objek interface.
+
+```go
+var bangunDatar hitung = lingkaran{14.0}
+var bangunLingkaran lingkaran = bangunDatar.(lingkaran)
+
+bangunLingkaran.jariJari()
+```
+
+Perlu diketahui juga, jika ada interface yang menampung objek konkrit di mana struct-nya tidak memiliki salah satu method yang terdefinisi di interface, error juga akan muncul. Intinya kembali ke aturan awal, variabel interface hanya bisa menampung objek yang minimal memiliki semua method yang terdefinisi di interface-nya.
+
+## A.27.2. Embedded Interface
+
+Interface bisa di-embed ke interface lain, sama seperti struct. Cara penerapannya juga sama, cukup dengan menuliskan nama interface yang ingin di-embed ke dalam interface tujuan.
+
+Pada contoh berikut, disiapkan interface bernama `hitung2d` dan `hitung3d`. Kedua interface tersebut kemudian di-embed ke interface baru bernama `hitung`.
+
+```go
+package main
+
+import "fmt"
+import "math"
+
+type hitung2d interface {
+ luas() float64
+ keliling() float64
+}
+
+type hitung3d interface {
+ volume() float64
+}
+
+type hitung interface {
+ hitung2d
+ hitung3d
+}
+```
+
+Interface `hitung2d` berisikan method untuk kalkulasi luas dan keliling, sedang `hitung3d` berisikan method untuk mencari volume bidang. Kedua interface tersebut diturunkan di interface `hitung`, menjadikannya memiliki kemampuan untuk menghitung luas, keliling, dan volume.
+
+Next, siapkan struct baru bernama `kubus` yang memiliki method `luas()`, `keliling()`, dan `volume()`.
+
+```go
+type kubus struct {
+ sisi float64
+}
+
+func (k *kubus) volume() float64 {
+ return math.Pow(k.sisi, 3)
+}
+
+func (k *kubus) luas() float64 {
+ return math.Pow(k.sisi, 2) * 6
+}
+
+func (k *kubus) keliling() float64 {
+ return k.sisi * 12
+}
+```
+
+Objek hasil cetakan struct `kubus` di atas, nantinya akan ditampung oleh objek cetakan interface `hitung` yang isinya merupakan gabungan interface `hitung2d` dan `hitung3d`.
+
+Terakhir, buat implementasi-nya di main.
+
+```go
+func main() {
+ var bangunRuang hitung = &kubus{4}
+
+ fmt.Println("===== kubus")
+ fmt.Println("luas :", bangunRuang.luas())
+ fmt.Println("keliling :", bangunRuang.keliling())
+ fmt.Println("volume :", bangunRuang.volume())
+}
+```
+
+Bisa dilihat di kode di atas, lewat interface `hitung`, method `luas`, `keliling`, dan `volume` bisa di akses.
+
+Pada chapter [A.23. Pointer](/A-pointer.html) dijelaskan bahwa method pointer bisa diakses lewat variabel objek biasa dan variabel objek pointer. Variabel objek yang dicetak menggunakan struct yang memiliki method pointer, jika ditampung ke dalam variabel interface, harus diambil referensi-nya terlebih dahulu. Contohnya bisa dilihat pada kode di atas `var bangunRuang hitung = &kubus{4}`.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 1a8e765546cff029a22af24a691805396dedc569 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:31 +0700
Subject: [PATCH 033/522] New translations a-json.md (English)
---
en/content-en/A-json.md | 141 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 141 insertions(+)
create mode 100644 en/content-en/A-json.md
diff --git a/en/content-en/A-json.md b/en/content-en/A-json.md
new file mode 100644
index 000000000..73bbcd034
--- /dev/null
+++ b/en/content-en/A-json.md
@@ -0,0 +1,141 @@
+# A.53. JSON Data
+
+**JSON** atau *Javascript Object Notation* adalah notasi standar yang umum digunakan untuk komunikasi data dalam web. JSON merupakan subset dari *javascript*.
+
+Go menyediakan package `encoding/json` yang berisikan banyak fungsi untuk kebutuhan operasi json.
+
+Pada chapter ini, kita akan belajar cara untuk konverstri string yang berbentuk json menjadi objek Go, dan sebaliknya.
+
+## A.53.1. Decode JSON Ke Variabel Objek Struct
+
+Di Go, data json dituliskan sebagai `string`. Dengan menggunakan `json.Unmarshal`, json string bisa dikonversi menjadi bentuk objek, entah itu dalam bentuk `map[string]interface{}` ataupun objek struct.
+
+Program berikut ini adalah contoh cara decoding json ke bentuk objek. Pertama import package yang dibutuhkan, lalu siapkan struct `User`.
+
+```go
+package main
+
+import "encoding/json"
+import "fmt"
+
+type User struct {
+ FullName string `json:"Name"`
+ Age int
+}
+```
+
+Struct `User` ini nantinya digunakan untuk membuat variabel baru penampung hasil decode json string. Proses decode sendiri dilakukan lewat fungsi `json.Unmarshal()`, dengan json string tersebut dimasukan ke statement fungsi tersebut.
+
+Silakan tulis kode berikut.
+
+```go
+func main() {
+ var jsonString = `{"Name": "john wick", "Age": 27}`
+ var jsonData = []byte(jsonString)
+
+ var data User
+
+ var err = json.Unmarshal(jsonData, &data)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ fmt.Println("user :", data.FullName)
+ fmt.Println("age :", data.Age)
+}
+```
+
+Fungsi unmarshal hanya menerima data json dalam bentuk `[]byte`, maka dari itu data json string pada kode di atas di-casting terlebih dahulu ke tipe `[]byte` sebelum dipergunakan pada fungsi unmarshal.
+
+Juga, perlu diperhatikan, argument ke-2 fungsi unmarshal harus diisi dengan **pointer** dari objek yang nantinya akan menampung hasilnya.
+
+
+
+Jika kita perhatikan lagi, pada struct `User`, salah satu property-nya yaitu `FullName` memiliki **tag** `json:"Name"`. Tag tersebut digunakan untuk mapping informasi json ke property yang bersangkutan.
+
+Data json yang akan diparsing memiliki 2 property yaitu `Name` dan `Age`. Kebetulan penulisan `Age` pada data json dan pada struktur struct adalah sama, berbeda dengan `Name` yang tidak ada pada struct.
+
+Dengan menambahkan tag json, maka property `FullName` struct akan secara cerdas menampung data json property `Name`.
+
+> Pada kasus decoding data json string ke variabel objek struct, semua level akses property struct penampung harus publik.
+
+## A.53.2. Decode JSON Ke `map[string]interface{}` & `interface{}`
+
+Tak hanya ke objek cetakan struct, target decoding data json juga bisa berupa variabel bertipe `map[string]interface{}`.
+
+```go
+var data1 map[string]interface{}
+json.Unmarshal(jsonData, &data1)
+
+fmt.Println("user :", data1["Name"])
+fmt.Println("age :", data1["Age"])
+```
+
+Variabel bertipe `interface{}` juga bisa digunakan untuk menampung hasil decode. Dengan catatan pada pengaksesan nilai property, harus dilakukan casting terlebih dahulu ke `map[string]interface{}`.
+
+```go
+var data2 interface{}
+json.Unmarshal(jsonData, &data2)
+
+var decodedData = data2.(map[string]interface{})
+fmt.Println("user :", decodedData["Name"])
+fmt.Println("age :", decodedData["Age"])
+```
+
+## A.53.3. Decode Array JSON Ke Array Objek
+
+Decode data dari array json ke slice/array objek masih sama, siapkan saja variabel penampung hasil decode dengan tipe slice struct. Contohnya bisa dilihat pada kode berikut.
+
+```go
+var jsonString = `[
+ {"Name": "john wick", "Age": 27},
+ {"Name": "ethan hunt", "Age": 32}
+]`
+
+var data []User
+
+var err = json.Unmarshal([]byte(jsonString), &data)
+if err != nil {
+ fmt.Println(err.Error())
+ return
+}
+
+fmt.Println("user 1:", data[0].FullName)
+fmt.Println("user 2:", data[1].FullName)
+```
+
+## A.53.4. Encode Objek Ke JSON String
+
+Setelah sebelumnya dijelaskan beberapa cara decode data dari json string ke objek, sekarang kita akan belajar cara **encode** data objek ke bentuk json string.
+
+Fungsi `json.Marshal` digunakan untuk encoding data ke json string. Sumber data bisa berupa variabel objek cetakan struct, `map[string]interface{}`, atau slice.
+
+Pada contoh berikut, data slice struct dikonversi ke dalam bentuk json string. Hasil konversi berupa `[]byte`, casting terlebih dahulu ke tipe `string` agar bisa ditampilkan bentuk json string-nya.
+
+```go
+var object = []User{{"john wick", 27}, {"ethan hunt", 32}}
+var jsonData, err = json.Marshal(object)
+if err != nil {
+ fmt.Println(err.Error())
+ return
+}
+
+var jsonString = string(jsonData)
+fmt.Println(jsonString)
+```
+
+Output:
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 3d7d12737d37a253d35d51d960e7d99217d33559 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:31 +0700
Subject: [PATCH 034/522] New translations a-komentar.md (English)
---
en/content-en/A-komentar.md | 56 +++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 en/content-en/A-komentar.md
diff --git a/en/content-en/A-komentar.md b/en/content-en/A-komentar.md
new file mode 100644
index 000000000..31718e5bf
--- /dev/null
+++ b/en/content-en/A-komentar.md
@@ -0,0 +1,56 @@
+# A.8. Komentar
+
+Komentar biasa dimanfaatkan untuk menyisipkan catatan pada kode program, menulis penjelasan/deskripsi mengenai suatu blok kode, atau bisa juga digunakan untuk me-*remark* kode (men-non-aktifkan kode yg tidak digunakan). Komentar akan diabaikan ketika kompilasi maupun eksekusi program.
+
+Ada 2 jenis komentar di Go, *inline* & *multiline*. Pada pembahasan ini akan dijelaskan tentang penerapan dan perbedaan kedua jenis komentar tersebut.
+
+## A.8.1. Komentar *Inline*
+
+Penulisan komentar jenis ini di awali dengan tanda **double slash** (`//`) lalu diikuti pesan komentarnya. Komentar inline hanya berlaku untuk satu baris pesan saja. Jika pesan komentar lebih dari satu baris, maka tanda `//` harus ditulis lagi di baris selanjutnya.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ // komentar kode
+ // menampilkan pesan hello world
+ fmt.Println("hello world")
+
+ // fmt.Println("baris ini tidak akan dieksekusi")
+}
+```
+
+Mari kita praktekan kode di atas. Siapkan file program baru dalam project folder (bisa buat project baru atau gunakan project yang sudah ada). Kemudian isi dengan kode di atas, lalu jalankan.
+
+
+
+Hasilnya hanya tulisan **hello world** saja yang muncul di layar, karena semua yang di awali tanda double slash `//` diabaikan oleh compiler.
+
+## A.8.2. Komentar *Multiline*
+
+Komentar yang cukup panjang akan lebih rapi jika ditulis menggunakan teknik komentar multiline. Ciri dari komentar jenis ini adalah penulisannya diawali dengan tanda `/*` dan diakhiri `*/`.
+
+```go
+/*
+ komentar kode
+ menampilkan pesan hello world
+*/
+fmt.Println("hello world")
+
+// fmt.Println("baris ini tidak akan dieksekusi")
+```
+
+Sifat komentar ini sama seperti komentar inline, yaitu sama-sama diabaikan oleh compiler.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From d4f8eb18c893b8f52b6253f5fbee6279a3e3aa6a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:32 +0700
Subject: [PATCH 035/522] New translations a-konstanta.md (English)
---
en/content-en/A-konstanta.md | 111 +++++++++++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
create mode 100644 en/content-en/A-konstanta.md
diff --git a/en/content-en/A-konstanta.md b/en/content-en/A-konstanta.md
new file mode 100644
index 000000000..5cd147327
--- /dev/null
+++ b/en/content-en/A-konstanta.md
@@ -0,0 +1,111 @@
+# A.11. Konstanta
+
+Konstanta adalah jenis variabel yang nilainya tidak bisa diubah. Inisialisasi nilai hanya dilakukan sekali di awal, setelah itu variabel tidak bisa diubah nilainya.
+
+## A.11.1. Penggunaan Konstanta
+
+Data seperti **pi** (22/7), kecepatan cahaya (299.792.458 m/s), adalah contoh data yang tepat jika dideklarasikan sebagai konstanta daripada variabel, karena nilainya sudah pasti dan tidak berubah.
+
+Cara penerapan konstanta sama seperti deklarasi variabel biasa, selebihnya tinggal ganti keyword `var` dengan `const`.
+
+```go
+const firstName string = "john"
+fmt.Print("halo ", firstName, "!\n")
+```
+
+Teknik type inference bisa diterapkan pada konstanta, caranya yaitu cukup dengan menghilangkan tipe data pada saat deklarasi.
+
+```go
+const lastName = "wick"
+fmt.Print("nice to meet you ", lastName, "!\n")
+```
+
+#### • Penggunaan Fungsi `fmt.Print()`
+
+Fungsi ini memiliki peran yang sama seperti fungsi `fmt.Println()`, pembedanya fungsi `fmt.Print()` tidak menghasilkan baris baru di akhir outputnya.
+
+Perbedaan lainnya adalah, nilai pada parameter-parameter yang dimasukkan ke fungsi tersebut digabungkan tanpa pemisah. Tidak seperti pada fungsi `fmt.Println()` yang nilai paremeternya digabung menggunakan penghubung spasi.
+
+```go
+fmt.Println("john wick")
+fmt.Println("john", "wick")
+
+fmt.Print("john wick\n")
+fmt.Print("john ", "wick\n")
+fmt.Print("john", " ", "wick\n")
+```
+
+Kode di atas menunjukkan perbedaan antara `fmt.Println()` dan `fmt.Print()`. Output yang dihasilkan oleh 5 statement di atas adalah sama, meski cara yang digunakan berbeda.
+
+Bila menggunakan `fmt.Println()` tidak perlu menambahkan spasi di tiap kata, karena fungsi tersebut akan secara otomatis menambahkannya di sela-sela nilai. Berbeda dengan `fmt.Print()`, perlu ditambahkan spasi, karena fungsi ini tidak menambahkan spasi di sela-sela nilai parameter yang digabungkan.
+
+## A.11.2. Deklarasi Multi Konstanta
+
+Sama seperti variabel, konstanta juga dapat dideklarasikan secara bersamaan.
+
+Berikut adalah contoh deklarasi konstanta dengan tipe data dan nilai yang berbeda.
+
+```go
+const (
+ square = "kotak"
+ isToday bool = true
+ numeric uint8 = 1
+ floatNum = 2.2
+)
+```
+
+- `square`, dideklarasikan dengan metode _type inference_ dengan tipe data **string** dan nilainya **"kotak"**
+- `isToday`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **bool** dan nilainya **true**
+- `numeric`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **uint8** dan nilainya **1**
+- `floatNum`, dideklarasikan dengan metode _type inference_ dengan tipe data **float** dan nilainya **2.2**
+
+Contoh deklarasi konstanta dengan tipe data dan nilai yang sama:
+
+```go
+const (
+ a = "konstanta"
+ b
+)
+```
+
+> Ketika tipe data dan nilai tidak dituliskan dalam deklarasi konstanta, maka tipe data dan nilai yang dipergunakan adalah sama seperti konstanta yang dideklarasikan diatasnya.
+
+- `a` dideklarasikan dengan metode _type inference_ dengan tipe data **string** dan nilainya **"konstanta"**
+- `b` dideklarasikan dengan metode _type inference_ dengan tipe data **string** dan nilainya **"konstanta"**
+
+Berikut contoh gabungan dari keduanya:
+
+```go
+const (
+ today string = "senin"
+ sekarang
+ isToday2 = true
+)
+```
+
+- `today` dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"senin"**
+- `sekarang` dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"senin"**
+- `isToday2` dideklarasikan dengan metode _type inference_ dengan tipe data **bool** dan nilainya **true**
+
+Berikut contoh deklrasi _multiple_ konstanta dalam satu baris:
+
+```go
+const satu, dua = 1, 2
+const three, four string = "tiga", "empat"
+```
+
+- `satu`, dideklarasikan dengan metode _type inference_ dengan tipe data **int** dan nilainya **1**
+- `dua`, dideklarasikan dengan metode _type inference_ dengan tipe data **int** dan nilainya **2**
+- `three`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"tiga"**
+- `four`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"empat"**
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 5c371aa1a423fa50dc963b61f388970a05b0e4c3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:33 +0700
Subject: [PATCH 036/522] New translations a-map.md (English)
---
en/content-en/A-map.md | 182 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)
create mode 100644 en/content-en/A-map.md
diff --git a/en/content-en/A-map.md b/en/content-en/A-map.md
new file mode 100644
index 000000000..7d1e06cd2
--- /dev/null
+++ b/en/content-en/A-map.md
@@ -0,0 +1,182 @@
+# A.17. Map
+
+**Map** adalah tipe data asosiatif yang ada di Go, berbentuk *key-value pair*. Untuk setiap data (atau value) yang disimpan, disiapkan juga key-nya. Key harus unik, karena digunakan sebagai penanda (atau identifier) untuk pengaksesan value yang bersangkutan.
+
+Kalau dilihat, `map` mirip seperti slice, hanya saja indeks yang digunakan untuk pengaksesan bisa ditentukan sendiri tipe-nya (indeks tersebut adalah key).
+
+## A.17.1. Penggunaan Map
+
+Cara menggunakan map cukup dengan menuliskan keyword `map` diikuti tipe data key dan value-nya. Agar lebih mudah dipahami, silakan perhatikan contoh di bawah ini.
+
+```go
+var chicken map[string]int
+chicken = map[string]int{}
+
+chicken["januari"] = 50
+chicken["februari"] = 40
+
+fmt.Println("januari", chicken["januari"]) // januari 50
+fmt.Println("mei", chicken["mei"]) // mei 0
+```
+
+Variabel `chicken` dideklarasikan sebagai map, dengan tipe data key adalah `string` dan value-nya `int`. Dari kode tersebut bisa dilihat bagaimana cara penggunaan keyword `map`.
+
+Kode `map[string]int` maknanya adalah, tipe data `map` dengan key bertipe `string` dan value bertipe `int`.
+
+Default nilai variabel `map` adalah `nil`. Oleh karena itu perlu dilakukan inisialisasi nilai default di awal, caranya cukup dengan tambahkan kurung kurawal pada akhir tipe, contoh seperti pada kode di atas: `map[string]int{}`.
+
+Cara menge-set nilai pada sebuah map adalah dengan menuliskan variabel-nya, kemudian disisipkan `key` pada kurung siku variabel (mirip seperti cara pengaksesan elemen slice), lalu isi nilainya. Contohnya seperti `chicken["februari"] = 40`. Sedangkan cara pengambilan value adalah cukup dengan menyisipkan `key` pada kurung siku variabel.
+
+Pengisian data pada map bersifat **overwrite**, ketika variabel sudah memiliki item dengan key yang sama, maka value lama akan ditimpa dengan value baru.
+
+
+
+Pada pengaksesan item menggunakan key yang belum tersimpan di map, akan dikembalikan nilai default tipe data value-nya. Contohnya seperti pada kode di atas, `chicken["mei"]` menghasilkan nilai 0 (nilai default tipe `int`), karena belum ada item yang tersimpan menggunakan key `"mei"`.
+
+## A.17.2. Inisialisasi Nilai Map
+
+Zero value dari map adalah `nil`, maka tiap variabel bertipe map harus di-inisialisasi secara explisit nilai awalnya (agar tidak `nil`).
+
+```go
+var data map[string]int
+data["one"] = 1
+// akan muncul error!
+
+data = map[string]int{}
+data["one"] = 1
+// tidak ada error
+```
+
+Nilai variabel bertipe map bisa didefinisikan di awal, caranya dengan menambahkan kurung kurawal setelah tipe data, lalu menuliskan key dan value di dalamnya. Cara ini sekilas mirip dengan definisi nilai array/slice namun dalam bentuk key-value.
+
+```go
+// cara horizontal
+var chicken1 = map[string]int{"januari": 50, "februari": 40}
+
+// cara vertical
+var chicken2 = map[string]int{
+ "januari": 50,
+ "februari": 40,
+}
+```
+
+Key dan value dituliskan dengan pembatas tanda titik dua (`:`). Sedangkan tiap itemnya dituliskan dengan pembatas tanda koma (`,`). Khusus deklarasi dengan gaya vertikal, tanda koma perlu dituliskan setelah item terakhir.
+
+Variabel `map` bisa di-inisialisasi dengan tanpa nilai awal, caranya menggunakan tanda kurung kurawal, contoh: `map[string]int{}`. Atau bisa juga dengan menggunakan keyword `make` dan `new`. Contohnya bisa dilihat pada kode berikut. Ketiga cara di bawah ini intinya adalah sama.
+
+```go
+var chicken3 = map[string]int{}
+var chicken4 = make(map[string]int)
+var chicken5 = *new(map[string]int)
+```
+
+Khusus inisialisasi data menggunakan keyword `new`, yang dihasilkan adalah data pointer. Untuk mengambil nilai aslinya bisa dengan menggunakan tanda asterisk (`*`). Topik pointer akan dibahas lebih detail ketika sudah masuk [A.23. Pointer](/A-pointer.html).
+
+## A.17.3. Iterasi Item Map Menggunakan `for` - `range`
+
+Item variabel `map` bisa di iterasi menggunakan `for` - `range`. Cara penerapannya masih sama seperti pada slice, pembedanya data yang dikembalikan di tiap perulangan adalah key dan value, bukan indeks dan elemen. Contohnya bisa dilihat pada kode berikut.
+
+```go
+var chicken = map[string]int{
+ "januari": 50,
+ "februari": 40,
+ "maret": 34,
+ "april": 67,
+}
+
+for key, val := range chicken {
+ fmt.Println(key, " \t:", val)
+}
+```
+
+
+
+## A.17.4. Menghapus Item Map
+
+Fungsi `delete()` digunakan untuk menghapus item dengan key tertentu pada variabel map. Cara penggunaannya, dengan memasukan objek map dan key item yang ingin dihapus sebagai parameter.
+
+```go
+var chicken = map[string]int{"januari": 50, "februari": 40}
+
+fmt.Println(len(chicken)) // 2
+fmt.Println(chicken)
+
+delete(chicken, "januari")
+
+fmt.Println(len(chicken)) // 1
+fmt.Println(chicken)
+```
+
+Item yang memiliki key `"januari"` dalam variabel `chicken` akan dihapus.
+
+
+
+Fungsi `len()` jika digunakan pada map akan mengembalikan jumlah item.
+
+## A.17.5. Deteksi Keberadaan Item Dengan Key Tertentu
+
+Ada cara untuk mengetahui apakah dalam sebuah variabel map terdapat item dengan key tertentu atau tidak, yaitu dengan memanfaatkan 2 variabel sebagai penampung nilai kembalian pengaksesan item. Return value ke-2 ini adalah opsional, isinya nilai `bool` yang menunjukkan ada atau tidaknya item yang dicari.
+
+```go
+var chicken = map[string]int{"januari": 50, "februari": 40}
+var value, isExist = chicken["mei"]
+
+if isExist {
+ fmt.Println(value)
+} else {
+ fmt.Println("item is not exists")
+}
+```
+
+## A.17.6. Kombinasi Slice & Map
+
+Slice dan `map` bisa dikombinasikan, dan sering digunakan pada banyak kasus, contohnya seperti data array yang berisikan informasi siswa, dan banyak lainnya.
+
+Cara menggunakannya cukup mudah, contohnya seperti `[]map[string]int`, artinya slice yang tipe tiap elemen-nya adalah `map[string]int`.
+
+Agar lebih jelas, silakan praktekan contoh berikut.
+
+```go
+var chickens = []map[string]string{
+ map[string]string{"name": "chicken blue", "gender": "male"},
+ map[string]string{"name": "chicken red", "gender": "male"},
+ map[string]string{"name": "chicken yellow", "gender": "female"},
+}
+
+for _, chicken := range chickens {
+ fmt.Println(chicken["gender"], chicken["name"])
+}
+```
+
+Variabel `chickens` di atas berisikan informasi bertipe `map[string]string`, yang kebetulan tiap elemen memiliki 2 key yang sama.
+
+Jika anda menggunakan versi go terbaru, cara deklarasi slice-map bisa dipersingkat, tipe tiap elemen tidak wajib untuk dituliskan.
+
+```go
+var chickens = []map[string]string{
+ {"name": "chicken blue", "gender": "male"},
+ {"name": "chicken red", "gender": "male"},
+ {"name": "chicken yellow", "gender": "female"},
+}
+```
+
+Dalam `[]map[string]string`, tiap elemen bisa saja memiliki key yang berbeda-beda, sebagai contoh seperti kode berikut.
+
+```go
+var data = []map[string]string{
+ {"name": "chicken blue", "gender": "male", "color": "brown"},
+ {"address": "mangga street", "id": "k001"},
+ {"community": "chicken lovers"},
+}
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 2cfd4d78e06d98d3f7d7dccb13962b8f35440029 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:34 +0700
Subject: [PATCH 037/522] New translations a-method.md (English)
---
en/content-en/A-method.md | 159 ++++++++++++++++++++++++++++++++++++++
1 file changed, 159 insertions(+)
create mode 100644 en/content-en/A-method.md
diff --git a/en/content-en/A-method.md b/en/content-en/A-method.md
new file mode 100644
index 000000000..32afeb085
--- /dev/null
+++ b/en/content-en/A-method.md
@@ -0,0 +1,159 @@
+# A.25. Method
+
+**Method** adalah fungsi yang menempel pada `type` (bisa `struct` atau tipe data lainnya). Method bisa diakses lewat variabel objek.
+
+Keunggulan method dibanding fungsi biasa adalah memiliki akses ke property struct hingga level *private* (level akses nantinya akan dibahas lebih detail pada chapter selanjutnya). Dan juga, dengan menggunakan method sebuah proses bisa di-enkapsulasi dengan baik.
+
+## A.25.1. Penerapan Method
+
+Cara menerapkan method sedikit berbeda dibanding penggunaan fungsi. Ketika deklarasi, ditentukan juga siapa pemilik method tersebut. Contohnya bisa dilihat pada kode berikut:
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+type student struct {
+ name string
+ grade int
+}
+
+func (s student) sayHello() {
+ fmt.Println("halo", s.name)
+}
+
+func (s student) getNameAt(i int) string {
+ return strings.Split(s.name, " ")[i-1]
+}
+```
+
+Cara deklarasi method sama seperti fungsi, hanya saja perlu ditambahkan deklarasi variabel objek di sela-sela keyword `func` dan nama fungsi. Struct yang digunakan akan menjadi pemilik method.
+
+`func (s student) sayHello()` maksudnya adalah fungsi `sayHello` dideklarasikan sebagai method milik struct `student`. Pada contoh di atas struct `student` memiliki dua buah method, yaitu `sayHello()` dan `getNameAt()`.
+
+Contoh pemanfaatan method bisa dilihat pada kode berikut.
+
+```go
+func main() {
+ var s1 = student{"john wick", 21}
+ s1.sayHello()
+
+ var name = s1.getNameAt(2)
+ fmt.Println("nama panggilan :", name)
+}
+```
+
+Output:
+
+
+
+Cara mengakses method sama seperti pengaksesan properti berupa variabel. Tinggal panggil saja methodnya.
+
+```go
+s1.sayHello()
+var name = s1.getNameAt(2)
+```
+
+Method memiliki sifat yang sama persis dengan fungsi biasa. Seperti bisa berparameter, memiliki nilai balik, dan lainnya. Dari segi sintaks, pembedanya hanya ketika pengaksesan dan deklarasi. Bisa dilihat di kode berikut, sekilas perbandingan penulisan fungsi dan method.
+
+```go
+func sayHello() {
+func (s student) sayHello() {
+
+func getNameAt(i int) string {
+func (s student) getNameAt(i int) string {
+```
+
+## A.25.2. Method Pointer
+
+Method pointer adalah method yang variabel objek pemilik method tersebut berupa pointer.
+
+Kelebihan method jenis ini adalah, ketika kita melakukan manipulasi nilai pada property lain yang masih satu struct, nilai pada property tersebut akan di rubah pada reference nya. Lebih jelasnya perhatikan kode berikut.
+
+```go
+package main
+
+import "fmt"
+
+type student struct {
+ name string
+ grade int
+}
+
+func (s student) changeName1(name string) {
+ fmt.Println("---> on changeName1, name changed to", name)
+ s.name = name
+}
+
+func (s *student) changeName2(name string) {
+ fmt.Println("---> on changeName2, name changed to", name)
+ s.name = name
+}
+
+func main() {
+ var s1 = student{"john wick", 21}
+ fmt.Println("s1 before", s1.name)
+ // john wick
+
+ s1.changeName1("jason bourne")
+ fmt.Println("s1 after changeName1", s1.name)
+ // john wick
+
+ s1.changeName2("ethan hunt")
+ fmt.Println("s1 after changeName2", s1.name)
+ // ethan hunt
+}
+```
+
+Output:
+
+
+
+Setelah eksekusi statement `s1.changeName1("jason bourne")`, nilai `s1.name` tidak berubah. Sebenarnya nilainya berubah tapi hanya dalam method `changeName1()` saja, nilai pada reference di objek-nya tidak berubah. Karena itulah ketika objek di print value dari `s1.name` tidak berubah.
+
+Keistimewaan lain method pointer adalah, method itu sendiri bisa dipanggil dari objek pointer maupun objek biasa.
+
+```go
+// pengaksesan method dari variabel objek biasa
+var s1 = student{"john wick", 21}
+s1.sayHello()
+
+// pengaksesan method dari variabel objek pointer
+var s2 = &student{"ethan hunt", 22}
+s2.sayHello()
+```
+
+---
+
+Berikut adalah penjelasan tambahan mengenai beberapa hal pada chapter ini.
+
+#### • Penggunaan Fungsi `strings.Split()`
+
+Pada chapter ini ada fungsi baru yang kita gunakan: `strings.Split()`. Fungsi ini berguna untuk memisah string menggunakan pemisah yang ditentukan sendiri. Hasilnya adalah array berisikan kumpulan substring.
+
+```go
+strings.Split("ethan hunt", " ")
+// ["ethan", "hunt"]
+```
+
+Pada contoh di atas, string `"ethan hunt"` dipisah menggunakan separator spasi `" "`. Maka hasilnya terbentuk array berisikan 2 data, `"ethan"` dan `"hunt"`.
+
+## A.25.3. Apakah `fmt.Println()` & `strings.Split()` Juga Merupakan Method?
+
+Setelah tahu apa itu method dan bagaimana penggunaannya, mungkin akan muncul di benak kita bahwa kode seperti `fmt.Println()`, `strings.Split()` dan lainnya-yang-berada-pada-package-lain adalah merupakan method. Tapi sayangnya **bukan**. `fmt` di situ bukanlah variabel objek, dan `Println()` bukan merupakan method-nya.
+
+`fmt` adalah nama **package** yang di-import (bisa dilihat pada kode `import "fmt"`). Sedangkan `Println()` adalah **nama fungsi**. Untuk mengakses fungsi yang berada pada package lain, harus dituliskan nama package-nya. Hal ini berlaku juga di dalam package `main`. Jika ada fungsi dalam package main yang diakses dari package lain yang berbeda, maka penulisannya `main.NamaFungsi()`.
+
+Lebih detailnya akan dibahas pada chapter selanjutnya.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From e46e1e96d315ee031644c09cf84bb964ce1e4887 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:35 +0700
Subject: [PATCH 038/522] New translations a-mongodb.md (English)
---
en/content-en/A-mongodb.md | 336 +++++++++++++++++++++++++++++++++++++
1 file changed, 336 insertions(+)
create mode 100644 en/content-en/A-mongodb.md
diff --git a/en/content-en/A-mongodb.md b/en/content-en/A-mongodb.md
new file mode 100644
index 000000000..fd7af9fcc
--- /dev/null
+++ b/en/content-en/A-mongodb.md
@@ -0,0 +1,336 @@
+# A.57. NoSQL MongoDB
+
+Go tidak menyediakan interface generic untuk NoSQL, jadi implementasi driver tiap brand NoSQL di Go biasanya berbeda satu dengan lainnya.
+
+Pada chapter ini kita akan belajar cara berkomunikasi dengan NoSQL MongoDB server menggunakan official driver untuk go, yaitu [mongo-go-driver](https://github.com/mongodb/mongo-go-driver).
+
+## A.57.1. Persiapan
+
+Ada beberapa hal yang perlu disiapkan sebelum mulai masuk ke bagian coding.
+
+ 1. Instal mongo-go-driver menggunakan `go get`.
+
+ ```
+ cd
+ go get go.mongodb.org/mongo-driver/mongo
+ ```
+
+ 2. Pastikan sudah terinstal MongoDB di komputer anda, dan jangan lupa untuk menjalankan daemon-nya. Jika belum, [download](ihttps://www.mongodb.org/downloads) dan install terlebih dahulu.
+
+ 3. Instal juga MongoDB GUI untuk mempermudah browsing data. Bisa menggunakan [MongoChef](http://3t.io/mongochef/), [Robomongo](http://robomongo.org/), atau lainnya.
+
+## A.57.2. Insert Data
+
+Cara insert data ke mongodb via Go tidak terlalu sulit. Kita akan mempelajarinya dengan cara praktek langsung. Pertama-tama silakan import package yang dibutuhkan.
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "time"
+
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "go.mongodb.org/mongo-driver/bson"
+)
+```
+
+Siapkan satu object context dan struct `student`. Rencananya satu buah document kita buat sebagai satu buah objek `student`.
+
+Perlu diketahui bahwa pada chapter ini tidak dijelaskan tentang apa itu context. Silakan merujuk ke [D.2. Google API Search Dengan Timeout](/D-google-api-search.html) untuk mempelajarinya. Menggunakan satu context background untuk semua operasi sangat tidak dianjurkan, tapi pada chapter ini kita terapkan demikian agar tidak menambah kebingungan pembaca yang masih proses belajar. Context sendiri fungsinya sangat banyak, untuk kasus sejenis biasanya digunakan untuk handle operation timeout atau lainnya.
+
+```go
+var ctx = context.Background()
+
+type student struct {
+ Name string `bson:"name"`
+ Grade int `bson:"Grade"`
+}
+```
+
+Tag `bson` pada property struct digunakan sebagai penentu nama field ketika data disimpan ke dalam collection. Jika sebuah property tidak memiliki tag bson, secara default nama field adalah sama dengan nama property hanya saja lowercase. Untuk customize nama field, gunakan tag `bson`.
+
+Pada contoh di atas, property `Name` ditentukan nama field mongo-nya sebagai `name`, dan `Grade` sebagai `Grade`.
+
+Selanjutnya siapkan fungsi untuk membuat satu buah mongo connection. Dari objek connection diambil object database, kemudian dijadikan sebagai nilai balik fungsi.
+
+```go
+func connect() (*mongo.Database, error) {
+ clientOptions := options.Client()
+ clientOptions.ApplyURI("mongodb://localhost:27017")
+ client, err := mongo.NewClient(clientOptions)
+ if err != nil {
+ return nil, err
+ }
+
+ err = client.Connect(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ return client.Database("belajar_golang"), nil
+}
+```
+
+Fungsi `mongo.NewClient()` digunakan untuk meng-inisialisasi koneksi database dari client ke server. Fungsi tersebut memerlukan parameter bertipe `*options.ClientOptions`. Pada client options mongo connection string perlu di set (lewat method `.ApplyURI()`).
+
+> Silakan sesuaikan connection string dengan mongo db server yang dipergunakan. Lebih jelasnya silakan merujuk ke [MongoDB Documentation: Connection String URI Format](https://docs.mongodb.com/manual/reference/connection-string/).
+
+Dari object client, panggil method `.Connect()` untuk inisialisasi koneksi ke db server. Setelah itu panggil method `.Database()` untuk set database yang aktif.
+
+Lanjut buat fungsi yang di dalamnya berisikan kode untuk insert data ke mongodb, lalu panggil fungsi tersebut di `main()`.
+
+```go
+func insert() {
+ db, err := connect()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ _, err = db.Collection("student").InsertOne(ctx, student{"Wick", 2})
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ _, err = db.Collection("student").InsertOne(ctx, student{"Ethan", 2})
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ fmt.Println("Insert success!")
+}
+
+func main() {
+ insert()
+}
+```
+
+Fungsi `connect()` mengembalikan objek bertipe `*mongo.Database`. Dari objek tersebut akses method `.Collection()` lalu chain dengan method lainnya untuk melakukan operasi database, kurang lebih skema statement-nya sama seperti operasi mongodb.
+
+Sebagai contoh, pada kode di atas `.InsertOne()` digunakan untuk insert satu data ke database. Perbandingannya kurang lebih seperti berikut:
+
+```js
+// mongodb
+db.getCollection("student").insertOne({ name: "Wick", Grade: 2 })
+
+// mongo-go-driver
+db.Collection("student").InsertOne(ctx, student{ name: "Wick", Grade: 2})
+```
+
+Perlu diketahui, bahwa di mongo-go-driver setiap operasi biasanya membutuhkan objek context untuk disisipkan sebagai parameter pertama. Pada contoh di atas kita gunakan variabel `ctx` yang sudah dideklarasikan sebelumnya.
+
+
+
+## A.57.3. Membaca Data
+
+Method `.Find()` digunakan untuk membaca atau mencari data. Method ini mengembalikan objek cursor, objek ini harus digunakan dalam perulangan untuk mengambil data yang ditemukan.
+
+Dalam pencarian, sisipkan query atau filter sebagai parameter ke-dua method `.Find()`.
+
+```go
+func find() {
+ db, err := connect()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ csr, err := db.Collection("student").Find(ctx, bson.M{"name": "Wick"})
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+ defer csr.Close(ctx)
+
+ result := make([]student, 0)
+ for csr.Next(ctx) {
+ var row student
+ err := csr.Decode(&row)
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ result = append(result, row)
+ }
+
+ if len(result) > 0 {
+ fmt.Println("Name :", result[0].Name)
+ fmt.Println("Grade :", result[0].Grade)
+ }
+}
+
+func main() {
+ find()
+}
+```
+
+Query selector ditulis dalam tipe `bson.M`. Tipe ini sebenarnya adalah alias dari `map[string]interface{}`.
+
+Cara untuk mendapatkan semua rows hasil pencarian kursor adalah dengan mengiterasi method `.Next()` dengan di dalamnya method `.Decode()` dipanggil untuk retrieve datanya. Setelah itu data yang sudah terampil di-append ke slice.
+
+Selain method `.Find()` ada juga `.FindOne()`, silakan cek dokumentasi lebih jelasnya.
+
+
+
+Berikut adalah skema perbandingan contoh operasi get data menggunakan mongo query vs mongo-go-driver:
+
+```js
+// mongodb
+db.getCollection("student").find({"name": "Wick"})
+
+// mongo-go-driver
+db.Collection("student").Find(ctx, bson.M{"name": "Wick"})
+```
+
+## A.57.4. Update Data
+
+Method `.Update()` digunakan untuk update data (jika update hanya diinginkan untuk berlaku pada 1 dokumen saja, maka gunakan `.UpdateOne()`). Method `.Update()` memerlukan 3 buah parameter dalam pemanggilannya.
+
+ 1. Parameter pertama, objek context
+ 2. Parameter kedua adalah query kondisi yang mengacu ke data mana yang ingin di update
+ 3. Parameter ketiga adalah perubahan datanya.
+
+Di bawah ini adalah contok implementasi method `Update()`.
+
+```go
+func update() {
+ db, err := connect()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ var selector = bson.M{"name": "Wick"}
+ var changes = student{"John Wick", 2}
+ _, err = db.Collection("student").UpdateOne(ctx, selector, bson.M{"$set": changes})
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ fmt.Println("Update success!")
+}
+
+func main() {
+ update()
+}
+```
+
+Jalankan kode di atas, lalu cek lewat Mongo GUI apakah data berubah.
+
+
+
+Berikut adalah skema perbandingan query vs mongo-go-driver dari operasi di atas.
+
+```js
+// mongodb
+db.getCollection("student").update({"name": "Wick"}, { "$set": {"name": "Wick", "Grade": 2} })
+
+// mongo-go-driver
+db.Collection("student").UpdateOne(ctx, bson.M{"name": "Wick"}, bson.M{"$set": student{"John Wick", 2}})
+```
+
+Selain method `.UpdateOne()` ada juga method `.UpdateMany()`, kegunaan masing-masing bisa dilihat dari nama fungsinya.
+
+## A.57.5. Menghapus Data
+
+Untuk menghapus data gunakan method `.DeleteOne()` atau `.DeleteMany()`.
+
+```go
+func remove() {
+ db, err := connect()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ var selector = bson.M{"name": "John Wick"}
+ _, err = db.Collection("student").DeleteOne(ctx, selector)
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ fmt.Println("Remove success!")
+}
+
+func main() {
+ remove()
+}
+```
+
+Hasil dari kode di atas, 2 data yang sebelumnya sudah di-insert kini tinggal satu saja.
+
+
+
+Berikut adalah skema perbandingan query vs mongo-go-driver dari operasi di atas.
+
+```js
+// mongodb
+db.getCollection("student").delete({"name": "John Wick"})
+
+// mongo-go-driver
+db.Collection("student").DeleteMany(ctx, bson.M{"name": "John Wick"})
+```
+
+## A.57.6. Aggregate Data
+
+Agregasi data menggunakan driver ini juga cukup mudah, caranya tinggal gunakan method `.Aggregate()` dan sisipkan pipeline query sebagai argument ke-2 pemanggilan method. Eksekusi method tersebut mengembalikan objek cursor. Selebihnya capture result dengan cara yang sama seperti capture cursor operasi `.Find()`.
+
+Pipeline sendiri bisa dituliskan langsung dalam `[]bson.M`, atau bisa tulis dalam bentuk string dan unmarshal ke `[]bson.M`.
+
+```go
+pipeline := make([]bson.M, 0)
+err = bson.UnmarshalExtJSON([]byte(strings.TrimSpace(`
+ [
+ { "$group": {
+ "_id": null,
+ "Total": { "$sum": 1 }
+ } },
+ { "$project": {
+ "Total": 1,
+ "_id": 0
+ } }
+ ]
+`)), true, &pipeline)
+if err != nil {
+ log.Fatal(err.Error())
+}
+```
+
+Pada kode lanjutan berikut, method `.Aggregate()` dipanggil dan disisipkan pipeline-nya.
+
+```go
+csr, err := db.Collection("student").Aggregate(ctx, pipeline)
+if err != nil {
+ log.Fatal(err.Error())
+}
+defer csr.Close(ctx)
+
+result := make([]bson.M, 0)
+for csr.Next(ctx) {
+ var row bson.M
+ err := csr.Decode(&row)
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ result = append(result, row)
+}
+
+if len(result) > 0 {
+ fmt.Println("Total :", result[0]["Total"])
+}
+```
+
+---
+
+- [Mongo Go Driver](https://github.com/mongodb/mongo-go-driver), by MongoDB Team, Apache-2.0 license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From b69469297e4118d86de550237d7a730845c3ed97 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:36 +0700
Subject: [PATCH 039/522] New translations a-mutex.md (English)
---
en/content-en/A-mutex.md | 181 +++++++++++++++++++++++++++++++++++++++
1 file changed, 181 insertions(+)
create mode 100644 en/content-en/A-mutex.md
diff --git a/en/content-en/A-mutex.md b/en/content-en/A-mutex.md
new file mode 100644
index 000000000..910ce03a0
--- /dev/null
+++ b/en/content-en/A-mutex.md
@@ -0,0 +1,181 @@
+# A.60. Mutex
+
+Sebelum kita membahas mengenai apa itu **mutex**? ada baiknya untuk mempelajari terlebih dahulu apa itu **race condition**, karena kedua konsep ini berhubungan erat satu sama lain.
+
+Race condition adalah kondisi di mana lebih dari satu goroutine, mengakses data yang sama pada waktu yang bersamaan (benar-benar bersamaan). Ketika hal ini terjadi, nilai data tersebut akan menjadi kacau. Dalam **concurrency programming** situasi seperti ini ini sering terjadi.
+
+Mutex melakukan pengubahan level akses sebuah data menjadi eksklusif, menjadikan data tersebut hanya dapat dikonsumsi (read / write) oleh satu buah goroutine saja. Ketika terjadi race condition, maka hanya goroutine yang beruntung saja yang bisa mengakses data tersebut. Goroutine lain (yang waktu running nya kebetulan bersamaan) akan dipaksa untuk menunggu, hingga goroutine yang sedang memanfaatkan data tersebut selesai.
+
+Go menyediakan `sync.Mutex` yang bisa dimanfaatkan untuk keperluan **lock** dan **unlock** data. Pada chapter ini kita akan membahas mengenai race condition dan cara mengatasinya menggunakan mutex.
+
+## A.60.1. Persiapan
+
+Pertama siapkan struct baru bernama `counter`, dengan isi satu buah property `val` bertipe `int`. Property ini nantinya dikonsumsi dan diolah oleh banyak goroutine.
+
+Lalu buat beberapa method struct `counter`.
+
+ 1. Method `Add()`, untuk increment nilai.
+ 2. Method `Value()`, untuk mengembalikan nilai.
+
+```go
+package main
+
+import (
+ "fmt"
+ "runtime"
+ "sync"
+)
+
+type counter struct {
+ val int
+}
+
+func (c *counter) Add(int) {
+ c.val++
+}
+
+func (c *counter) Value() (int) {
+ return c.val
+}
+```
+
+Kode di atas kita gunakan sebagai template contoh source code yang ada pada chapter ini.
+
+## A.60.2. Contoh Race Condition
+
+Program berikut merupakan contoh program yang di dalamnya memungkinkan terjadi race condition atau kondisi goroutine balapan.
+
+> Pastikan jumlah core prosesor komputer anda adalah lebih dari satu. Karena contoh pada chapter ini hanya akan berjalan sesuai harapan jika `GOMAXPROCS` > 1.
+
+```go
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var wg sync.WaitGroup
+ var meter counter
+
+ for i := 0; i < 1000; i++ {
+ wg.Add(1)
+
+ go func() {
+ for j := 0; j < 1000; j++ {
+ meter.Add(1)
+ }
+
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+ fmt.Println(meter.Value())
+}
+```
+
+Pada kode di atas, disiapkan sebuah instance `sync.WaitGroup` bernama `wg`, dan variabel object `meter` bertipe `counter` (nilai property `val` default-nya adalah **0**).
+
+Setelahnya dijalankan perulangan sebanyak 1000 kali, yang ditiap perulanganya dijalankan sebuah goroutine baru. Di dalam goroutine tersebut, terdapat perulangan lagi, sebanyak 1000 kali. Dalam perulangan tersebut nilai property `val` dinaikkan sebanyak 1 lewat method `Add()`.
+
+Dengan demikian, ekspektasi nilai akhir `meter.val` harusnya adalah 1000000.
+
+Di akhir, `wg.Wait()` dipanggil, dan nilai variabel counter `meter` diambil lewat `meter.Value()` untuk kemudian ditampilkan.
+
+Jalankan program, lihat hasilnya.
+
+
+
+Nilai `meter.val` tidak genap 1000000? kenapa bisa begitu? Padahal seharusnya tidak ada masalah dalam kode yang kita tulis di atas.
+
+Inilah yang disebut dengan race condition, data yang diakses bersamaan dalam 1 waktu menjadi kacau.
+
+## A.60.3. Deteksi Race Condition Menggunakan Go Race Detector
+
+Go menyediakan fitur untuk [deteksi race condition](http://blog.golang.org/race-detector). Cara penggunaannya adalah dengan menambahkan flag `-race` pada saat eksekusi aplikasi.
+
+
+
+Terlihat pada gambar di atas, ada pesan memberitahu terdapat kemungkinan data race pada program yang kita jalankan.
+
+## A.60.4. Penerapan `sync.Mutex`
+
+Sekarang kita tahu bahwa program di atas menghasilkan bug, ada kemungkinan data race di dalamnya. Untuk mengatasi masalah ini ada beberapa cara yang bisa digunakan, dan di sini kita akan menggunakan `sync.Mutex`.
+
+Ubah kode di atas, embed struct `sync.Mutex` ke dalam struct `counter`, agar lewat objek cetakan `counter` kita bisa melakukan lock dan unlock dengan mudah. Tambahkan method `Lock()` dan `Unlock()` di dalam method `Add()`.
+
+```go
+type counter struct {
+ sync.Mutex
+ val int
+}
+
+func (c *counter) Add(int) {
+ c.Lock()
+ c.val++
+ c.Unlock()
+}
+
+func (c *counter) Value() (int) {
+ return c.val
+}
+```
+
+Method `Lock()` digunakan untuk menandai bahwa semua operasi pada baris setelah kode tersebut adalah bersifat eksklusif. Hanya ada satu buah goroutine yang bisa melakukannya dalam satu waktu. Jika ada banyak goroutine yang eksekusinya bersamaan, harus antri.
+
+Pada kode di atas terdapat kode untuk increment nilai `meter.val`. Maka property tersebut hanya bisa diakses oleh satu goroutine saja.
+
+Method `Unlock()` akan membuka kembali akses operasi ke property/variabel yang di lock, proses mutual exclusion terjadi di antara method `Lock()` dan `Unlock()`.
+
+Di contoh di atas, pada saat bagian pengambilan nilai, mutex tidak dipasang, karena kebetulan pengambilan nilai terjadi setelah semua goroutine selesai. Data Race bisa terjadi saat pengubahan maupun pengambilan data, jadi penggunaan mutex harus disesuaikan dengan kasus.
+
+Coba jalankan program, dan lihat hasilnya.
+
+
+
+Pada contoh di atas, mutex diterapkan dengan cara di-embed ke objek yang memerlukan proses lock-unlock, menjadikan variabel mutex tersebut adalah eksklusif untuk objek tersebut saja. Cara ini merupakan cara yang dianjurkan. Meskipun demikian, mutex tetap bisa digunakan dengan cara tanpa ditempelkan ke objek yang memerlukan lock-unlock. Contohnya bisa dilihat di bawah ini.
+
+```go
+func (c *counter) Add(int) {
+ c.val++
+}
+
+func (c *counter) Value() (int) {
+ return c.val
+}
+
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var wg sync.WaitGroup
+ var mtx sync.Mutex
+ var meter counter
+
+ for i := 0; i < 1000; i++ {
+ wg.Add(1)
+
+ go func() {
+ for j := 0; j < 1000; j++ {
+ mtx.Lock()
+ meter.Add(1)
+ mtx.Unlock()
+ }
+
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+ fmt.Println(meter.Value())
+}
+```
+
+> `sync.Mutex` merupakan salah satu tipe yang *thread safe*. Kita tidak perlu khawatir terhadap potensi *race condition* karena variabel bertipe ini aman untuk digunakan di banyak goroutine secara paralel.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 49bacf46fc4e435b66dbe832ccfd93ac69468ec7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:37 +0700
Subject: [PATCH 040/522] New translations a-operator.md (English)
---
en/content-en/A-operator.md | 102 ++++++++++++++++++++++++++++++++++++
1 file changed, 102 insertions(+)
create mode 100644 en/content-en/A-operator.md
diff --git a/en/content-en/A-operator.md b/en/content-en/A-operator.md
new file mode 100644
index 000000000..4d3675421
--- /dev/null
+++ b/en/content-en/A-operator.md
@@ -0,0 +1,102 @@
+# A.12. Operator
+
+Chapter ini membahas mengenai macam operator yang bisa digunakan di Go. Secara umum terdapat 3 kategori operator: aritmatika, perbandingan, dan logika.
+
+## A.12.1. Operator Aritmatika
+
+Operator aritmatika adalah operator yang digunakan untuk operasi yang sifatnya perhitungan. Go mendukung beberapa operator aritmatika standar, list-nya bisa dilihat di tabel berikut.
+
+| Tanda | Penjelasan |
+|:-----:|:------------------------------ |
+| `+` | penjumlahan |
+| `-` | pengurangan |
+| `*` | perkalian |
+| `/` | pembagian |
+| `%` | modulus / sisa hasil pembagian |
+
+Contoh penggunaan:
+
+```go
+var value = (((2 + 6) % 3) * 4 - 2) / 3
+```
+
+## A.12.2. Operator Perbandingan
+
+Operator perbandingan digunakan untuk menentukan kebenaran suatu kondisi. Hasilnya berupa nilai boolean, `true` atau `false`.
+
+Tabel di bawah ini berisikan operator perbandingan yang bisa digunakan di Go.
+
+| Tanda | Penjelasan |
+|:-------:|:-------------------------------------------------------------- |
+| `==` | apakah nilai kiri **sama dengan** nilai kanan |
+| `!=` | apakah nilai kiri **tidak sama dengan** nilai kanan |
+| `<` | apakah nilai kiri **lebih kecil daripada** nilai kanan |
+| `<=` | apakah nilai kiri **lebih kecil atau sama dengan** nilai kanan |
+| `>` | apakah nilai kiri **lebih besar dari** nilai kanan |
+| `>=` | apakah nilai kiri **lebih besar atau sama dengan** nilai kanan |
+
+Contoh penggunaan:
+
+```go
+var value = (((2 + 6) % 3) * 4 - 2) / 3
+var isEqual = (value == 2)
+
+fmt.Printf("nilai %d (%t) \n", value, isEqual)
+```
+
+Pada kode di atas, terdapat statement operasi aritmatika yang hasilnya ditampung oleh variabel `value`. Selanjutnya, variabel tersebut dibandingkan dengan angka **2** untuk dicek apakah nilainya sama. Jika iya, maka hasilnya adalah `true`, jika tidak maka `false`. Nilai hasil operasi perbandingan tersebut kemudian disimpan dalam variabel `isEqual`.
+
+
+
+Untuk memunculkan nilai `bool` menggunakan `fmt.Printf()`, bisa gunakan layout format `%t`.
+
+## A.12.3. Operator Logika
+
+Operator ini digunakan untuk mencari benar tidaknya kombinasi data bertipe `bool` (bisa berupa variabel bertipe `bool`, atau hasil dari operator perbandingan).
+
+Beberapa operator logika standar yang bisa digunakan:
+
+| Tanda | Penjelasan |
+|:-------------------------:|:------------------------ |
+| `&&` | kiri **dan** kanan |
+| || | kiri **atau** kanan |
+| `!` | negasi / nilai kebalikan |
+
+Contoh penggunaan:
+
+```go
+var left = false
+var right = true
+
+var leftAndRight = left && right
+fmt.Printf("left && right \t(%t) \n", leftAndRight)
+
+var leftOrRight = left || right
+fmt.Printf("left || right \t(%t) \n", leftOrRight)
+
+var leftReverse = !left
+fmt.Printf("!left \t\t(%t) \n", leftReverse)
+```
+
+Hasil dari operator logika sama dengan hasil dari operator perbandingan, yaitu berupa boolean.
+
+
+
+Berikut penjelasan statemen operator logika pada kode di atas.
+
+ - `leftAndRight` bernilai `false`, karena hasil dari `false` **dan** `true` adalah `false`.
+ - `leftOrRight` bernilai `true`, karena hasil dari `false` **atau** `true` adalah `true`.
+ - `leftReverse` bernilai `true`, karena **negasi** (atau lawan dari) `false` adalah `true`.
+
+Template `\t` digunakan untuk menambahkan indent tabulasi. Biasa dimanfaatkan untuk merapikan tampilan output pada console.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 6cc7d47faee4fce5ec95a5e6daf778c971eaaee4 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:38 +0700
Subject: [PATCH 041/522] New translations a-perulangan.md (English)
---
en/content-en/A-perulangan.md | 166 ++++++++++++++++++++++++++++++++++
1 file changed, 166 insertions(+)
create mode 100644 en/content-en/A-perulangan.md
diff --git a/en/content-en/A-perulangan.md b/en/content-en/A-perulangan.md
new file mode 100644
index 000000000..7a935f0b4
--- /dev/null
+++ b/en/content-en/A-perulangan.md
@@ -0,0 +1,166 @@
+# A.14. Perulangan
+
+Perulangan adalah proses mengulang-ulang eksekusi blok kode tanpa henti, selama kondisi yang dijadikan acuan terpenuhi. Biasanya disiapkan variabel untuk iterasi atau variabel penanda kapan perulangan akan diberhentikan.
+
+Di Go keyword perulangan hanya **for** saja, tetapi meski demikian, kemampuannya merupakan gabungan `for`, `foreach`, dan `while` ibarat bahasa pemrograman lain.
+
+## A.14.1. Perulangan Menggunakan Keyword `for`
+
+Ada beberapa cara standar menggunakan `for`. Cara pertama dengan memasukkan variabel counter perulangan beserta kondisinya setelah keyword. Perhatikan dan praktekan kode berikut.
+
+```go
+for i := 0; i < 5; i++ {
+ fmt.Println("Angka", i)
+}
+```
+
+Perulangan di atas hanya akan berjalan ketika variabel `i` bernilai di bawah `5`, dengan ketentuan setiap kali perulangan, nilai variabel `i` akan di-iterasi atau ditambahkan 1 (`i++` artinya ditambah satu, sama seperti `i = i + 1`). Karena `i` pada awalnya bernilai 0, maka perulangan akan berlangsung 5 kali, yaitu ketika `i` bernilai 0, 1, 2, 3, dan 4.
+
+
+
+## A.14.2. Penggunaan Keyword `for` Dengan Argumen Hanya Kondisi
+
+Cara ke-2 adalah dengan menuliskan kondisi setelah keyword `for` (hanya kondisi). Deklarasi dan iterasi variabel counter tidak dituliskan setelah keyword, hanya kondisi perulangan saja. Konsepnya mirip seperti `while` milik bahasa pemrograman lain.
+
+Kode berikut adalah contoh `for` dengan argumen hanya kondisi (seperti `if`), output yang dihasilkan sama seperti penerapan `for` cara pertama.
+
+```go
+var i = 0
+
+for i < 5 {
+ fmt.Println("Angka", i)
+ i++
+}
+```
+
+## A.14.3. Penggunaan Keyword `for` Tanpa Argumen
+
+Cara ke-3 adalah `for` ditulis tanpa kondisi. Dengan ini akan dihasilkan perulangan tanpa henti (sama dengan `for true`). Pemberhentian perulangan dilakukan dengan menggunakan keyword `break`.
+
+```go
+var i = 0
+
+for {
+ fmt.Println("Angka", i)
+
+ i++
+ if i == 5 {
+ break
+ }
+}
+```
+
+Dalam perulangan tanpa henti di atas, variabel `i` yang nilai awalnya `0` di-inkrementasi. Ketika nilai `i` sudah mencapai `5`, keyword `break` digunakan, dan perulangan akan berhenti.
+
+## A.14.4. Penggunaan Keyword `for` - `range`
+
+Cara ke-4 adalah perulangan dengan menggunakan kombinasi keyword `for` dan `range`. Cara ini biasa digunakan untuk me-looping data gabungan (misalnya string, array, slice, map). Detailnya akan dibahas dalam chapter-chapter selanjutnya ([A.15. Array](/A-array.html), [A.16. Slice](/A-slice.html), [A.17. Map](/A-map.html)).
+
+```go
+var xs = "123" // string
+for i, v := range xs {
+ fmt.Println("Index=", i, "Value=", v)
+}
+
+var ys = [5]int{10, 20, 30, 40, 50} // array
+for _, v := range ys {
+ fmt.Println("Value=", v)
+}
+
+var zs = ys[0:2] // slice
+for _, v := range zs {
+ fmt.Println("Value=", v)
+}
+
+var kvs = map[byte]int{'a': 0, 'b': 1, 'c': 2} // map
+for k, v := range kvs {
+ fmt.Println("Key=", k, "Value=", v)
+}
+
+// boleh juga baik k dan atau v nya diabaikan
+for range kvs {
+ fmt.Println("Done")
+}
+```
+
+## A.14.5. Penggunaan Keyword `break` & `continue`
+
+Keyword `break` digunakan untuk menghentikan secara paksa sebuah perulangan, sedangkan `continue` dipakai untuk memaksa maju ke perulangan berikutnya.
+
+Berikut merupakan contoh penerapan `continue` dan `break`. Kedua keyword tersebut dimanfaatkan untuk menampilkan angka genap berurutan yang lebih besar dari 0 dan kurang dari atau sama dengan 8.
+
+```go
+for i := 1; i <= 10; i++ {
+ if i % 2 == 1 {
+ continue
+ }
+
+ if i > 8 {
+ break
+ }
+
+ fmt.Println("Angka", i)
+}
+```
+
+Kode di atas akan lebih mudah dicerna jika dijelaskan secara berurutan. Berikut adalah penjelasannya.
+
+ 1. Dilakukan perulangan mulai angka 1 hingga 10 dengan `i` sebagai variabel iterasi.
+ 2. Ketika `i` adalah ganjil (dapat diketahui dari `i % 2`, jika hasilnya `1`, berarti ganjil), maka akan dipaksa lanjut ke perulangan berikutnya.
+ 3. Ketika `i` lebih besar dari 8, maka perulangan akan berhenti.
+ 4. Nilai `i` ditampilkan.
+
+
+
+## A.14.6. Perulangan Bersarang
+
+Tak hanya seleksi kondisi yang bisa bersarang, perulangan juga bisa. Cara pengaplikasiannya kurang lebih sama, tinggal tulis blok statement perulangan di dalam perulangan.
+
+```go
+for i := 0; i < 5; i++ {
+ for j := i; j < 5; j++ {
+ fmt.Print(j, " ")
+ }
+
+ fmt.Println()
+}
+```
+
+Pada kode di atas, untuk pertama kalinya fungsi `fmt.Println()` dipanggil tanpa disisipkan parameter. Cara seperti ini bisa digunakan untuk menampilkan baris baru. Kegunaannya sama seperti output dari statement `fmt.Print("\n")`.
+
+
+
+## A.14.7. Pemanfaatan Label Dalam Perulangan
+
+Di perulangan bersarang, `break` dan `continue` akan berlaku pada blok perulangan di mana ia digunakan saja. Ada cara agar kedua keyword ini bisa tertuju pada perulangan terluar atau perulangan tertentu, yaitu dengan memanfaatkan teknik pemberian **label**.
+
+Program untuk memunculkan matriks berikut merupakan contoh penerapan label perulangan.
+
+```go
+outerLoop:
+for i := 0; i < 5; i++ {
+ for j := 0; j < 5; j++ {
+ if i == 3 {
+ break outerLoop
+ }
+ fmt.Print("matriks [", i, "][", j, "]", "\n")
+ }
+}
+```
+
+Tepat sebelum keyword `for` terluar, terdapat baris kode `outerLoop:`. Maksud dari kode tersebut adalah disiapkan sebuah label bernama `outerLoop` untuk `for` di bawahnya. Nama label bisa diganti dengan nama lain (dan harus diakhiri dengan tanda titik dua atau *colon* (`:`) ).
+
+Pada `for` bagian dalam, terdapat seleksi kondisi untuk pengecekan nilai `i`. Ketika nilai tersebut sama dengan `3`, maka `break` dipanggil dengan target adalah perulangan yang dilabeli `outerLoop`, perulangan tersebut akan dihentikan.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 2b3da62ae889daadb2b73ac9731a016ee24a7838 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:39 +0700
Subject: [PATCH 042/522] New translations a-pipeline-context-cancellation.md
(English)
---
.../A-pipeline-context-cancellation.md | 458 ++++++++++++++++++
1 file changed, 458 insertions(+)
create mode 100644 en/content-en/A-pipeline-context-cancellation.md
diff --git a/en/content-en/A-pipeline-context-cancellation.md b/en/content-en/A-pipeline-context-cancellation.md
new file mode 100644
index 000000000..b0ed84d02
--- /dev/null
+++ b/en/content-en/A-pipeline-context-cancellation.md
@@ -0,0 +1,458 @@
+# A.64. Concurrency Pattern: Context Cancellation Pipeline
+
+Pada chapter ini kita akan belajar tentang salah satu *concurrency pattern* di Go, yaitu **cancellation**. Cancellation merupakan mekanisme untuk menggagalkan secara paksa proses konkuren yang sedang berjalan, entah itu karena ada timeout, ada error, atau ada faktor lain.
+
+Di sini kita akan gunakan salah satu API milik Go yang tersedia untuk *cancellation*, yaitu `context.Context`.
+
+Context digunakan untuk mendefinisikan tipe *context* yang di dalamnya ada beberapa hal yaitu: informasi *deadlines*, signal *cancellation*, dan data untuk keperluan komunikasi antar API atau antar proses.
+
+## A.64.1. Skenario Praktek
+
+Kita akan modifikasi file program `1-generate-dummy-files-concurrently.go` yang pada chapter sebelumnya ([A.63. Concurrency Pattern: Simplified Fan-in Fan-out Pipeline](/A-simplified-fan-in-fan-out-pipeline.html)) sudah dibuat. Pada program tersebut akan kita tambahkan mekanisme cancellation ketika ada timeout.
+
+Jadi kurang lebih akan ada dua result:
+
+- Proses sukses, karena *execution time* di bawah timeout.
+- Proses digagalkan secara paksa ditengah jalan, karena *running time* sudah melebihi batas timeout.
+
+## A.64.2. Program Generate Dummy File *Concurrently*
+
+Ok langsung saja, pertama yang perlu dipersiapkan adalah tulis dulu kode program versi *concurrent* tanpa *cancellation*. Bisa langsung copy-paste, atau tulis dari awal dengan mengikut tutorial ini secara keseluruhan. Untuk penjelasan detail program versi sekuensial silakan merujuk ke chapter sebelumnya saja, di sini kita tulis langsung agar bisa cepat dimulai bagian program konkuren.
+
+#### • Import Packages dan Definisi Variabel
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+const totalFile = 3000
+const contentLength = 5000
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.61-pipeline-cancellation-context")
+```
+
+#### • Definisi struct `FileInfo`
+
+```go
+type FileInfo struct {
+ Index int
+ FileName string
+ WorkerIndex int
+ Err error
+}
+```
+
+#### • Fungsi `main()`
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ generateFiles()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+#### • Fungsi `randomString()`
+
+```go
+func randomString(length int) string {
+ randomizer := rand.New(rand.NewSource(time.Now().Unix()))
+ letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, length)
+ for i := range b {
+ s := randomizer.Intn(len(letters))
+ b[i] = letters[s]
+ }
+
+ return string(b)
+}
+```
+
+#### • Fungsi `generateFiles()`
+
+```go
+func generateFiles() {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ // pipeline 1: job distribution
+ chanFileIndex := generateFileIndexes()
+
+ // pipeline 2: the main logic (creating files)
+ createFilesWorker := 100
+ chanFileResult := createFiles(chanFileIndex, createFilesWorker)
+
+ // track and print output
+ counterTotal := 0
+ counterSuccess := 0
+ for fileResult := range chanFileResult {
+ if fileResult.Err != nil {
+ log.Printf("error creating file %s. stack trace: %s", fileResult.FileName, fileResult.Err)
+ } else {
+ counterSuccess++
+ }
+ counterTotal++
+ }
+
+ log.Printf("%d/%d of total files created", counterSuccess, counterTotal)
+}
+```
+#### • Fungsi `generateFileIndexes()`
+
+```go
+func generateFileIndexes() <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for i := 0; i < totalFile; i++ {
+ chanOut <- FileInfo{
+ Index: i,
+ FileName: fmt.Sprintf("file-%d.txt", i),
+ }
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+#### • Fungsi `createFiles()`
+
+```go
+func createFiles(chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ wg := new(sync.WaitGroup)
+ wg.Add(numberOfWorkers)
+
+ go func() {
+ for workerIndex := 0; workerIndex < numberOfWorkers; workerIndex++ {
+ go func(workerIndex int) {
+ for job := range chanIn {
+ filePath := filepath.Join(tempPath, job.FileName)
+ content := randomString(contentLength)
+ err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+
+ log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
+
+ chanOut <- FileInfo{
+ FileName: job.FileName,
+ WorkerIndex: workerIndex,
+ Err: err,
+ }
+ }
+
+ wg.Done()
+ }(workerIndex)
+ }
+ }()
+
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Hasil eksekusi program:
+
+
+
+## A.64.3. Program Generate Dummy File *Concurrently* dan Mekanisme *Cancellation*
+
+Ok, sekarang kita akan refactor kode tersebut, kita tambahkan mekanisme *cancellation* menggunakan `context.Context` API. Silakan duplikasi file program, lalu ikuti petunjuk berikut.
+
+#### • Import package `context`
+
+Tambahkan package `context` dalam block import packages.
+
+```go
+import (
+ "context"
+
+ // ...
+)
+```
+
+#### • Tambahkan definisi konstanta timeout
+
+Di sini saya tentukan timeout adalah 3 detik. Nantinya kita akan modifikasi angka timeout untuk keperluan testing.
+
+```go
+const timeoutDuration = 3 * time.Second
+```
+
+#### • Penerapan context di fungsi `main()`
+
+Pada fungsi main, lakukan sedikit perubahan. Yang sebelumnya ada statement berikut:
+
+```go
+generateFiles()
+```
+
+Ubah menjadi berikut:
+
+```go
+ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
+defer cancel()
+generateFilesWithContext(ctx)
+```
+
+Fungsi `generateFilesWithContext()` merupakan fungsi yang sama persis dengan `generateFiles()` (yang tentunya akan kita buat). Perbedaannya adalah hanya pada fungsi baru ini ada satu argument baru yaitu data `context.Context`.
+
+Ini merupakan salah satu idiomatic Go untuk cara penulisan fungsi yang *cancellable*. Umumnya akan ada fungsi tanpa context dan fungsi yang ada context-nya. Contohnya seperti berikut:
+
+```go
+generateFiles()
+generateFilesWithContext(ctx)
+```
+
+Dimisalkan lagi jika argument context adalah wajib pada sebuah fungsi, maka cukup gunakan 1 fungsi saja, yang ada `WithContext()`-nya dihapus, tapi satu fungsi yang ada ditambahkan context. Contohnya:
+
+```go
+generateFiles(ctx)
+```
+
+Pada contoh ini kita akan siapkan dua fungsi, yang ada context-nya dan yang tidak.
+
+Ok lanjut ke pembahasan. Fungsi `context.WithTimeout` digunakan untuk menambahkan timeout pada sebuah context. Parameter pertama fungsi ini adalah objek context juga. Pada contoh di atas, karena sebelumnya belum ada objek context, maka kita buat objek context baru lewat `context.Background()`.
+
+Cara pembuatan object context sendiri sebenarnya ada 3:
+
+1. Menggunakan fungsi `context.Background()`. Fungsi tersebut menghasilkan objek context yang data di dalamnya adalah kosong dan tidak memiliki deadline. Context ini biasanya digunakan untuk inisialisasi object context baru yang nanti akan di-chain dengan fungsi `context.With...`.
+2. Menggunakan fungsi `context.TODO()`. Fungsi ini menghasilkan objek context baru seperti `context.Background()`. Context buatan fungsi TODO ini biasanya digunakan dalam situasi ketika belum jelas nantinya harus menggunakan jenis context apa (apakah dengan timeout, apakah dengan cancel).
+3. Menggunakan fungsi `context.With...`. Fungsi ini sebenarnya bukan digunakan untuk inisialisasi objek konteks baru, tapi digunakan untuk menambahkan informasi tertentu pada *copied* context yang disisipkan di parameter pertama pemanggilan fungsi. Ada 3 buah fungsi `context.With...` yang bisa digunakan, yaitu:
+
+ - Fungsi `context.WithCancel(ctx) (ctx, cancel)`. Fungsi ini digunakan untuk menambahkan fasilitas *cancellable* pada context yang disisipkan sebagai parameter pertama pemanggilan fungsi. Lewat nilai balik kedua, yaitu `cancel` yang tipenya `context.CancelFunc`, kita bisa secara paksa meng-*cancel* context ini.
+ - Fungsi `context.WithDeadline(ctx, time.Time) (ctx, cancel)`. Fungsi ini juga menambahkan fitur *cancellable* pada context, tapi selain itu juga menambahkan informasi deadline yang di mana jika waktu sekarang sudah melebihi deadline yang sudah ditentukan maka context otomatis di-cancel secara paksa.
+ - Fungsi `context.WithTimeout(ctx, time.Duration) (ctx, cancel)`. Fungsi ini sama seperti `context.WithDeadline()`, bedanya pada parameter kedua argument bertipe durasi (bukan objek `time.Time`).
+
+Kesamaan dari ketiga fungsi `context.With...` adalah sama-sama menambahkan fasilitas *cancellable* yang bisa dieksekusi lewat nilai balik kedua fungsi tersebut (yang tipenya `context.CancelFunc`).
+
+Jadi pada contoh yang kita tulis di atas, kurang lebih yang akan dilakukan adalah:
+
+* Kira buat object context baru lewat `context.Background()`.
+* Objek context tersebut ditambahkan fasilitas *cancellable* di dalamnya, dan juga auto cancel ketika timeout menggunakan `context.WithTimeout()`, dengan durasi timeout adalah `timeoutDuration`.
+* Fungsi `generateFilesWithContext()` dipanggil dengan disisipkan object context.
+* Callback `context.CancelFunc` dipanggil secara deferred. Ini merupakan idiomatic Go dalam penerapan context. Meskipun context sudah punya timeout atau deadline dan kita tidak perlu meng-*cancel* context secara manual, sangat dianjurkan untuk tetap memanggil callback `cancel()` tersebut secara deferred.
+
+#### • Modifikasi fungsi `generateFiles()`
+
+Isi dari fungsi `generateFiles()` kita ubah menjadi pemanggilan fungsi `generateFilesWithContext()` dengan parameter context kosong.
+
+```go
+func generateFiles() {
+ generateFilesWithContext(context.Background())
+}
+```
+
+Pada fungsi `generateFilesWithContext()` sendiri, isinya adalah isi `generateFiles()` sebelumnya tapi ditambahkan beberapa hal. Silakan tulis dulu kode berikut.
+
+```go
+func generateFilesWithContext(ctx context.Context) {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ done := make(chan int)
+
+ go func() {
+ // pipeline 1: job distribution
+ chanFileIndex := generateFileIndexes(ctx)
+
+ // pipeline 2: the main logic (creating files)
+ createFilesWorker := 100
+ chanFileResult := createFiles(ctx, chanFileIndex, createFilesWorker)
+
+ // track and print output
+ counterSuccess := 0
+ for fileResult := range chanFileResult {
+ if fileResult.Err != nil {
+ log.Printf("error creating file %s. stack trace: %s", fileResult.FileName, fileResult.Err)
+ } else {
+ counterSuccess++
+ }
+ }
+
+ // notify that the process is complete
+ done <- counterSuccess
+ }()
+
+ select {
+ case <-ctx.Done():
+ log.Printf("generation process stopped. %s", ctx.Err())
+ case counterSuccess := <-done:
+ log.Printf("%d/%d of total files created", counterSuccess, totalFile)
+ }
+}
+```
+
+Penambahan yang dimaksud adalah, statement pipelines dibungkus dengan sebuah goroutine IIFE, yang di akhir fungsi kita kirim informasi jumlah file yang berhasil di-generate (`counterSuccess`) ke sebuah channel bernama `done`.
+
+Channel `done` ini kita gunakan sebagai indikator bahwa proses pipeline sudah selesai secara keseluruhan.
+
+Selain goroutine, di akhir fungsi `generateFilesWithContext()` sendiri ditambahkan *channel selection* dengan isi dua buah cases.
+
+Case pertama adalah ketika channel done pada context `ctx` menerima data. Cara penggunaannya seperti ini `<-ctx.Done()`. Ketika channel done milik context ini menerima data, berarti context telah di-cancel secara paksa. Cancel-nya bisa karena memang context melebihi timeout yang sudah ditentukan, atau di-cancel secara eksplisit lewat pemanggilan callback `context.CancelFunc`. Untuk mengetahui alasan cancel bisa dengan cara mengakses method error milik contex, yaitu: `ctx.Err()`.
+
+Jadi pada contoh di atas, ketika context timeout atau di-cancel secara eksplisit (via callback `cancel`), maka case pertama akan terpenuhi dan message ditampilkan.
+
+Untuk case kedua akan terpenuhi ketika proses pipeline sudah selesai secara keseluruhan. Bisa dilihat di akhir goroutine, di situ channel `done` dikirimi informasi `counterSuccess`. Ketika ini terjadi maka kondisi case kedua terpenuhi, lalu ditampilkan informasi file yang sudah sukses dibuat.
+
+Nah jadi lewat seleksi kondisi 2 case di atas, kita bisa dengan mudah mengidentifikasi apakah proses selesai sepenuhnya, ataukah cancelled ditengah jalan karena timeout ataupun karena di-cancel secara eksplisit.
+
+Selain beberapa hal yang sudah saya sampaikan, ada *minor changes* lainnya, yaitu pada pemanggilan fungsi `generateFileIndexes()` dan `createFiles()` ditambahkan argument context.
+
+#### • Penambahan context pada fungsi `generateFiles()`
+
+Kenapa ini perlu? karena **meski eksekusi fungsi `generateFilesWithContext()` otomatis di stop ketika cancelled, proses di dalamnya akan tetap berjalan jika tidak di-*handle* dengan baik *cancellation*-nya.**
+
+Maka dari itu kita perlu memodifikasi, memastikan bahwa cancellation juga diberlakukan dalam level sub proses.
+
+Silakan tulis kode berikut pada fungsi `generateFileIndexes()`.
+
+```go
+func generateFileIndexes(ctx context.Context) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for i := 0; i < totalFile; i++ {
+ select {
+ case <-ctx.Done():
+ break
+ default:
+ chanOut <- FileInfo{
+ Index: i,
+ FileName: fmt.Sprintf("file-%d.txt", i),
+ }
+ }
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Dibanding sebelumnya, perbedaannya adalah ada *channel selection*. Jadi di bagian pengiriman jobs, ketika sebelum semua jobs dikirim tapi ada notif untuk cancel maka kita akan skip pengiriman *remainin* jobs secara paksa.
+
+* Jika ada notif cancel paksa, maka case pertama akan terpenuhi, dan perulangan di-`break`.
+* Selebihnya, pengiriman jobs akan berlangsung seperti normalnya.
+
+#### • Penambahan context pada fungsi `createFiles()`
+
+Hal yang sama (cancel di level sub prosees) juga perlu diterapkan pada `createFiles()`, karena jika tidak, maka proses pembuatan file akan tetap berjalan sesuai dengan jumlah jobs yang dikirim meskipun sudah di-cancel secara paksa.
+
+Sebenarnya penambahan cancellation pada fungsi `generateFiles()` sudah cukup, karena ketika cancelled maka sisa jobs tidak akan dikirim. Tapi pada contoh ini penulis ingin ketika cancelled, maka tidak hanya pengiriman jobs tapi eksekusi jobs juga di-stop secara paksa (meski mungkin masih ada sebagian jobs yang masih dalam antrian).
+
+Silakan tulis kode berikut.
+
+```go
+func createFiles(ctx context.Context, chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ wg := new(sync.WaitGroup)
+ wg.Add(numberOfWorkers)
+
+ go func() {
+ for workerIndex := 0; workerIndex < numberOfWorkers; workerIndex++ {
+ go func(workerIndex int) {
+ for job := range chanIn {
+ select {
+ case <-ctx.Done():
+ break
+ default:
+ filePath := filepath.Join(tempPath, job.FileName)
+ content := randomString(contentLength)
+ err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+
+ log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
+
+ chanOut <- FileInfo{
+ FileName: job.FileName,
+ WorkerIndex: workerIndex,
+ Err: err,
+ }
+ }
+ }
+
+ wg.Done()
+ }(workerIndex)
+ }
+ }()
+
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Penambahannya juga sama seperti fungsi-fungsi yang lain, yaitu dengan menambahkan *channel selection*. Ketika ada notifikasi cancel maka perulangan jobs di break. Selebihnya maka harus berjalan seperti normalnya.
+
+## A.64.4. Test Eksekusi Program
+
+Jalankan program ke-dua, lihat hasilnya. Karena sebelumnya kita sudah set durasi timeout adalah **3 detik**, maka jika proses belum selesai sebelum durasi tersebut akan di-cancel secara paksa.
+
+
+
+Cukup mudah bukan?
+
+Silakan coba modifikasi durasinya dengan nilai lebih besar, misalnya **15 detik**, lalu coba jalankan.
+
+
+
+Bisa dilihat, di gambar di atas, jika program selesai sebelum **15 detik** maka aman.
+
+## A.64.5. Cancel Context Secara Paksa (Tanpa Timeout)
+
+Coba lakukan modifikasi sedikit pada bagian pemanggilan generate files, dari:
+
+```go
+ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
+defer cancel()
+generateFilesWithContext(ctx)
+```
+
+Ke:
+
+```go
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel()
+time.AfterFunc(timeoutDuration, cancel)
+generateFilesWithContext(ctx)
+```
+
+Lalu coba jalankan, maka hasilnya adalah tetap sama. Jika eksekusi program melebihi context maka akan di-cancel secara paksa, selebihnya aman.
+
+Perbedannya ada pada penerapan *cancellation*-nya. Pada contoh ini kita tidak menggunakan timeout, melainkan menggunakan *explicit cancel* dengan mensimulasi timeout menggunakan `time.AfterFunc()`.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 4ad9d08aa0627141a8d7405860142d0c48f1bad1 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:39 +0700
Subject: [PATCH 043/522] New translations a-pointer.md (English)
---
en/content-en/A-pointer.md | 110 +++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
create mode 100644 en/content-en/A-pointer.md
diff --git a/en/content-en/A-pointer.md b/en/content-en/A-pointer.md
new file mode 100644
index 000000000..30112cefb
--- /dev/null
+++ b/en/content-en/A-pointer.md
@@ -0,0 +1,110 @@
+# A.23. Pointer
+
+Pointer adalah *reference* atau alamat memori. Variabel pointer berarti variabel yang berisi alamat memori suatu nilai. Sebagai contoh sebuah variabel bertipe integer memiliki nilai **4**, maka yang dimaksud pointer adalah **alamat memori di mana nilai 4 disimpan**, bukan nilai 4 itu sendiri.
+
+Variabel-variabel yang memiliki *reference* atau alamat pointer yang sama, saling berhubungan satu sama lain dan nilainya pasti sama. Ketika ada perubahan nilai, maka akan memberikan efek kepada variabel lain (yang referensi-nya sama) yaitu nilainya ikut berubah.
+
+## A.23.1. Penerapan Pointer
+
+Variabel bertipe pointer ditandai dengan adanya tanda **asterisk** (`*`) tepat sebelum penulisan tipe data ketika deklarasi.
+
+```go
+var number *int
+var name *string
+```
+
+Nilai default variabel pointer adalah `nil` (kosong). Variabel pointer tidak bisa menampung nilai yang bukan pointer, dan sebaliknya variabel biasa tidak bisa menampung nilai pointer.
+
+Ada dua hal penting yang perlu diketahui mengenai pointer:
+
+- Variabel biasa bisa diambil nilai pointernya, caranya dengan menambahkan tanda **ampersand** (`&`) tepat sebelum nama variabel. Metode ini disebut dengan **referencing**.
+- Dan sebaliknya, nilai asli variabel pointer juga bisa diambil, dengan cara menambahkan tanda **asterisk** (`*`) tepat sebelum nama variabel. Metode ini disebut dengan **dereferencing**.
+
+OK, langsung saja kita praktekan.
+
+```go
+var numberA int = 4
+var numberB *int = &numberA
+
+fmt.Println("numberA (value) :", numberA) // 4
+fmt.Println("numberA (address) :", &numberA) // 0xc20800a220
+
+fmt.Println("numberB (value) :", *numberB) // 4
+fmt.Println("numberB (address) :", numberB) // 0xc20800a220
+```
+
+Variabel `numberB` dideklarasikan bertipe pointer `int` dengan nilai awal adalah referensi variabel `numberA` (bisa dilihat pada kode `&numberA`). Dengan ini, variabel `numberA` dan `numberB` menampung data dengan referensi alamat memori yang sama.
+
+
+
+Variabel pointer jika di-print akan menghasilkan string alamat memori (dalam notasi heksadesimal), contohnya seperti `numberB` yang diprint menghasilkan `0xc20800a220`.
+
+Nilai asli sebuah variabel pointer bisa didapatkan dengan cara di-dereference terlebih dahulu (bisa dilihat pada kode `*numberB`).
+
+## A.23.2. Efek Perubahan Nilai Pointer
+
+Ketika salah satu variabel pointer di ubah nilainya, sedang ada variabel lain yang memiliki referensi memori yang sama, maka nilai variabel lain tersebut juga akan berubah.
+
+```go
+var numberA int = 4
+var numberB *int = &numberA
+
+fmt.Println("numberA (value) :", numberA)
+fmt.Println("numberA (address) :", &numberA)
+fmt.Println("numberB (value) :", *numberB)
+fmt.Println("numberB (address) :", numberB)
+
+fmt.Println("")
+
+numberA = 5
+
+fmt.Println("numberA (value) :", numberA)
+fmt.Println("numberA (address) :", &numberA)
+fmt.Println("numberB (value) :", *numberB)
+fmt.Println("numberB (address) :", numberB)
+```
+
+Variabel `numberA` dan `numberB` memiliki referensi memori yang sama. Perubahan pada salah satu nilai variabel tersebut akan memberikan efek pada variabel lainnya. Pada contoh di atas, `numberA` nilainya di ubah menjadi `5`. membuat nilai asli variabel `numberB` ikut berubah menjadi `5`.
+
+
+
+## A.23.3. Parameter Pointer
+
+Parameter bisa juga dirancang sebagai pointer. Cara penerapannya kurang lebih sama, dengan cara mendeklarasikan parameter sebagai pointer.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var number = 4
+ fmt.Println("before :", number) // 4
+
+ change(&number, 10)
+ fmt.Println("after :", number) // 10
+}
+
+func change(original *int, value int) {
+ *original = value
+}
+```
+
+Fungsi `change()` memiliki 2 parameter, yaitu `original` yang tipenya adalah pointer `int`, dan `value` yang bertipe `int`. Di dalam fungsi tersebut nilai asli parameter pointer `original` diubah.
+
+Fungsi `change()` kemudian diimplementasikan di `main`. Variabel `number` yang nilai awalnya adalah `4` diambil referensi-nya lalu digunakan sebagai parameter pada pemanggilan fungsi `change()`.
+
+Nilai variabel `number` berubah menjadi `10` karena perubahan yang terjadi di dalam fungsi `change` adalah pada variabel pointer.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From d1459cbb29e99b749dc0fce0a64247f3853633a4 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:40 +0700
Subject: [PATCH 044/522] New translations a-properti-public-dan-private.md
(English)
---
.../A-properti-public-dan-private.md | 338 ++++++++++++++++++
1 file changed, 338 insertions(+)
create mode 100644 en/content-en/A-properti-public-dan-private.md
diff --git a/en/content-en/A-properti-public-dan-private.md b/en/content-en/A-properti-public-dan-private.md
new file mode 100644
index 000000000..a3488e7ee
--- /dev/null
+++ b/en/content-en/A-properti-public-dan-private.md
@@ -0,0 +1,338 @@
+# A.26. Properti Public dan Private (Exported vs Unexported)
+
+Chapter ini membahas mengenai *property modifier* public dan private dalam Go. Kapan sebuah struct, fungsi, atau method bisa diakses dari package lain dan kapan tidak.
+
+Di Go sebenarnya tidak ada istilah *public modifier* dan *private modifier*. Yang ada adalah **exported** yang kalau di bahasa lain ekuivalen dengan *public modifier*, dan **unexported** untuk *private modifier*.
+
+## A.26.1. Intro
+
+Intro ini ditulis agar pembaca tau ekspektasi yang penulis kejar pada chapter ini.
+
+Pembahasan kali ini memiliki beberapa perbedaan dibanding lainnya. Jika pembaca mengikuti secara berurutan, membaca penjelasan dan pembahasan yang sudah tertulis, maka **pasti akan mendapati 3 buah error**. Di tiap-tiap error, sebenarnya sudah terlampir:
+
+1. Screenshot error
+2. Penjelasan penyebab error
+3. Cara resolve atau solusi dari ketiga error tersebut.
+
+Kesimpulan dari email-email yang penulis dapati: **pembaca bingung karena mendapati error, dan tidak tau cara mengatasi error tersebut. Padahal sudah ada keterangan yang jelas bahwa error tersebut pasti muncul, dan juga sudah dijelaskan cara mengatasinya. Ini kemungkinan besar disebabkan karena pembaca hanya copy-paste source code, tanpa membaca penjelasan-penjelasan yang padahal sudah tertulis cukup mendetail**.
+
+> Saya sangat anjurkan, untuk itu saya mohon **jangan hanya *copas* source code, usahakan dibaca! dipelajari! dan dipahami!** *No hard feeling* ya 👌😁
+
+## A.26.2. Exported Package dan Unexported Package
+
+Pengembangan aplikasi dalam *real development* pasti membutuhkan banyak sekali file program. Tidak mungkin dalam sebuah project semua file memiliki nama package `main`, biasanya akan dipisah sebagai package berbeda sesuai bagiannya.
+
+Project folder selain berisikan file-file `.go` juga bisa berisikan sub-folder lainnya. Di Go, setiap folder atau sub-folder adalah satu package, file-file yang ada di dalam sebuah folder package-nya harus sama. Dan package pada file-file tersebut harus berbeda dengan package pada file-file lainnya yang berada pada folder berbeda.
+
+> Jadi mudahnya, 1 folder adalah 1 package.
+
+Dalam sebuah package, biasanya kita menulis sangat banyak komponen, entah itu fungsi, struct, variabel, atau lainnya. Komponen tersebut bisa leluasa digunakan dalam package yang sama. Contoh sederhananya seperti program yang telah kita praktekan pada chapter sebelum-sebelumnya, dalam package `main` ada banyak yang di-*define*: fungsi, variabel, closure, struct, dan lainnya; ke semuanya bisa langsung dimanfaatkan.
+
+Jika dalam satu program terdapat lebih dari 1 package, atau ada package lain selain `main`, maka komponen dalam package lain tersebut tidak bisa diakses secara bebas dari file yang package-nya `main`, karena tiap komponen memiliki hak akses.
+
+Ada 2 jenis hak akses di Go:
+
+ - Hak akses **Exported** atau **public**. Menandakan komponen tersebut diperbolehkan untuk diakses dari package lain yang berbeda
+ - Hak akses **Unexported** atau **private**. Berarti komponen hanya bisa diakses dalam package yang sama, bisa dalam satu file saja atau dalam beberapa file yang masih 1 folder.
+
+Penentuan hak akses yang tepat untuk tiap komponen sangatlah penting.
+
+Di Go cara menentukan level akses atau modifier sangat mudah, penandanya adalah **character case** huruf pertama nama fungsi, struct, variabel, atau lainnya. Ketika namanya diawali dengan huruf kapital menandakan kalau *exported* (atau *public*). Dan sebaliknya, jika diawali huruf kecil, berarti *unexported* (atau private).
+
+## A.26.3. Penggunaan Package, Import, Dan Hak Akses *Exported* dan *Unexported*
+
+Agar lebih mudah dipahami, maka langsung saja kita praktekan.
+
+Pertama buat folder proyek baru bernama `belajar-golang-level-akses`, inisialisasi sebagai project dengan nama yang sama. Kemudian buat file baru bernama `main.go` di dalamnya, lalu set nama package file tersebut sebagai **main**.
+
+Kemudian, buat sub-folder baru bernama `library` di dalam folder `belajar-golang-level-akses`. Di dalam folder `library`, buat file baru `library.go`, set nama package-nya **library**.
+
+
+
+Buka file `library.go` lalu isi dengan kode berikut.
+
+```go
+package library
+
+import "fmt"
+
+func SayHello() {
+ fmt.Println("hello")
+}
+
+func introduce(name string) {
+ fmt.Println("nama saya", name)
+}
+```
+
+File `library.go` yang telah dibuat ditentukan nama package-nya adalah `library` (sesuai dengan nama folder), berisi dua buah fungsi, `SayHello()` dan `introduce()`.
+
+ - Fungsi `SayHello()`, level aksesnya adalah publik, ditandai dengan nama fungsi diawali huruf besar.
+ - Fungsi `introduce()` dengan level akses private, ditandai oleh huruf kecil di awal nama fungsi.
+
+Selanjutnya kita lakukan tes apakah memang fungsi yang ber-modifier private dalam package `library` tidak bisa diakses dari package lain.
+
+Buka file `main.go`, lalu tulis kode berikut.
+
+```go
+package main
+
+import "belajar-golang-level-akses/library"
+
+func main() {
+ library.SayHello()
+ library.introduce("ethan")
+}
+```
+
+Bisa dilihat bahwa package `library` yang telah dibuat tadi, di-import ke dalam package `main`.
+
+Folder utama atau root folder dalam project yang sedang digarap adalah `belajar-golang-level-akses`, sehingga untuk import package lain yang merupakan subfolder, harus dituliskan lengkap path folder nya, seperti `belajar-golang-level-akses/library`.
+
+Penanda root folder adalah tempat di mana file `go.mod` berada.
+
+Ok, kita lanjut. Perhatikan kode berikut.
+
+```go
+library.SayHello()
+library.introduce("ethan")
+```
+
+Cara pemanggilan fungsi yang berada dalam package lain adalah dengan menuliskan nama package target diikut dengan nama fungsi menggunakan *dot notation* atau tanda titik, seperti `library.SayHello()` atau `library.introduce("ethan")`
+
+OK, sekarang coba jalankan kode yang sudah disiapkan di atas, hasilnya error.
+
+
+
+Error di atas disebabkan karena fungsi `introduce()` yang berada dalam package `library` memiliki level akses *unexported* (atau *private*), fungsi ini tidak bisa diakses dari package lain (pada kasus ini `main`). Agar bisa diakses, solusinya bisa dengan menjadikannya ke bentuk *exported* (atau *public*), atau diubah cara pemanggilannya. Di sini kita menggunakan cara ke-2.
+
+Tambahkan parameter `name` pada fungsi `SayHello()`, lalu panggil fungsi `introduce()` dengan menyisipkan parameter `name` dari dalam fungsi `SayHello()`.
+
+```go
+func SayHello(name string) {
+ fmt.Println("hello")
+ introduce(name)
+}
+```
+
+Di `main`, cukup panggil fungsi `SayHello()` saja, sisipkan sebuah string sebagai parameter.
+
+```go
+func main() {
+ library.SayHello("ethan")
+}
+```
+
+Coba jalankan lagi.
+
+
+
+## A.26.4. Penggunaan Hak Akses *Exported* dan *Unexported* pada Struct dan Propertinya
+
+Level akses *exported* (atau public) dan *unexported* (atau private) juga bisa diterapkan di fungsi, struct, method, maupun property variabel. Cara penggunaannya sama seperti pada pembahasan sebelumnya, yaitu dengan menentukan **character case** huruf pertama nama komponen, apakah huruf besar atau kecil.
+
+Belajar tentang level akses di Go akan lebih cepat jika langsung praktek. Oleh karena itu langsung saja. Hapus isi file `library.go`, buat struct baru dengan nama `student` di dalamnya.
+
+```go
+package library
+
+type student struct {
+ Name string
+ grade int
+}
+```
+
+Buat contoh sederhana penerapan struct di atas pada file `main.go`.
+
+```go
+package main
+
+import "belajar-golang-level-akses/library"
+import "fmt"
+
+func main() {
+ var s1 = library.student{"ethan", 21}
+ fmt.Println("name ", s1.Name)
+ fmt.Println("grade", s1.grade)
+}
+```
+
+Setelah itu jalankan program.
+
+
+
+Error muncul lagi, kali ini penyebabnya adalah karena struct `student` masih di set sebagai *unexported*. Ganti ke bentuk *exported* dengan cara mengubah huruf awalnya menjadi huruf besar, kemudian jalankan ulang.
+
+```go
+// pada library/library.go
+type Student struct {
+ Name string
+ grade int
+}
+
+// pada main.go
+var s1 = library.Student{"ethan", 21}
+fmt.Println("name ", s1.Name)
+fmt.Println("grade", s1.grade)
+```
+
+Output:
+
+
+
+Error masih tetap muncul, tapi kali ini berbeda. Error yang baru ini disebabkan karena salah satu properti dari struct `Student` adalah *unexported*. Properti yg dimaksud adalah `grade`. Ubah ke bentuk *exported*, lalu jalankan lagi.
+
+```go
+// pada library/library.go
+type Student struct {
+ Name string
+ Grade int
+}
+
+// pada main.go
+var s1 = library.Student{"ethan", 21}
+fmt.Println("name ", s1.Name)
+fmt.Println("grade", s1.Grade)
+```
+
+Dari contoh program di atas, bisa disimpulkan bahwa untuk menggunakan `struct` yang berada di package lain, selain nama struct-nya harus berbentuk *exported*, properti yang diakses juga harus *exported* juga.
+
+
+
+## A.26.5. Import Dengan Prefix Tanda Titik
+
+Seperti yang kita tahu, untuk mengakses fungsi/struct/variabel yg berada di package lain, nama package nya perlu ditulis, contohnya seperti pada penggunaan `library.Student` dan `fmt.Println()`.
+
+Di Go, komponen yang berada di package lain yang di-import bisa dijadikan se-level dengan komponen package peng-import, caranya dengan menambahkan tanda titik (`.`) setelah penulisan keyword `import`. Maksud dari se-level di sini adalah, semua properti di package lain yg di-import bisa diakses tanpa perlu menuliskan nama package, seperti ketika mengakses sesuatu dari file yang sama.
+
+```go
+import (
+ . "belajar-golang-level-akses/library"
+ "fmt"
+)
+
+func main() {
+ var s1 = Student{"ethan", 21}
+ fmt.Println("name ", s1.Name)
+ fmt.Println("grade", s1.Grade)
+}
+```
+
+Pada kode di atas package `library` di-import menggunakan tanda titik. Dengan itu, pemanggilan struct `Student` tidak perlu dengan menuliskan nama package nya.
+
+## A.26.6. Pemanfaatan Alias Ketika Import Package
+
+Fungsi yang berada di package lain bisa diakses dengan cara menuliskan nama-package diikuti nama fungsi-nya, contohnya seperti `fmt.Println()`. Package yang sudah di-import tersebut bisa diubah namanya dengan cara menggunakan alias pada saat import. Contohnya bisa dilihat pada kode berikut.
+
+```go
+import (
+ f "fmt"
+)
+
+func main() {
+ f.Println("Hello World!")
+}
+```
+
+Pada kode di-atas, package `fmt` di tentukan aliasnya adalah `f`, untuk mengakses `Println()` cukup dengan `f.Println()`.
+
+## A.26.7. Mengakses Properti Dalam File Yang Package-nya Sama
+
+Jika properti yang ingin di akses masih dalam satu package tapi berbeda file, cara mengaksesnya bisa langsung dengan memanggil namanya. Hanya saja ketika eksekusi, file-file lain yang yang nama package-nya sama juga ikut dipanggil.
+
+Langsung saja kita praktekan, buat file baru dalam `belajar-golang-level-akses` dengan nama `partial.go`.
+
+
+
+Tulis kode berikut pada file `partial.go`. File ini kita set package-nya `main` (sama dengan nama package file `main.go`).
+
+```go
+package main
+
+import "fmt"
+
+func sayHello(name string) {
+ fmt.Println("halo", name)
+}
+```
+
+Hapus semua isi file `main.go`, lalu silakan tulis kode berikut.
+
+```go
+package main
+
+func main() {
+ sayHello("ethan")
+}
+```
+
+Sekarang terdapat 2 file berbeda (`main.go` dan `partial.go`) dengan package adalah sama, `main`. Pada saat `go build` atau `go run`, semua file dengan nama package `main` harus dituliskan sebagai argumen command.
+
+```
+go run main.go partial.go
+```
+
+Fungsi `sayHello` pada file `partial.go` bisa dikenali meski level aksesnya adalah *unexported*. Hal ini karena kedua file tersebut (`main.go` dan `partial.go`) memiliki package yang sama.
+
+> Cara lain untuk menjalankan program bisa dengan perintah `go run *.go`, dengan cara ini tidak perlu menuliskan nama file nya satu per satu.
+
+
+
+## A.26.7.1. Fungsi `init()`
+
+Selain fungsi `main()`, terdapat juga fungsi spesial, yaitu `init()`. Fungsi ini otomatis dipanggil pertama kali ketika aplikasi di-run. Jika fungsi ini berada dalam package main, maka dipanggil lebih dulu sebelum fungsi `main()` dieksekusi.
+
+Langsung saja kita praktekkan. Buka file `library.go`, hapus isinya lalu isi dengan kode berikut.
+
+```go
+package library
+
+import "fmt"
+
+var Student = struct {
+ Name string
+ Grade int
+}{}
+
+func init() {
+ Student.Name = "John Wick"
+ Student.Grade = 2
+
+ fmt.Println("--> library/library.go imported")
+}
+```
+
+Pada package tersebut, variabel `Student` dibuat dengan isi anonymous struct. Dalam fungsi init, nilai `Name` dan `Grade` variabel di-set.
+
+Selanjutnya buka file `main.go`, isi dengan kode berikut.
+
+```go
+package main
+
+import "belajar-golang-level-akses/library"
+import "fmt"
+
+func main() {
+ fmt.Printf("Name : %s\n", library.Student.Name)
+ fmt.Printf("Grade : %d\n", library.Student.Grade)
+}
+```
+
+Package `library` di-import, dan variabel `Student` dikonsumsi. Pada saat import package, fungsi `init()` yang berada di dalamnya langsung dieksekusi.
+
+Property variabel objek `Student` akan diisi dan sebuah pesan ditampilkan ke console.
+
+Dalam sebuah package diperbolehkan ada banyak fungsi `init()` (urutan eksekusinya adalah sesuai file mana yg terlebih dahulu digunakan). Fungsi ini dipanggil sebelum fungsi `main()`, pada saat eksekusi program.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 6b0c6f0c794cd48369e2531254ecb47cce04ec3e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:41 +0700
Subject: [PATCH 045/522] New translations a-random.md (English)
---
en/content-en/A-random.md | 126 ++++++++++++++++++++++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 en/content-en/A-random.md
diff --git a/en/content-en/A-random.md b/en/content-en/A-random.md
new file mode 100644
index 000000000..4e7e884fb
--- /dev/null
+++ b/en/content-en/A-random.md
@@ -0,0 +1,126 @@
+# A.39. Random
+
+Pada chapter ini kita akan belajar cara untuk mengutilisasi package `math/rand` untuk menciptakan data acak atau random.
+
+## A.39.1. Definisi
+
+Random Number Generator (RNG) merupakan sebuah perangkat (bisa software, bisa hardware) yang menghasilkan data deret/urutan angka yang sifatnya acak.
+
+RNG bisa berupa hardware yang murni bisa menghasilkan data angka acak, atau bisa saja sebuah [pseudo-random](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) yang menghasilkan deret angka-angka yang **terlihat acak** tetapi sebenarnya tidak benar-benar acak, yang deret angka tersebut sebenarnya merupakan hasil kalkulasi algoritma deterministik dan probabilitas. Jadi untuk pseudo-random ini, asalkan kita tau *state*-nya maka kita akan bisa menebak hasil deret angka random-nya.
+
+Dalam per-randoman-duniawi terdapat istilah **seed** atau titik mulai (*starting point*). Seed ini digunakan oleh RNG dalam peng-generate-an angka random di tiap urutannya.
+
+Sedikit ilustrasi mengenai korelasi antara seed dengan RNG, agar lebih jelas.
+
+- Dimisalkan saya menggunakan seed yaitu angka `10`, maka ketika fungsi RNG dijalankan untuk pertama kalinya, output angka yang dihasilkan pasti `5221277731205826435`. Angka random tersebut pasti *fix* dan akan selalu menjadi output angka random pertama yang dihasilkan, ketika seed yang digunakan adalah angka `10`.
+- Misalnya lagi, fungsi RNG di-eksekusi untuk ke-dua kalinya, maka angka random kedua yang dihasilkan adalah pasti `3852159813000522384`. Dan seterusnya.
+- Misalkan lagi, fungsi RNG di-eksekusi lagi, maka angka random ketiga pasti `8532807521486154107`.
+- Jadi untuk seed angka `10`, akan selalu menghasilkan angka random ke-1: `5221277731205826435`, ke-2: `3852159813000522384`, ke-3 `8532807521486154107`. Meskipun fungsi random dijalankan di program yang berbeda, di waktu yang berbeda, di environment yang berbeda, jika seed adalah `10` maka deret angka random yang dihasilkan pasti sama seperti contoh di atas.
+
+## A.39.2. Package `math/rand`
+
+Di Go terdapat sebuah package yaitu `math/rand` yang isinya banyak sekali API untuk keperluan penciptaan angka random. Package ini mengadopsi **PRNG** atau *pseudo-random* number generator. Deret angka random yang dihasilkan sangat tergantung dengan angka **seed** yang digunakan.
+
+Cara menggunakan package ini sangat mudah, yaitu cukup import `math/rand`, lalu set seed-nya, lalu panggil fungsi untuk generate angka random-nya. Lebih jelasnya silakan cek contoh berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "math/rand"
+)
+
+func main() {
+ randomizer := rand.New(rand.NewSource(10))
+ fmt.Println("random ke-1:", randomizer.Int()) // 5221277731205826435
+ fmt.Println("random ke-2:", randomizer.Int()) // 3852159813000522384
+ fmt.Println("random ke-3:", randomizer.Int()) // 8532807521486154107
+}
+```
+
+Fungsi `rand.New(rand.NewSource(x))` digunakan untuk membuat object randomizer sekaligus penentuan nilai seed-nya. Dari object randomizer, method `Int()` bisa diakses, gunanya untuk generate angka random dalam bentuk numerik bertipe `int`. Statement `randomizer.Int()` ini setiap kali dipanggil akan menghasilkan angka berbeda, tapi jika diperhatikan angka-angka tersebut tidak berubah, pasti hanya angka-angka itu saja yang muncul.
+
+Silakan coba sendiri kode di atas di local masing-masing, hasilnya pasti:
+
+- Angka random ke-1 akan selalu `5221277731205826435`
+- Angka random ke-2 akan selalu `3852159813000522384`
+- Angka random ke-3 akan selalu `8532807521486154107`
+
+Jika perlu jalankan program di atas beberapa kali, hasilnya selalu sama untuk angka random ke-1, ke-2, dan seterusnya.
+
+
+
+## A.39.3. Unique Seed
+
+Lalu bagaimana cara agar angka yang dihasilkan selalu berbeda setiap kali dipanggil? Apakah harus set ulang seed-nya? Jangan, karena kalau seed di-set ulang maka urutan deret random akan berubah. Seed hanya perlu di set sekali di awal. Lha, terus bagaimana?
+
+Jadi begini, setiap kali `randomizer.Int()` dipanggil, hasilnya itu selalu berbeda, tapi sangat bisa diprediksi jika kita tau seed-nya, dan ini adalah masalah besar. Nah, ada cara agar angka random yang dihasilkan tidak berulang-ulang selalu contoh di-atas, caranya adalah menggunakan angka yang *unique*/unik sebagai seed, contohnya seperti angka [unix nano](https://en.wikipedia.org/wiki/GNU_nano) dari waktu sekarang.
+
+Coba modifikasi program dengan kode berikut, lalu jalankan ulang. Jangan lupa meng-import package `time` ya.
+
+```go
+randomizer := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+fmt.Println("random ke-1:", randomizer.Int())
+fmt.Println("random ke-2:", randomizer.Int())
+fmt.Println("random ke-3:", randomizer.Int())
+```
+
+
+
+Bisa dilihat, setiap program dieksekusi angka random nya selalu berbeda, hal ini karena seed yang digunakan pasti berbeda satu sama lain saat program dijalankan. Seed-nya adalah angka unix nano dari waktu sekarang.
+
+## A.39.4. Random Tipe Data Numerik Lainnya
+
+Di dalam package `math/rand`, ada banyak fungsi untuk generate angka random. Method `Int()` milik object randomizer hanya salah satu dari fungsi yang tersedia di dalam package tersebut, yang gunanya adalah menghasilkan angka random bertipe `int`.
+
+Selain itu, ada juga `randomizer.Float32()` yang menghasilkan angka random bertipe `float32`. Ada juga `randomizer.Uint32()` yang menghasilkan angka random bertipe *unsigned* int, dan lainnya.
+
+Contoh penerapan fungsi-fungsi tersebut:
+
+```go
+randomizer := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+fmt.Println("random int:", randomizer.Int())
+fmt.Println("random float32:", randomizer.Float32())
+fmt.Println("random uint:", randomizer.Uint32())
+```
+
+lebih detailnya silakan merujuk ke https://golang.org/pkg/math/rand/
+
+## A.39.5. Angka Random Index Tertentu
+
+Gunakan `randomizer.Intn(n)` untuk mendapatkan angka random dengan batas `0` hingga <`n`, contoh: `randomizer.Intn(100)` akan mengembalikan angka acak dari 0 hingga 99.
+
+## A.39.6. Random Tipe Data String
+
+Untuk menghasilkan data random string, ada banyak cara yang bisa digunakan, salah satunya adalah dengan memafaatkan alfabet dan hasil random numerik.
+
+```go
+var randomizer = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+func randomString(length int) string {
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = letters[randomizer.Intn(len(letters))]
+ }
+ return string(b)
+}
+
+func main() {
+ fmt.Println("random string 5 karakter:", randomString(5))
+}
+```
+
+Dengan fungsi di atas kita bisa dengan mudah meng-generate string random dengan panjang karakter yang sudah ditentukan, misal `randomString(10)` akan menghasilkan random string 10 karakter.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From badeda2adeb29cc28caaa8628ded94aeb10f94fb Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:42 +0700
Subject: [PATCH 046/522] New translations a-reflect.md (English)
---
en/content-en/A-reflect.md | 175 +++++++++++++++++++++++++++++++++++++
1 file changed, 175 insertions(+)
create mode 100644 en/content-en/A-reflect.md
diff --git a/en/content-en/A-reflect.md b/en/content-en/A-reflect.md
new file mode 100644
index 000000000..5be4db6ec
--- /dev/null
+++ b/en/content-en/A-reflect.md
@@ -0,0 +1,175 @@
+# A.29. Reflect
+
+Reflection adalah teknik untuk inspeksi variabel, mengambil informasi dari variabel tersebut atau bahkan memanipulasinya. Cakupan informasi yang bisa didapatkan lewat reflection sangat luas, seperti melihat struktur variabel, tipe, nilai pointer, dan banyak lagi.
+
+Go menyediakan package `reflect`, berisikan banyak sekali fungsi untuk keperluan reflection. Pada chapter ini, kita akan belajar tentang dasar penggunaan package tersebut.
+
+Dari banyak fungsi yang tersedia di dalam package tersebut, ada 2 fungsi yang paling penting untuk diketahui, yaitu `reflect.ValueOf()` dan `reflect.TypeOf()`.
+
+- Fungsi `reflect.ValueOf()` akan mengembalikan objek dalam tipe `reflect.Value`, yang berisikan informasi yang berhubungan dengan nilai pada variabel yang dicari.
+- Sedangkan `reflect.TypeOf()` mengembalikan objek dalam tipe `reflect.Type`. Objek tersebut berisikan informasi yang berhubungan dengan tipe data variabel yang dicari.
+
+## A.29.1. Mencari Tipe Data & Value Menggunakan Reflect
+
+Dengan reflection, tipe data dan nilai variabel dapat diketahui dengan mudah. Contoh penerapannya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "reflect"
+
+func main() {
+ var number = 23
+ var reflectValue = reflect.ValueOf(number)
+
+ fmt.Println("tipe variabel :", reflectValue.Type())
+
+ if reflectValue.Kind() == reflect.Int {
+ fmt.Println("nilai variabel :", reflectValue.Int())
+ }
+}
+```
+
+
+
+Fungsi `reflect.valueOf()` memiliki parameter yang bisa menampung segala jenis tipe data. Fungsi tersebut mengembalikan objek dalam tipe `reflect.Value`, yang berisikan informasi mengenai variabel yang bersangkutan.
+
+Objek `reflect.Value` memiliki beberapa method, salah satunya `Type()`. Method ini mengembalikan tipe data variabel yang bersangkutan dalam bentuk `string`.
+
+Statement `reflectValue.Int()` menghasilkan nilai `int` dari variabel `number`. Untuk menampilkan nilai variabel reflect, harus dipastikan dulu tipe datanya. Ketika tipe data adalah `int`, maka bisa menggunakan method `Int()`. Ada banyak lagi method milik struct `reflect.Value` yang bisa digunakan untuk pengambilan nilai dalam bentuk tertentu, contohnya: `reflectValue.String()` digunakan untuk mengambil nilai `string`, `reflectValue.Float64()` untuk nilai `float64`, dan lainnya.
+
+Perlu diketahui, fungsi yang digunakan harus sesuai dengan tipe data nilai yang ditampung variabel. Jika fungsi yang digunakan berbeda dengan tipe data variabelnya, maka akan menghasilkan error. Contohnya pada variabel menampung nilai bertipe `float64`, maka tidak bisa menggunakan method `String()`.
+
+Diperlukan adanya pengecekan tipe data nilai yang disimpan, agar pengambilan nilai bisa tepat. Salah satunya bisa dengan cara seperti kode di atas, yaitu dengan mengecek dahulu apa jenis tipe datanya menggunakan method `Kind()`, setelah itu diambil nilainya dengan method yang sesuai.
+
+List konstanta tipe data dan method yang bisa digunakan dalam refleksi di Go bisa dilihat di https://godoc.org/reflect#Kind
+
+## Pengaksesan Nilai Dalam Bentuk `interface{}`
+
+Jika nilai hanya diperlukan untuk ditampilkan ke output, bisa menggunakan `.Interface()`. Lewat method tersebut segala jenis nilai bisa diakses dengan mudah.
+
+```go
+var number = 23
+var reflectValue = reflect.ValueOf(number)
+
+fmt.Println("tipe variabel :", reflectValue.Type())
+fmt.Println("nilai variabel :", reflectValue.Interface())
+```
+
+Fungsi `Interface()` mengembalikan nilai interface kosong atau `interface{}`. Nilai aslinya sendiri bisa diakses dengan meng-casting interface kosong tersebut.
+
+```go
+var nilai = reflectValue.Interface().(int)
+```
+
+## A.29.2. Pengaksesan Informasi Property Variabel Objek
+
+Reflect bisa digunakan untuk mengambil informasi semua property variabel objek cetakan struct, dengan catatan property-property tersebut bermodifier public. Langsung saja kita praktekan, siapkan sebuah struct bernama `student`.
+
+```go
+type student struct {
+ Name string
+ Grade int
+}
+```
+
+Buat method baru untuk struct tersebut, dengan nama method `getPropertyInfo()`. Method ini berisikan kode untuk mengambil dan menampilkan informasi tiap property milik struct `student`.
+
+```go
+func (s *student) getPropertyInfo() {
+ var reflectValue = reflect.ValueOf(s)
+
+ if reflectValue.Kind() == reflect.Ptr {
+ reflectValue = reflectValue.Elem()
+ }
+
+ var reflectType = reflectValue.Type()
+
+ for i := 0; i < reflectValue.NumField(); i++ {
+ fmt.Println("nama :", reflectType.Field(i).Name)
+ fmt.Println("tipe data :", reflectType.Field(i).Type)
+ fmt.Println("nilai :", reflectValue.Field(i).Interface())
+ fmt.Println("")
+ }
+}
+```
+
+Terakhir, lakukan uji coba method di fungsi `main`.
+
+```go
+func main() {
+ var s1 = &student{Name: "wick", Grade: 2}
+ s1.getPropertyInfo()
+}
+```
+
+
+
+Di dalam method `getPropertyInfo` terjadi beberapa hal. Pertama objek `reflect.Value` dari variabel `s` diambil. Setelah itu dilakukan pengecekan apakah variabel objek tersebut merupakan pointer atau tidak (bisa dilihat dari `if reflectValue.Kind() == reflect.Ptr`, jika bernilai `true` maka variabel adalah pointer). jika ternyata variabel memang pointer, maka perlu diambil objek reflect aslinya dengan cara memanggil method `Elem()`.
+
+Setelah itu, dilakukan perulangan sebanyak jumlah property yang ada pada struct `student`. Method `NumField()` akan mengembalikan jumlah property publik yang ada dalam struct.
+
+Di tiap perulangan, informasi tiap property struct diambil berurutan dengan lewat method `Field()`. Method ini ada pada tipe `reflect.Value` dan `reflect.Type`.
+
+- `reflectType.Field(i).Name` akan mengembalikan nama property
+- `reflectType.Field(i).Type` mengembalikan tipe data property
+- `reflectValue.Field(i).Interface()` mengembalikan nilai property dalam bentuk `interface{}`
+
+Pengambilan informasi property, selain menggunakan indeks, bisa diambil berdasarkan nama field dengan menggunakan method `FieldByName()`.
+
+## A.29.3. Pengaksesan Informasi Method Variabel Objek
+
+Informasi mengenai method juga bisa diakses lewat reflect, syaratnya masih sama seperti pada pengaksesan proprerty, yaitu harus bermodifier public.
+
+Pada contoh di bawah ini informasi method `SetName` akan diambil lewat reflection. Siapkan method baru di struct `student`, dengan nama `SetName`.
+
+```go
+func (s *student) SetName(name string) {
+ s.Name = name
+}
+```
+
+Buat contoh penerapannya di fungsi `main`.
+
+```go
+func main() {
+ var s1 = &student{Name: "john wick", Grade: 2}
+ fmt.Println("nama :", s1.Name)
+
+ var reflectValue = reflect.ValueOf(s1)
+ var method = reflectValue.MethodByName("SetName")
+ method.Call([]reflect.Value{
+ reflect.ValueOf("wick"),
+ })
+
+ fmt.Println("nama :", s1.Name)
+}
+```
+
+
+
+Pada kode di atas, disiapkan variabel `s1` yang merupakan instance struct `student`. Awalnya property `Name` variabel tersebut berisikan string `"john wick"`.
+
+Setelah itu, refleksi nilai objek tersebut diambil, refleksi method `SetName` juga diambil. Pengambilan refleksi method dilakukan menggunakan `MethodByName` dengan argument adalah nama method yang diinginkan, atau bisa juga lewat indeks method-nya (menggunakan `Method(i)`).
+
+Setelah refleksi method yang dicari sudah didapatkan, `Call()` dipanggil untuk eksekusi method.
+
+Jika eksekusi method diikuti pengisian parameter, maka parameternya harus ditulis dalam bentuk array `[]reflect.Value` berurutan sesuai urutan deklarasi parameter-nya. Dan nilai yang dimasukkan ke array tersebut harus dalam bentuk `reflect.Value` (gunakan `reflect.ValueOf()` untuk pengambilannya).
+
+```go
+[]reflect.Value{
+ reflect.ValueOf("wick"),
+}
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 953f7fa5572744273b9b3d45964f5e731a6cfc74 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:43 +0700
Subject: [PATCH 047/522] New translations a-regex.md (English)
---
en/content-en/A-regex.md | 174 +++++++++++++++++++++++++++++++++++++++
1 file changed, 174 insertions(+)
create mode 100644 en/content-en/A-regex.md
diff --git a/en/content-en/A-regex.md b/en/content-en/A-regex.md
new file mode 100644
index 000000000..4ef767fa6
--- /dev/null
+++ b/en/content-en/A-regex.md
@@ -0,0 +1,174 @@
+# A.45. Regex
+
+Regex atau regexp atau **regular expression** adalah suatu teknik yang digunakan untuk pencocokan string dengan pola tertentu. Regex biasa dimanfaatkan untuk pencarian dan pengubahan data string.
+
+Go mengadopsi standar regex **RE2**, untuk melihat sintaks yang di-support engine ini bisa langsung merujuk ke dokumentasinya di [https://github.com/google/re2/wiki/Syntax](https://github.com/google/re2/wiki/Syntax).
+
+Pada chapter ini kita akan belajar mengenai pengaplikasian regex dengan memanfaatkan fungsi-fungsi dalam package `regexp`.
+
+## A.45.1. Penerapan Regexp
+
+Fungsi `regexp.Compile()` digunakan untuk mengkompilasi ekspresi regex. Fungsi tersebut mengembalikan objek bertipe `regexp.*Regexp`.
+
+Berikut merupakan contoh penerapan regex untuk pencarian karakter.
+
+```go
+package main
+
+import "fmt"
+import "regexp"
+
+func main() {
+ var text = "banana burger soup"
+ var regex, err = regexp.Compile(`[a-z]+`)
+
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+
+ var res1 = regex.FindAllString(text, 2)
+ fmt.Printf("%#v \n", res1)
+ // []string{"banana", "burger"}
+
+ var res2 = regex.FindAllString(text, -1)
+ fmt.Printf("%#v \n", res2)
+ // []string{"banana", "burger", "soup"}
+}
+```
+
+Ekspresi `[a-z]+` maknanya adalah, semua string yang merupakan alphabet yang hurufnya kecil. Ekspresi tersebut di-compile oleh `regexp.Compile()` lalu disimpan ke variabel objek `regex` bertipe `regexp.*Regexp`.
+
+Struct `regexp.Regexp` memiliki banyak method, salah satunya adalah `FindAllString()`, berfungsi untuk mencari semua string yang sesuai dengan ekspresi regex, dengan kembalian berupa slice string.
+
+Jumlah hasil pencarian dari `regex.FindAllString()` bisa ditentukan. Contohnya pada `res1`, ditentukan maksimal `2` data saja pada nilai kembalian. Jika batas di set `-1`, maka akan mengembalikan semua data.
+
+Ada cukup banyak method struct `regexp.*Regexp` yang bisa kita manfaatkan untuk keperluan pengelolaan string. Berikut merupakan pembahasan tiap method-nya.
+
+## A.45.2. Method `MatchString()`
+
+Method ini digunakan untuk mendeteksi apakah string memenuhi sebuah pola regexp.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var isMatch = regex.MatchString(text)
+fmt.Println(isMatch)
+// true
+```
+
+Pada contoh di atas `isMatch` bernilai `true` karena string `"banana burger soup"` memenuhi pola regex `[a-z]+`.
+
+## A.45.3. Method `FindString()`
+
+Digunakan untuk mencari string yang memenuhi kriteria regexp yang telah ditentukan.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str = regex.FindString(text)
+fmt.Println(str)
+// "banana"
+```
+
+Fungsi ini hanya mengembalikan 1 buah hasil saja. Jika ada banyak substring yang sesuai dengan ekspresi regexp, akan dikembalikan yang pertama saja.
+
+## A.45.4. Method `FindStringIndex()`
+
+Digunakan untuk mencari index string kembalian hasil dari operasi regexp.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var idx = regex.FindStringIndex(text)
+fmt.Println(idx)
+// [0, 6]
+
+var str = text[0:6]
+fmt.Println(str)
+// "banana"
+```
+
+Method ini sama dengan `FindString()` hanya saja yang dikembalikan indeks-nya.
+
+## A.45.5. Method `FindAllString()`
+
+Digunakan untuk mencari banyak string yang memenuhi kriteria regexp yang telah ditentukan.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str1 = regex.FindAllString(text, -1)
+fmt.Println(str1)
+// ["banana", "burger", "soup"]
+
+var str2 = regex.FindAllString(text, 1)
+fmt.Println(str2)
+// ["banana"]
+```
+
+Jumlah data yang dikembalikan bisa ditentukan. Jika diisi dengan `-1`, maka akan mengembalikan semua data.
+
+## A.45.6. Method `ReplaceAllString()`
+
+Berguna untuk me-replace semua string yang memenuhi kriteri regexp, dengan string lain.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str = regex.ReplaceAllString(text, "potato")
+fmt.Println(str)
+// "potato potato potato"
+```
+
+## A.45.7. Method `ReplaceAllStringFunc()`
+
+Digunakan untuk me-replace semua string yang memenuhi kriteri regexp, dengan kondisi yang bisa ditentukan untuk setiap substring yang akan di replace.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str = regex.ReplaceAllStringFunc(text, func(each string) string {
+ if each == "burger" {
+ return "potato"
+ }
+ return each
+})
+fmt.Println(str)
+// "banana potato soup"
+```
+
+Pada contoh di atas, jika salah satu substring yang *match* adalah `"burger"` maka akan diganti dengan `"potato"`, string selainnya tidak di replace.
+
+## A.45.8. Method `Split()`
+
+Digunakan untuk memisah string dengan pemisah adalah substring yang memenuhi kriteria regexp yang telah ditentukan.
+
+Jumlah karakter yang akan di split bisa ditentukan dengan mengisi parameter kedua fungsi `regex.Split()`. Jika di-isi `-1` maka semua karakter yang memenuhi kriteria regex akan menjadi *separator* dalam operasi pemisahan/split. Contoh lain, jika di-isi `2`, maka hanya 2 karakter pertama yang memenuhi kriteria regex akan menjadi *separator* dalam split tersebut.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-b]+`) // split dengan separator adalah karakter "a" dan/atau "b"
+
+var str = regex.Split(text, -1)
+fmt.Printf("%#v \n", str)
+// []string{"", "n", "n", " ", "urger soup"}
+```
+
+Pada contoh di atas, ekspresi regexp `[a-b]+` digunakan sebagai kriteria split. Maka karakter `a` dan/atau `b` akan menjadi separator.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 71851b5f625ab1546c90b91759a051a87a4a84a7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:44 +0700
Subject: [PATCH 048/522] New translations a-seleksi-kondisi.md (English)
---
en/content-en/A-seleksi-kondisi.md | 212 +++++++++++++++++++++++++++++
1 file changed, 212 insertions(+)
create mode 100644 en/content-en/A-seleksi-kondisi.md
diff --git a/en/content-en/A-seleksi-kondisi.md b/en/content-en/A-seleksi-kondisi.md
new file mode 100644
index 000000000..c8e7988e2
--- /dev/null
+++ b/en/content-en/A-seleksi-kondisi.md
@@ -0,0 +1,212 @@
+# A.13. Seleksi Kondisi
+
+Seleksi kondisi digunakan untuk mengontrol alur program. Analoginya mirip seperti fungsi rambu lalu lintas di jalan raya. Kapan kendaraan diperbolehkan melaju dan kapan harus berhenti diatur oleh rambu tersebut. Seleksi kondisi pada program juga kurang lebih sama, kapan sebuah blok kode akan dieksekusi dikontrol.
+
+Yang dijadikan acuan oleh seleksi kondisi adalah nilai bertipe `bool`, bisa berasal dari variabel, ataupun hasil operasi perbandingan. Nilai tersebut menentukan blok kode mana yang akan dieksekusi.
+
+Go memiliki 2 macam keyword untuk seleksi kondisi, yaitu **if else** dan **switch**. Pada chapter ini kita akan mempelajarinya satu-persatu.
+
+> Go tidak mendukung seleksi kondisi menggunakan **ternary**. Statement seperti: `var data = (isExist ? "ada" : "tidak ada")` adalah invalid dan menghasilkan error.
+
+## A.13.1. Seleksi Kondisi Menggunakan Keyword `if`, `else if`, & `else`
+
+Cara penerapan if-else di Go sama seperti pada bahasa pemrograman lain. Yang membedakan hanya tanda kurungnya *(parentheses)*, di Go tidak perlu ditulis. Kode berikut merupakan contoh penerapan seleksi kondisi if else, dengan jumlah kondisi 4 buah.
+
+```go
+var point = 8
+
+if point == 10 {
+ fmt.Println("lulus dengan nilai sempurna")
+} else if point > 5 {
+ fmt.Println("lulus")
+} else if point == 4 {
+ fmt.Println("hampir lulus")
+} else {
+ fmt.Printf("tidak lulus. nilai anda %d\n", point)
+}
+```
+
+Dari ke-empat kondisi di atas, yang terpenuhi adalah `if point > 5`, karena nilai variabel `point` memang lebih besar dari `5`. Maka blok kode tepat di bawah kondisi tersebut akan dieksekusi (blok kode ditandai kurung kurawal buka dan tutup), hasilnya text `"lulus"` muncul sebagai output.
+
+
+
+Skema if else Go sama seperti pada pemrograman umumnya. Yaitu di awal seleksi kondisi menggunakan `if`, dan ketika kondisinya tidak terpenuhi akan menuju ke `else` (jika ada). Ketika ada banyak kondisi, gunakan `else if`.
+
+> Di bahasa pemrograman lain, ketika ada seleksi kondisi yang isi blok-nya hanya 1 baris saja, kurung kurawal boleh tidak dituliskan. Berbeda dengan aturan di Go, kurung kurawal harus tetap dituliskan meski isinya hanya 1 blok satement.
+
+## A.13.2. Variabel Temporary Pada `if` - `else`
+
+Variabel temporary adalah variabel yang hanya bisa digunakan pada blok seleksi kondisi di mana ia ditempatkan saja. Penggunaan variabel ini membawa beberapa manfaat, antara lain:
+
+ - Scope atau cakupan variabel jelas, hanya bisa digunakan pada blok seleksi kondisi itu saja
+ - Kode menjadi lebih rapi
+ - Ketika nilai variabel tersebut didapat dari sebuah komputasi, perhitungan tidak perlu dilakukan di dalam blok masing-masing kondisi.
+
+```go
+var point = 8840.0
+
+if percent := point / 100; percent >= 100 {
+ fmt.Printf("%.1f%s perfect!\n", percent, "%")
+} else if percent >= 70 {
+ fmt.Printf("%.1f%s good\n", percent, "%")
+} else {
+ fmt.Printf("%.1f%s not bad\n", percent, "%")
+}
+```
+
+Variabel `percent` nilainya didapat dari hasil perhitungan, dan hanya bisa digunakan di deretan blok seleksi kondisi itu saja.
+
+> Deklarasi variabel temporary hanya bisa dilakukan lewat metode type inference yang menggunakan tanda `:=`. Penggunaan keyword `var` di situ tidak diperbolehkan karena akan menyebabkan error.
+
+## A.13.3. Seleksi Kondisi Menggunakan Keyword `switch` - `case`
+
+Switch merupakan seleksi kondisi yang sifatnya fokus pada satu variabel, lalu kemudian di-cek nilainya. Contoh sederhananya seperti penentuan apakah nilai variabel `x` adalah: `1`, `2`, `3`, atau lainnya.
+
+```go
+var point = 6
+
+switch point {
+case 8:
+ fmt.Println("perfect")
+case 7:
+ fmt.Println("awesome")
+default:
+ fmt.Println("not bad")
+}
+```
+
+Pada kode di atas, tidak ada kondisi atau `case` yang terpenuhi karena nilai variabel `point` tetap `6`. Ketika hal seperti ini terjadi, blok kondisi `default` dipanggil. Bisa dibilang bahwa `default` merupakan `else` dalam sebuah switch.
+
+Perlu diketahui, switch pada pemrograman Go memiliki perbedaan dibanding bahasa lain. Di Go, ketika sebuah case terpenuhi, tidak akan dilanjutkan ke pengecekan case selanjutnya, meskipun tidak ada keyword `break` di situ. Konsep ini berkebalikan dengan switch pada umumnya, yang ketika sebuah case terpenuhi, maka akan tetap dilanjut mengecek case selanjutnya kecuali ada keyword `break`.
+
+## A.13.4. Pemanfaatan `case` Untuk Banyak Kondisi
+
+Sebuah `case` dapat menampung banyak kondisi. Cara penerapannya yaitu dengan menuliskan nilai pembanding-pembanding variabel yang di-switch setelah keyword `case` dipisah tanda koma (`,`).
+
+```go
+var point = 6
+
+switch point {
+case 8:
+ fmt.Println("perfect")
+case 7, 6, 5, 4:
+ fmt.Println("awesome")
+default:
+ fmt.Println("not bad")
+}
+```
+
+Kondisi `case 7, 6, 5, 4:` akan terpenuhi ketika nilai variabel `point` adalah 7 atau 6 atau 5 atau 4.
+
+## A.13.5. Kurung Kurawal Pada Keyword `case` & `default`
+
+Tanda kurung kurawal (`{ }`) bisa diterapkan pada keyword `case` dan `default`. Tanda ini opsional, boleh dipakai boleh tidak. Bagus jika dipakai pada blok kondisi yang di dalamnya ada banyak statement, kode akan terlihat lebih rapi dan mudah di-maintain.
+
+Perhatikan kode berikut, bisa dilihat pada keyword `default` terdapat kurung kurawal yang mengapit 2 statement di dalamnya.
+
+```go
+var point = 6
+
+switch point {
+case 8:
+ fmt.Println("perfect")
+case 7, 6, 5, 4:
+ fmt.Println("awesome")
+default:
+ {
+ fmt.Println("not bad")
+ fmt.Println("you can be better!")
+ }
+}
+```
+
+## A.13.6. Switch Dengan Gaya `if` - `else`
+
+Uniknya di Go, switch bisa digunakan dengan gaya ala if-else. Nilai yang akan dibandingkan tidak dituliskan setelah keyword `switch`, melainkan akan ditulis langsung dalam bentuk perbandingan dalam keyword `case`.
+
+Pada kode di bawah ini, kode program switch di atas diubah ke dalam gaya `if-else`. Variabel `point` dihilangkan dari keyword `switch`, lalu kondisi-kondisinya dituliskan di tiap `case`.
+
+```go
+var point = 6
+
+switch {
+case point == 8:
+ fmt.Println("perfect")
+case (point < 8) && (point > 3):
+ fmt.Println("awesome")
+default:
+ {
+ fmt.Println("not bad")
+ fmt.Println("you need to learn more")
+ }
+}
+```
+
+## A.13.7. Penggunaan Keyword `fallthrough` Dalam `switch`
+
+Seperti yang sudah dijelaskan sebelumnya, bahwa switch pada Go memiliki perbedaan dengan bahasa lain. Ketika sebuah `case` terpenuhi, pengecekan kondisi tidak akan diteruskan ke case-case setelahnya.
+
+Keyword `fallthrough` digunakan untuk memaksa proses pengecekan diteruskan ke satu `case` selanjutnya dengan **tanpa menghiraukan nilai kondisinya**, jadi satu case di pengecekan selanjutnya tersebut selalu dianggap benar (meskipun aslinya adalah salah). Dalam sebuah `switch` lebih dari satu `fallthrough` bisa di tempatkan untuk memaksa melanjutkan proses pengecekan ke satu `case` setelahnya.
+
+```go
+var point = 6
+
+switch {
+case point == 8:
+ fmt.Println("perfect")
+case (point < 8) && (point > 3):
+ fmt.Println("awesome")
+ fallthrough
+case point < 5:
+ fmt.Println("you need to learn more")
+default:
+ {
+ fmt.Println("not bad")
+ fmt.Println("you need to learn more")
+ }
+}
+```
+
+
+Setelah pengecekan `case (point < 8) && (point > 3)` selesai, akan dilanjut ke pengecekan `case point < 5`, karena ada `fallthrough` di situ.
+
+
+
+## A.13.8. Seleksi Kondisi Bersarang
+
+Seleksi kondisi bersarang adalah seleksi kondisi, yang berada dalam seleksi kondisi, yang mungkin juga berada dalam seleksi kondisi, dan seterusnya. Seleksi kondisi bersarang bisa dilakukan pada `if` - `else`, `switch`, ataupun kombinasi keduanya.
+
+```go
+var point = 10
+
+if point > 7 {
+ switch point {
+ case 10:
+ fmt.Println("perfect!")
+ default:
+ fmt.Println("nice!")
+ }
+} else {
+ if point == 5 {
+ fmt.Println("not bad")
+ } else if point == 3 {
+ fmt.Println("keep trying")
+ } else {
+ fmt.Println("you can do it")
+ if point == 0 {
+ fmt.Println("try harder!")
+ }
+ }
+}
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From f0829701acb30f98a706236359ef254db8841385 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:45 +0700
Subject: [PATCH 049/522] New translations
a-setup-go-project-dengan-go-modules.md (English)
---
.../A-setup-go-project-dengan-go-modules.md | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 en/content-en/A-setup-go-project-dengan-go-modules.md
diff --git a/en/content-en/A-setup-go-project-dengan-go-modules.md b/en/content-en/A-setup-go-project-dengan-go-modules.md
new file mode 100644
index 000000000..83e992a3c
--- /dev/null
+++ b/en/content-en/A-setup-go-project-dengan-go-modules.md
@@ -0,0 +1,53 @@
+# A.3. Go Modules
+
+Pada bagian ini kita akan belajar cara inisialisasi project menggunakan Go Modules (atau Modules).
+
+## A.3.1. Penjelasan
+
+Go modules merupakan manajemen dependensi resmi untuk Go. Modules ini diperkenalkan pertama kali di `go1.11`, sebelum itu pengembangan project Go dilakukan dalam `GOPATH`.
+
+Modules digunakan untuk menginisialisasi sebuah project, sekaligus melakukan manajemen terhadap *3rd party* atau *library* lain yang dipergunakan.
+
+Modules penggunaannya adalah lewat CLI. Dan jika temen-temen sudah sukses meng-*install* Go, maka otomatis bisa mempergunakan Go Modules.
+
+> Modules atau Module di sini merupakan istilah untuk project ya. Jadi jangan bingung.
+
+## A.3.2. Inisialisasi Project Menggunakan Go Modules
+
+Command `go mod init` digunakan untuk menginisialisasi project baru.
+
+Mari kita praktekan, buat folder baru, bisa via CLI atau lewat browser/finder.
+
+```bash
+mkdir project-pertama
+cd project-pertama
+go mod init project-pertama
+dir
+```
+
+Bisa dilihat pada *command* di atas ada direktori `project-pertama`, dibuat. Setelah masuk ke direktori tersebut, perintah `go mod init project-pertama` dijalankan. Dengan ini maka kita telah menginisialisasi direktori `project-pertama` sebagai sebuah project Go dengan nama `project-pertama` (kebetulan di sini nama project sama dengan nama direktori-nya).
+
+
+
+Skema penulisan command `go mod`:
+
+```
+go mod init
+go mod init project-pertama
+```
+
+Untuk nama project, umumnya adalah disamakan dengan nama direktori, tapi bisa saja sebenarnya menggunakan nama yang lain.
+
+> Nama project dan Nama module merupakan istilah yang sama.
+
+Eksekusi perintah `go mod init` menghasilkan satu buah file baru bernama `go.mod`. File ini digunakan oleh Go toolchain untuk menandai bahwa folder di mana file tersebut berada adalah folder project. Jadi jangan di hapus ya file tersebut.
+
+---
+
+Ok, sekian. Cukup itu saja cara inisialisasi project di Go.
+
+O iya, sebenarnya selain Go Modules, setup project di Go juga bisa menggunakan `$GOPATH` ([A.4. Setup GOPATH Dan Workspace](/A-gopath-dan-workspace.html)). Tapi inisialisasi project dengan GOPATH sudah outdate dan kurang dianjurkan untuk project-project yang dikembangkan menggunakan Go versi terbaru (1.14 ke atas). Jadi setelah chapter ini, bisa langsung lanjut ke [A. Instalasi Editor](/A-instalasi-editor.html).
+
+---
+
+
From 9f5a4c8d79eea6fc3c65ebfca6c183aee4c3b3a5 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:46 +0700
Subject: [PATCH 050/522] New translations
a-simplified-fan-in-fan-out-pipeline.md (English)
---
.../A-simplified-fan-in-fan-out-pipeline.md | 345 ++++++++++++++++++
1 file changed, 345 insertions(+)
create mode 100644 en/content-en/A-simplified-fan-in-fan-out-pipeline.md
diff --git a/en/content-en/A-simplified-fan-in-fan-out-pipeline.md b/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
new file mode 100644
index 000000000..9dc5493c1
--- /dev/null
+++ b/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
@@ -0,0 +1,345 @@
+# A.63. Concurrency Pattern: Simplified Fan-out Fan-in Pipeline
+
+Pada chapter sebelumnya, yaitu chapter [A.62. Concurrency Pattern: Pipeline](/A-concurrency-pipeline.html), kita telah mempelajari tentang pipeline pattern, yang di mana pattern tersebut merupakan rekomendasi dari tim Go dalam meng-*handle* jenis kasus sekarangkain proses yang berjalan secara konkuren.
+
+> Penulis sangat anjurkan untuk mencoba mempelajari praktek chapter sebelumnya terlebih dahulu jika belum. Karena chapter kali ini ada hubungannya dengan chapter tersebut.
+
+Pada chapter ini kita akan mempelajari concurrency pattern juga, lanjutan dari sebelumnya. Pada versi ini kalau dilihat dari perspektif coding penerapannya akan lebih ringkas. Tapi apakah lebih mudah dan lebih *performant* dibanding penerapan pipeline sebelumnya? Jawabannya sangat tergantung dengan kasus yang dihadapi, tergantung spesifikasi hardware-nya juga, dan mungkin juga tergantung dengan taste dari si engineer pembuat program.
+
+Perbedaannya sebenarnya hanya pada bagian Fan-out Fan-in nya saja. Di sini (hampir) semua pipeline isinya adalah gabungan dari Fan-out dan juga Fan-in. Jadi kita tidak perlu report *merge*. Dan juga beda lainnya adalah, jumlah worker bisa kita tentukan sesuai kebutuhan (*parameterized*).
+
+Ok, agar lebih jelas mari kita mulai praktek.
+
+## A.63.1. Skenario Praktek
+
+Kita akan modifikasi file program `1-dummy-file-generator.go` yang pada chapter sebelumnya sudah dibuat ([A.62. Concurrency Pattern: Pipeline](/A-concurrency-pipeline.html)). Kita rubah mekanisme generate dummy files-nya dari sekuensial ke konkuren.
+
+## A.63.2. Program Generate Dummy File *Sequentially*
+
+Ok langsung saja, pertama yang perlu dipersiapkan adalah tulis dulu kode program versi sekuensialnya. Bisa langsung copy-paste, atau tulis dari awal dengan mengikut tutorial ini secara keseluruhan. Untuk penjelasan detail program versi sekuensial silakan merujuk ke chapter sebelumnya saja, di sini kita tulis langsung agar bisa cepat dimulai bagian program konkuren.
+
+Siapkan folder project baru, isinya satu buah file `1-generate-dummy-files-sequentially.go`.
+
+#### • Import Packages dan Definisi Variabel
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+const totalFile = 3000
+const contentLength = 5000
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.60-worker-pool")
+```
+
+#### • Fungsi `main()`
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ generateFiles()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+#### • Fungsi `randomString()`
+
+```go
+func randomString(length int) string {
+ randomizer := rand.New(rand.NewSource(time.Now().Unix()))
+ letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = letters[randomizer.Intn(len(letters))]
+ }
+
+ return string(b)
+}
+
+```
+
+#### • Fungsi `generateFiles()`
+
+```go
+func generateFiles() {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ for i := 0; i < totalFile; i++ {
+ filename := filepath.Join(tempPath, fmt.Sprintf("file-%d.txt", i))
+ content := randomString(contentLength)
+ err := ioutil.WriteFile(filename, []byte(content), os.ModePerm)
+ if err != nil {
+ log.Println("Error writing file", filename)
+ }
+
+ log.Println(i, "files created")
+ }
+
+ log.Printf("%d of total files created", totalFile)
+}
+```
+
+Pada bagian fungsi `generateFiles()` kali ini sedikit berbeda dibanding sebelumnya. Di sini log `file created` tidak ditampilkan per seratus data, melainkan setiap file sukses dibuat. Ini memang akan berpengaruh ke performa, tapi diperlukan untuk perbandingan antara file-file yang di-generate secara sekuensial vs file-file yang di-generate secara konkuren.
+
+Kita lanjut dulu saja. Berikut adalah output jika program di atas di-run.
+
+
+
+## A.63.3. Program Generate Dummy File *Concurrently*
+
+Sekarang saya buat file program `2-generate-dummy-files-concurrently.go` yang isinya adalah sama yaitu untuk keperluan generate dummy files, tapi pembuatannya dilakukan secara konkuren.
+
+#### • Import Packages dan Definisi Variabel
+
+Import beberapa hal pada file baru ini, lalu definisikan beberapa variabel juga.
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+const totalFile = 3000
+const contentLength = 5000
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.60-worker-pool")
+```
+
+#### • Definisi struct `FileInfo`
+
+Kita perlu siapkan struct baru bernama `FileInfo`, struct ini digunakan sebagai skema payload data ketika dikirimkan via channel dari goroutine jobs ke goroutine worker.
+
+```go
+type FileInfo struct {
+ Index int
+ FileName string
+ WorkerIndex int
+ Err error
+}
+```
+
+* Property `Index` dan `FileName` menurut saya cukup jelas, isinya adalah angka dan nama file. Nama file sendiri formatnya adalah `file-.txt`.
+* Property `WorkerIndex` digunakan sebagai penanda worker mana yang akan melakukan operasi pembuatan file tersebut.
+* Property `Err` default isinya kosong. Nantinya akan diisi dengan objek error ketika ada error saat pembuatan file.
+
+#### • Fungsi `main()`
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ generateFiles()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+#### • Fungsi `randomString()`
+
+```go
+func randomString(length int) string {
+ randomizer := rand.New(rand.NewSource(time.Now().Unix()))
+ letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = letters[randomizer.Intn(len(letters))]
+ }
+
+ return string(b)
+}
+```
+
+#### • Fungsi `generateFiles()`
+
+```go
+func generateFiles() {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ // pipeline 1: job distribution
+ chanFileIndex := generateFileIndexes()
+
+ // pipeline 2: the main logic (creating files)
+ createFilesWorker := 100
+ chanFileResult := createFiles(chanFileIndex, createFilesWorker)
+
+ // track and print output
+ counterTotal := 0
+ counterSuccess := 0
+ for fileResult := range chanFileResult {
+ if fileResult.Err != nil {
+ log.Printf("error creating file %s. stack trace: %s", fileResult.FileName, fileResult.Err)
+ } else {
+ counterSuccess++
+ }
+ counterTotal++
+ }
+
+ log.Printf("%d/%d of total files created", counterSuccess, counterTotal)
+}
+```
+
+Isi fungsi generate files ini secara garis besar ada 3:
+
+* Pipeline 1, bertugas men-*dispatch* goroutine untuk distribusi jobs.
+* Pipeline 2, bertugas men-*dispatch* goroutine untuk start worker yang masing-masing worker punya tugas utama yaitu membuat files.
+* Terakhir, tracking channel dari Fan-in nilai balik fungsi pipeline ke-2.
+
+Fungsi `generateFileIndexes()` nantinya akan mengembalikan channel `chanFileIndex` yang fungsi dari channel ini adalah untuk media komunikasi antara proses dalam fungsi `generateFileIndexes()` (yaitu distribusi jobs) dengan proses dalam fungsi selanjutnya yaitu `createFiles()`.
+
+Fungsi `createFiles()` di sini merupakan fungsi **Fan-out Fan-in** karena menerima parameter channel pipeline sebelumnya, lalu kemudian min-dispatch goroutine worker dan menge-track output dari masing-masing worker ke channel output. Jadi fungsi `createFiles()` ini merupakan gabungan dari fungsi Fan-out dan Fan-in (proses merge channel output dari Fan-out juga ada di dalam fungsi tersebut).
+
+Fungsi `createFiles()` menghasilkan channel yang isinya merupakan result dari operasi tiap-tiap jobs. Dari data yang dilewatkan via channel tersebut akan ketahuan misal ada error atau tidak saat pembuatan files. Channel tersebut kemudian di-loop lalu ditampilkan tiap-tiap result-nya.
+
+#### • Fungsi `generateFileIndexes()`
+
+Fungsi ini merupakan fungsi Fan-out distribusi jobs. Di dalamnya dilakukan perulangan sejumlah `totalFile`, kemudian data tiap index digunakan untuk pembentukan filename lalu dikirim ke channel outputnya.
+
+```go
+func generateFileIndexes() <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for i := 0; i < totalFile; i++ {
+ chanOut <- FileInfo{
+ Index: i,
+ FileName: fmt.Sprintf("file-%d.txt", i),
+ }
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Setelah dipastikan semua job terkirim, kita close channel output `chanOut` tersebut.
+
+#### • Fungsi `dispatchWorkers()`
+
+Bagian ini merupakan yang paling butuh *effort* untuk dipahami. Jadi fungsi `createFiles()` seperti yang sudah saja jelaskan secara singkat di atas, fungsi ini merupakan fungsi gabungan Fan-out (menerima channel output dari pipeline sebelumnya) dan juga Fan-in (menjalankan beberapa worker untuk memproses channel output dari pipeline sebelumnya, lalu output masing-masing worker yang juga merupakan channel - langsung di merge jadi satu channel saja).
+
+Mungkin lebih enak silakan tulis dulu fungsinya, lalu kita bahas satu per satu setelahnya.
+
+```go
+func createFiles(chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ // wait group to control the workers
+ wg := new(sync.WaitGroup)
+
+ // allocate N of workers
+ wg.Add(numberOfWorkers)
+
+ go func() {
+
+ // dispatch N workers
+ for workerIndex := 0; workerIndex < numberOfWorkers; workerIndex++ {
+ go func(workerIndex int) {
+
+ // listen to `chanIn` channel for incoming jobs
+ for job := range chanIn {
+
+ // do the jobs
+ filePath := filepath.Join(tempPath, job.FileName)
+ content := randomString(contentLength)
+ err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+
+ log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
+
+ // construct the job's result, and send it to `chanOut`
+ chanOut <- FileInfo{
+ FileName: job.FileName,
+ WorkerIndex: workerIndex,
+ Err: err,
+ }
+ }
+
+ // if `chanIn` is closed, and the remaining jobs are finished,
+ // only then we mark the worker as complete.
+ wg.Done()
+ }(workerIndex)
+ }
+ }()
+
+ // wait until `chanIn` closed and then all workers are done,
+ // because right after that - we need to close the `chanOut` channel.
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Penjelasan:
+
+1. Pertama-tama, kita siapkan `chanOut` yang merupakan channel output Fan-in dari worker-worker yang ada. Channel ini langsung dijadikan nilai balik fungsi `createFiles()`. Saya gunakan kata **langsung** di situ karena semua proses lainnya selain deklarasi channel dan waitgroup - adalah berjalan secara asynchronous via goroutine.
+2. Kemudian objek `sync.WaitGroup` didefinisikan, lalu di-*notify* bahwa akan ada sejumlah `numberOfWorkers` workers berjalan secara konkuren dan harus ditunggu. Jadi waitgroup ini untuk keperluan manajemen worker-nya.
+3. Jalankan goroutine yang isinya dispatch sejumlah `numberOfWorkers` workers. Karena pada bagian ini ada goroutine dalam perulangan, maka informasi perulangan yang akan digunakan di dalam goroutine harus dijadikan argumen eksekusi goroutine (dalam contoh ini `workerIndex`).
+4. Pantau channel `chanIn`, setiap ada job yg masuk maka kerjakan.
+5. Output dari eksekusi job ada dua yaitu: error, atau tidak error. Informasi *truthy* ini disimpan dalam objek `FileInfo` result yang kemudian di-kirim ke `chanOut`.
+6. Jika perulangan terhadap `chanIn` sudah selesai (ditandai dengan channel-nya closed), maka kita tandai juga worker sebagai complete via statement `wg.Done()`
+7. Dispatch goroutine baru lagi untuk menunggu semua worker selesai. Jika iya, maka kita close channel `chanOut`.
+
+Semoga cukup jelas ya. Kelebihan metode ini ini salah satunya adalah kita bisa dengan mudah menentukan jumlah workernya.
+
+> Untuk temen-temen yang bingung, mungkin fungsi ini bisa dipecah menjadi satu fungsi Fan-out dan satu fungsi Fan-in seperti chapter sebelumnya.
+
+## A.63.4. Test Eksekusi Program
+
+Saya akan coba jalankan program pertama dan kedua, lalu mari kita lihat perbedaannya.
+
+#### • Program Generate Dummy File *Sequentially*
+
+
+
+Testing di awal chapter ini hasilnya butuh sekitar **19 detik** untuk menyelesaikan generate dummy files sebanyak 3000 secara sekuensial. Tapi kali ini lebih lambat, yaitu **23 detik** dan ini wajar, karena di tiap operasi kita munculkan log ke stdout (via `log.Println()`).
+
+#### • Program Generate Dummy File *Concurrently*
+
+
+
+Bandingkan dengan ini, **3 detik** saja! luar biasa sekali bukan beda performanya. Dan pastinya akan lebih cepat lagi kalau kita hapus statement untuk logging ke stdout (`log.Println()`).
+
+Nah dari sini semoga cukup jelas ya bedanya kalau dari sisi performa. Inilah pentingnya kenapa konkurensi di Go harus diterapkan (untuk kasus yang memang bisa di-konkurensikan prosesnya). Tapi temen-temen juga harus hati-hati dalam mendesain pipeline dan menentukan jumlah workernya, karena jika tidak tepat bisa makan *resources* seperti CPU dan RAM cukup tinggi.
+
+> Untuk menentukan jumlah worker yang ideal, caranya adalah dengan coba-coba dan disesuaikan dengan spesifikasi server/laptopnya. Jadi tidak ada angka yang pasti berapa jumlah worker ideal. Sangat tergantung ke banyak hal (jenis proses, jumlah pipeline, jumlah worker per pipeline, spesifikasi hardware, dsb).
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 1feac978d13f4f3be85a3e8be96437bb4ed5cc01 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:47 +0700
Subject: [PATCH 051/522] New translations a-slice.md (English)
---
en/content-en/A-slice.md | 267 +++++++++++++++++++++++++++++++++++++++
1 file changed, 267 insertions(+)
create mode 100644 en/content-en/A-slice.md
diff --git a/en/content-en/A-slice.md b/en/content-en/A-slice.md
new file mode 100644
index 000000000..f913e377f
--- /dev/null
+++ b/en/content-en/A-slice.md
@@ -0,0 +1,267 @@
+# A.16. Slice
+
+**Slice** adalah *reference* elemen array. Slice bisa dibuat, atau bisa juga dihasilkan dari manipulasi sebuah array ataupun slice lainnya. Karena merupakan data *reference*, menjadikan perubahan data di tiap elemen slice akan berdampak pada slice lain yang memiliki alamat memori yang sama.
+
+## A.16.1. Inisialisasi Slice
+
+Cara pembuatan slice mirip seperti pembuatan array, bedanya tidak perlu mendefinisikan jumlah elemen ketika awal deklarasi. Pengaksesan nilai elemen-nya juga sama. Contoh pembuatan slice:
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+fmt.Println(fruits[0]) // "apple"
+```
+
+Salah satu perbedaan slice dan array bisa diketahui pada saat deklarasi variabel-nya, jika jumlah elemen tidak dituliskan, maka variabel tersebut adalah slice.
+
+```go
+var fruitsA = []string{"apple", "grape"} // slice
+var fruitsB = [2]string{"banana", "melon"} // array
+var fruitsC = [...]string{"papaya", "grape"} // array
+```
+
+## A.16.2. Hubungan Slice Dengan Array & Operasi Slice
+
+Kalau perbedannya hanya di penentuan alokasi pada saat inisialisasi, kenapa tidak menggunakan satu istilah saja? atau adakah perbedaan lainnya?
+
+Sebenarnya slice dan array tidak bisa dibedakan karena merupakan sebuah kesatuan. Array adalah kumpulan nilai atau elemen, sedang slice adalah referensi tiap elemen tersebut.
+
+Slice bisa dibentuk dari array yang sudah didefinisikan, caranya dengan memanfaatkan teknik **2 index** untuk mengambil elemen-nya. Contoh bisa dilihat pada kode berikut.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+var newFruits = fruits[0:2]
+
+fmt.Println(newFruits) // ["apple", "grape"]
+```
+
+Kode `fruits[0:2]` maksudnya adalah pengaksesan elemen dalam slice `fruits` yang **dimulai dari indeks ke-0, hingga elemen sebelum indeks ke-2**. Elemen yang memenuhi kriteria tersebut akan didapat, untuk kemudian disimpan pada variabel lain sebagai slice baru. Pada contoh di atas, `newFruits` adalah slice baru yang tercetak dari slice `fruits`, dengan isi 2 elemen, yaitu `"apple"` dan `"grape"`.
+
+
+
+Ketika mengakses elemen array menggunakan satu buah indeks (seperti `data[2]`), nilai yang didapat merupakan hasil **copy** dari referensi aslinya. Berbeda dengan pengaksesan elemen menggunakan 2 indeks (seperti `data[0:2]`), nilai yang didapat adalah *reference* elemen atau slice.
+
+> Tidak apa jikalau pembaca masih bingung, di bawah akan dijelaskan lebih mendetail lagi tentang slice dan *reference*
+
+Tabel berikut adalah list operasi operasi menggunakan teknik 2 indeks yang bisa dilakukan.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+```
+
+| Kode | Output | Penjelasan |
+|:------------- |:------------------------------- |:------------------------------------------------------------------------------------ |
+| `fruits[0:2]` | `[apple, grape]` | semua elemen mulai indeks ke-0, hingga sebelum indeks ke-2 |
+| `fruits[0:4]` | [apple, grape, banana, melon] | semua elemen mulai indeks ke-0, hingga sebelum indeks ke-4 |
+| `fruits[0:0]` | `[]` | menghasilkan slice kosong, karena tidak ada elemen sebelum indeks ke-0 |
+| `fruits[4:4]` | `[]` | menghasilkan slice kosong, karena tidak ada elemen yang dimulai dari indeks ke-4 |
+| `fruits[4:0]` | `[]` | error, pada penulisan `fruits[a:b]` nilai `a` harus lebih kecil atau sama dengan `b` |
+| `fruits[:]` | `[apple, grape, banana, melon]` | semua elemen |
+| `fruits[2:]` | `[banana, melon]` | semua elemen mulai indeks ke-2 |
+| `fruits[:2]` | `[apple, grape]` | semua elemen hingga sebelum indeks ke-2 |
+
+## A.16.3. Slice Merupakan Tipe Data Reference
+
+Slice merupakan tipe data *reference* atau referensi. Artinya jika ada slice baru yang terbentuk dari slice lama, maka data elemen slice yang baru akan memiliki alamat memori yang sama dengan elemen slice lama. Setiap perubahan yang terjadi di elemen slice baru, akan berdampak juga pada elemen slice lama yang memiliki referensi yang sama.
+
+Program berikut merupakan pembuktian tentang teori yang baru kita bahas. Kita akan mencoba mengubah data elemen slice baru, yang terbentuk dari slice lama.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+
+var aFruits = fruits[0:3]
+var bFruits = fruits[1:4]
+
+var aaFruits = aFruits[1:2]
+var baFruits = bFruits[0:1]
+
+fmt.Println(fruits) // [apple grape banana melon]
+fmt.Println(aFruits) // [apple grape banana]
+fmt.Println(bFruits) // [grape banana melon]
+fmt.Println(aaFruits) // [grape]
+fmt.Println(baFruits) // [grape]
+
+// Buah "grape" diubah menjadi "pinnaple"
+baFruits[0] = "pinnaple"
+
+fmt.Println(fruits) // [apple pinnaple banana melon]
+fmt.Println(aFruits) // [apple pinnaple banana]
+fmt.Println(bFruits) // [pinnaple banana melon]
+fmt.Println(aaFruits) // [pinnaple]
+fmt.Println(baFruits) // [pinnaple]
+```
+
+Sekilas bisa kita lihat bahwa setelah slice yang isi datanya adalah `grape` di-ubah menjadi `pinnaple`, semua slice pada 4 variabel lainnya juga ikut berubah.
+
+Variabel `aFruits`, `bFruits` merupakan slice baru yang terbentuk dari variabel `fruits`. Dengan menggunakan dua slice baru tersebut, diciptakan lagi slice lainnya, yaitu `aaFruits`, dan `baFruits`. Kelima slice tersebut ditampilkan nilainya.
+
+Selanjutnya, nilai dari `baFruits[0]` diubah, dan 5 slice tadi ditampilkan lagi. Hasilnya akan ada banyak slice yang elemennya ikut berubah. Yaitu elemen-elemen yang referensi-nya sama dengan referensi elemen `baFruits[0]`.
+
+
+
+Bisa dilihat pada output di atas, elemen yang sebelumnya bernilai `"grape"` pada variabel `fruits`, `aFruits`, `bFruits`, `aaFruits`, dan `baFruits`; Seluruhnya berubah menjadi `"pinnaple"`, karena memiliki referensi yang sama.
+
+---
+
+Pembahasan mengenai dasar slice sepertinya sudah cukup, selanjutnya kita akan membahas tentang beberapa *built in function* bawaan Go, yang bisa dimanfaatkan untuk keperluan operasi slice.
+
+## A.16.4. Fungsi `len()`
+
+Fungsi `len()` digunakan untuk menghitung jumlah elemen slice yang ada. Sebagai contoh jika sebuah variabel adalah slice dengan data 4 buah, maka fungsi ini pada variabel tersebut akan mengembalikan angka **4**.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+fmt.Println(len(fruits)) // 4
+```
+
+## A.16.5. Fungsi `cap()`
+
+Fungsi `cap()` digunakan untuk menghitung lebar atau kapasitas maksimum slice. Nilai kembalian fungsi ini untuk slice yang baru dibuat pasti sama dengan `len`, tapi bisa berubah seiring operasi slice yang dilakukan. Agar lebih jelas, silakan disimak kode berikut.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+fmt.Println(len(fruits)) // len: 4
+fmt.Println(cap(fruits)) // cap: 4
+
+var aFruits = fruits[0:3]
+fmt.Println(len(aFruits)) // len: 3
+fmt.Println(cap(aFruits)) // cap: 4
+
+var bFruits = fruits[1:4]
+fmt.Println(len(bFruits)) // len: 3
+fmt.Println(cap(bFruits)) // cap: 3
+```
+
+Variabel `fruits` disiapkan di awal dengan jumlah elemen 4, fungsi `len(fruits)` dan `cap(fruits)` pasti hasinya 4.
+
+Variabel `aFruits` dan `bFruits` merupakan slice baru berisikan 3 buah elemen milik slice `fruits`. Variabel `aFruits` mengambil elemen index 0, 1, 2; sedangkan `bFruits` 1, 2, 3.
+
+Fungsi `len()` menghasilkan angka 3, karena jumlah elemen kedua slice ini adalah 3. Tetapi `cap(aFruits)` menghasilkan angka yang berbeda, yaitu 4 untuk `aFruits` dan 3 untuk `bFruits`. kenapa? jawabannya bisa dilihat pada tabel berikut.
+
+| Kode | Output | `len()` | `cap()` |
+|:-------------- |:--------------------------------- |:-------:|:-------:|
+| `fruits[0:4]` | [**`buah` `buah` `buah` `buah`**] | 4 | 4 |
+| `aFruits[0:3]` | [**`buah` `buah` `buah`** `----`] | 3 | 4 |
+| `bFruits[1:4]` | `----` [**`buah` `buah` `buah`**] | 3 | 3 |
+
+Kita analogikan slicing 2 index menggunakan **x** dan **y**.
+
+```go
+fruits[x:y]
+```
+
+**Slicing** yang dimulai dari indeks **0** hingga **y** akan mengembalikan elemen-elemen mulai indeks **0** hingga sebelum indeks **y**, dengan lebar kapasitas adalah sama dengan slice aslinya.
+
+Sedangkan slicing yang dimulai dari indeks **x**, yang di mana nilai **x** adalah lebih dari **0**, membuat elemen ke-**x** slice yang diambil menjadi elemen ke-0 slice baru. Hal inilah yang membuat kapasitas slice berubah.
+
+## A.16.6. Fungsi `append()`
+
+Fungsi `append()` digunakan untuk menambahkan elemen pada slice. Elemen baru tersebut diposisikan setelah indeks paling akhir. Nilai balik fungsi ini adalah slice yang sudah ditambahkan nilai barunya. Contoh penggunaannya bisa dilihat di kode berikut.
+
+```go
+var fruits = []string{"apple", "grape", "banana"}
+var cFruits = append(fruits, "papaya")
+
+fmt.Println(fruits) // ["apple", "grape", "banana"]
+fmt.Println(cFruits) // ["apple", "grape", "banana", "papaya"]
+```
+
+Ada 3 hal yang perlu diketahui dalam penggunaan fungsi ini.
+
+ - Ketika jumlah elemen dan lebar kapasitas adalah sama (`len(fruits) == cap(fruits)`), maka elemen baru hasil `append()` merupakan referensi baru.
+ - Ketika jumlah elemen lebih kecil dibanding kapasitas (`len(fruits) < cap(fruits)`), elemen baru tersebut ditempatkan ke dalam cakupan kapasitas, menjadikan semua elemen slice lain yang referensi-nya sama akan berubah nilainya.
+
+Agar lebih jelas silakan perhatikan contoh berikut.
+
+```go
+var fruits = []string{"apple", "grape", "banana"}
+var bFruits = fruits[0:2]
+
+fmt.Println(cap(bFruits)) // 3
+fmt.Println(len(bFruits)) // 2
+
+fmt.Println(fruits) // ["apple", "grape", "banana"]
+fmt.Println(bFruits) // ["apple", "grape"]
+
+var cFruits = append(bFruits, "papaya")
+
+fmt.Println(fruits) // ["apple", "grape", "papaya"]
+fmt.Println(bFruits) // ["apple", "grape"]
+fmt.Println(cFruits) // ["apple", "grape", "papaya"]
+```
+
+Pada contoh di atas bisa dilihat, elemen indeks ke-2 slice `fruits` nilainya berubah setelah ada penggunaan keyword `append()` pada `bFruits`. Slice `bFruits` kapasitasnya adalah **3** sedang jumlah datanya hanya **2**. Karena `len(bFruits) < cap(bFruits)`, maka elemen baru yang dihasilkan, terdeteksi sebagai perubahan nilai pada referensi yang lama (referensi elemen indeks ke-2 slice `fruits`), membuat elemen yang referensinya sama, nilainya berubah.
+
+## A.16.7. Fungsi `copy()`
+
+Fungsi `copy()` digunakan untuk men-copy elements slice pada `src` (parameter ke-2), ke `dst` (parameter pertama).
+
+```go
+copy(dst, src)
+```
+
+Jumlah element yang di-copy dari `src` adalah sejumlah lebar slice `dst` (atau `len(dst)`). Jika jumlah slice pada `src` lebih kecil dari `dst`, maka akan ter-copy semua. Lebih jelasnya silakan perhatikan contoh berikut.
+
+```go
+dst := make([]string, 3)
+src := []string{"watermelon", "pinnaple", "apple", "orange"}
+n := copy(dst, src)
+
+fmt.Println(dst) // watermelon pinnaple apple
+fmt.Println(src) // watermelon pinnaple apple orange
+fmt.Println(n) // 3
+```
+
+Pada kode di atas variabel slice `dst` dipersiapkan dengan lebar adalah 3 elements. Slice `src` yang isinya 4 elements, di-copy ke `dst`. Menjadikan isi slice `dst` sekarang adalah 3 buah elements yang sama dengan 3 buah elements `src`, hasil dari operasi `copy()`.
+
+Yang ter-copy hanya 3 buah (meski `src` memiliki 4 elements) hal ini karena `copy()` hanya meng-copy elements sebanyak `len(dst)`.
+
+> Fungsi `copy()` mengembalikan informasi angka, representasi dari jumlah element yang berhasil di-copy.
+
+Pada contoh kedua berikut, `dst` merupakan slice yang sudah ada isinya, 3 buah elements. Variabel `src` yang juga merupakan slice dengan isi dua elements, di-copy ke `dst`. Karena operasi `copy()` akan meng-copy sejumlah `len(dst)`, maka semua elements `src` akan ter-copy **karena jumlahnya di bawah atau sama dengan lebar** `dst`.
+
+```go
+dst := []string{"potato", "potato", "potato"}
+src := []string{"watermelon", "pinnaple"}
+n := copy(dst, src)
+
+fmt.Println(dst) // watermelon pinnaple potato
+fmt.Println(src) // watermelon pinnaple
+fmt.Println(n) // 2
+```
+
+Jika dilihat pada kode di atas, isi `dst` masih tetap 3 elements, tapi dua elements pertama adalah sama dengan `src`. Element terakhir `dst` isinya tidak berubah, tetap `potato`, hal ini karena proses copy hanya memutasi element ke-1 dan ke-2 milik `dst`, karena memang pada `src` hanya dua itu elements-nya.
+
+## A.16.8. Pengaksesan Elemen Slice Dengan 3 Indeks
+
+**3 index** adalah teknik slicing elemen yang sekaligus menentukan kapasitasnya. Cara menggunakannnya yaitu dengan menyisipkan angka kapasitas di belakang, seperti `fruits[0:1:1]`. Angka kapasitas yang diisikan tidak boleh melebihi kapasitas slice yang akan di slicing.
+
+Berikut merupakan contoh penerapannya.
+
+```go
+var fruits = []string{"apple", "grape", "banana"}
+var aFruits = fruits[0:2]
+var bFruits = fruits[0:2:2]
+
+fmt.Println(fruits) // ["apple", "grape", "banana"]
+fmt.Println(len(fruits)) // len: 3
+fmt.Println(cap(fruits)) // cap: 3
+
+fmt.Println(aFruits) // ["apple", "grape"]
+fmt.Println(len(aFruits)) // len: 2
+fmt.Println(cap(aFruits)) // cap: 3
+
+fmt.Println(bFruits) // ["apple", "grape"]
+fmt.Println(len(bFruits)) // len: 2
+fmt.Println(cap(bFruits)) // cap: 2
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From d319aa80978492c98f0d0c3bf91bc5f226e18268 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:48 +0700
Subject: [PATCH 052/522] New translations a-sql.md (English)
---
en/content-en/A-sql.md | 347 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 347 insertions(+)
create mode 100644 en/content-en/A-sql.md
diff --git a/en/content-en/A-sql.md b/en/content-en/A-sql.md
new file mode 100644
index 000000000..39c3e20aa
--- /dev/null
+++ b/en/content-en/A-sql.md
@@ -0,0 +1,347 @@
+# A.56. SQL
+
+Go menyediakan package `database/sql` berisikan generic interface untuk keperluan interaksi dengan database sql. Package ini hanya bisa digunakan ketika **driver** database engine yang dipilih juga ada.
+
+Ada cukup banyak sql driver yang tersedia untuk Go, detailnya bisa diakses di [https://github.com/golang/go/wiki/SQLDrivers](https://github.com/golang/go/wiki/SQLDrivers). Beberapa di antaranya:
+
+ - MySql
+ - Oracle
+ - MS Sql Server
+ - dan lainnya
+
+Driver-driver tersebut merupakan project open source yang diinisiasi oleh komunitas di Github. Artinya kita selaku developer juga bisa ikut berkontribusi di dalamnya.
+
+Pada chapter ini kita akan belajar bagaimana berkomunikasi dengan database MySQL menggunakan driver [Go MySQL Driver](https://github.com/go-sql-driver/mysql).
+
+## A.56.1. Instalasi Driver
+
+Unduh driver mysql menggunakan `go get`.
+
+```
+cd
+go get github.com/go-sql-driver/mysql
+```
+
+
+
+## A.56.2. Setup Database
+
+> Sebelumnya pastikan sudah ada [mysql server](https://dev.mysql.com/downloads/mysql/) yang terinstal di lokal anda.
+
+Buat database baru bernama `db_belajar_golang`, dan tabel baru bernama `tb_student`.
+
+```sql
+CREATE TABLE IF NOT EXISTS `tb_student` (
+ `id` varchar(5) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `age` int(11) NOT NULL,
+ `grade` int(11) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+INSERT INTO `tb_student` (`id`, `name`, `age`, `grade`) VALUES
+('B001', 'Jason Bourne', 29, 1),
+('B002', 'James Bond', 27, 1),
+('E001', 'Ethan Hunt', 27, 2),
+('W001', 'John Wick', 28, 2);
+
+ALTER TABLE `tb_student` ADD PRIMARY KEY (`id`);
+```
+
+## A.56.3. Membaca Data Dari MySQL Server
+
+Import package yang dibutuhkan, lalu disiapkan struct dengan skema yang sama seperti pada tabel `tb_student` di database. Nantinya struct ini digunakan sebagai tipe data penampung hasil query.
+
+```go
+package main
+
+import "fmt"
+import "database/sql"
+import _ "github.com/go-sql-driver/mysql"
+
+type student struct {
+ id string
+ name string
+ age int
+ grade int
+}
+```
+
+Driver database yang digunakan perlu di-import menggunakan tanda `_`, karena meskipun dibutuhkan oleh package `database/sql`, kita tidak langsung berinteraksi dengan driver tersebut.
+
+Selanjutnya buat fungsi untuk koneksi ke database.
+
+```go
+func connect() (*sql.DB, error) {
+ db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/db_belajar_golang")
+ if err != nil {
+ return nil, err
+ }
+
+ return db, nil
+}
+```
+
+Fungsi `sql.Open()` digunakan untuk memulai koneksi dengan database. Fungsi tersebut memiliki 2 parameter mandatory, nama driver dan **connection string**.
+
+Skema connection string untuk driver mysql yang kita gunakan cukup unik, `root@tcp(127.0.0.1:3306)/db_belajar_golang`. Di bawah ini merupakan skema connection string yang bisa digunakan pada driver Go MySQL Driver. Jika anda menggunakan driver mysql lain, skema koneksinya bisa saja berbeda tergantung driver yang digunakan.
+```
+user:password@tcp(host:port)/dbname
+user@tcp(host:port)/dbname
+```
+
+Di bawah ini adalah penjelasan mengenai connection string yang digunakan pada fungsi `connect()`.
+
+```
+root@tcp(127.0.0.1:3306)/db_belajar_golang
+// user => root
+// password =>
+// host => 127.0.0.1 atau localhost
+// port => 3306
+// dbname => db_belajar_golang
+```
+
+Setelah fungsi untuk konektivitas dengan database sudah dibuat, saatnya untuk mempraktekan proses pembacaan data dari server database. Siapkan fungsi `sqlQuery()` dengan isi adalah kode berikut.
+
+```go
+func sqlQuery() {
+ db, err := connect()
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer db.Close()
+
+ var age = 27
+ rows, err := db.Query("select id, name, grade from tb_student where age = ?", age)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer rows.Close()
+
+ var result []student
+
+ for rows.Next() {
+ var each = student{}
+ var err = rows.Scan(&each.id, &each.name, &each.grade)
+
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ result = append(result, each)
+ }
+
+ if err = rows.Err(); err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ for _, each := range result {
+ fmt.Println(each.name)
+ }
+}
+```
+
+Setiap kali terbuat koneksi baru, jangan lupa untuk selalu **close** instance koneksinya. Bisa menggunakan keyword `defer` seperti pada kode di atas, `defer db.Close()`.
+
+Fungsi `db.Query()` digunakan untuk eksekusi sql query. Fungsi tersebut parameter keduanya adalah variadic, sehingga boleh tidak diisi. Pada kode di atas bisa dilihat bahwa nilai salah satu clause `where` adalah tanda tanya (`?`). Tanda tersebut kemudian akan ter-replace oleh nilai pada parameter setelahnya (nilai variabel `age`). Teknik penulisan query sejenis ini sangat dianjurkan, untuk mencegah [sql injection](https://en.wikipedia.org/wiki/SQL_injection).
+
+Fungsi tersebut menghasilkan instance bertipe `sql.*Rows`, yang juga perlu di **close** ketika sudah tidak digunakan (`defer rows.Close()`).
+
+Selanjutnya, sebuah array dengan tipe elemen struct `student` disiapkan dengan nama `result`. Nantinya hasil query akan ditampung ke variabel tersebut.
+
+Kemudian dilakukan perulangan dengan acuan kondisi adalah `rows.Next()`. Perulangan dengan cara ini dilakukan sebanyak jumlah total record yang ada, berurutan dari record pertama hingga akhir, satu per satu.
+
+Method `Scan()` milik `sql.Rows` berfungsi untuk mengambil nilai record yang sedang diiterasi, untuk disimpan pada variabel pointer. Variabel yang digunakan untuk menyimpan field-field record dituliskan berurutan sebagai parameter variadic, sesuai dengan field yang di select pada query. Silakan lihat perbandingan di bawah ini unuk lebih jelasnya.
+
+```
+// query
+select id, name, grade ...
+
+// scan
+rows.Scan(&each.id, &each.name, &each.grade ...
+```
+
+Data record yang didapat kemudian di-append ke slice `result`, lewat statement `result = append(result, each)`.
+
+OK, sekarang tinggal panggil fungsi `sqlQuery()` di `main`, lalu jalankan program.
+
+```go
+func main() {
+ sqlQuery()
+}
+```
+
+Output:
+
+
+
+## A.56.4. Membaca 1 Record Data Menggunakan Method `QueryRow()`
+
+Untuk query yang menghasilkan 1 baris record saja, bisa gunakan method `QueryRow()`, dengan metode ini kode menjadi lebih ringkas. Chain dengan method `Scan()` untuk mendapatkan value-nya.
+
+```go
+func sqlQueryRow() {
+ var db, err = connect()
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer db.Close()
+
+ var result = student{}
+ var id = "E001"
+ err = db.
+ QueryRow("select name, grade from tb_student where id = ?", id).
+ Scan(&result.name, &result.grade)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ fmt.Printf("name: %s\ngrade: %d\n", result.name, result.grade)
+}
+
+func main() {
+ sqlQueryRow()
+}
+```
+
+Dari kode di atas ada statement yang dituliskan cukup unik, chain statement boleh dituliskan dalam beberapa baris, contohnya:
+
+```go
+err = db.
+ QueryRow("select name, grade from tb_student where id = ?", id).
+ Scan(&result.name, &result.grade)
+```
+
+Sekarang jalankan program. Outputnya akan muncul data record sesuai id.
+
+
+
+## A.56.5. Eksekusi Query Menggunakan `Prepare()`
+
+Teknik **prepared statement** adalah teknik penulisan query di awal dengan kelebihan bisa di re-use atau digunakan banyak kali untuk eksekusi yang berbeda-beda.
+
+Metode ini bisa digabung dengan `Query()` maupun `QueryRow()`. Berikut merupakan contoh penerapannya.
+
+```go
+func sqlPrepare() {
+ db, err := connect()
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer db.Close()
+
+ stmt, err := db.Prepare("select name, grade from tb_student where id = ?")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ var result1 = student{}
+ stmt.QueryRow("E001").Scan(&result1.name, &result1.grade)
+ fmt.Printf("name: %s\ngrade: %d\n", result1.name, result1.grade)
+
+ var result2 = student{}
+ stmt.QueryRow("W001").Scan(&result2.name, &result2.grade)
+ fmt.Printf("name: %s\ngrade: %d\n", result2.name, result2.grade)
+
+ var result3 = student{}
+ stmt.QueryRow("B001").Scan(&result3.name, &result3.grade)
+ fmt.Printf("name: %s\ngrade: %d\n", result3.name, result3.grade)
+}
+
+func main() {
+ sqlPrepare()
+}
+```
+
+Method `Prepare()` digunakan untuk deklarasi query, yang mengembalikan objek bertipe `sql.*Stmt`. Dari objek tersebut, dipanggil method `QueryRow()` beberapa kali dengan isi value untuk `id` berbeda-beda untuk tiap pemanggilannya.
+
+
+
+## A.56.6. Insert, Update, & Delete Data Menggunakan `Exec()`
+
+Untuk operasi **insert**, **update**, dan **delete**; dianjurkan untuk tidak menggunakan fungsi `sql.Query()` ataupun `sql.QueryRow()` untuk eksekusinya. Direkomendasikan eksekusi perintah-perintah tersebut lewat fungsi `Exec()`, contohnya seperti pada kode berikut.
+
+```go
+func sqlExec() {
+ db, err := connect()
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer db.Close()
+
+ _, err = db.Exec("insert into tb_student values (?, ?, ?, ?)", "G001", "Galahad", 29, 2)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ fmt.Println("insert success!")
+
+ _, err = db.Exec("update tb_student set age = ? where id = ?", 28, "G001")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ fmt.Println("update success!")
+
+ _, err = db.Exec("delete from tb_student where id = ?", "G001")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ fmt.Println("delete success!")
+}
+
+func main() {
+ sqlExec()
+}
+```
+
+Teknik prepared statement juga bisa digunakan pada metode ini. Berikut adalah perbandingan eksekusi `Exec()` menggunakan `Prepare()` dan cara biasa.
+
+```go
+// menggunakan metode prepared statement
+stmt, err := db.Prepare("insert into tb_student values (?, ?, ?, ?)")
+stmt.Exec("G001", "Galahad", 29, 2)
+
+// menggunakan metode biasa
+_, err := db.Exec("insert into tb_student values (?, ?, ?, ?)", "G001", "Galahad", 29, 2)
+```
+
+## A.56.7. Koneksi Dengan Engine Database Lain
+
+Karena package `database/sql` merupakan interface generic, maka cara untuk koneksi ke engine database lain (semisal Oracle, Postgres, SQL Server) adalah sama dengan cara koneksi ke MySQL. Cukup dengan meng-import driver yang digunakan, lalu mengganti nama driver pada saat pembuatan koneksi baru.
+
+```go
+sql.Open(driverName, connectionString)
+```
+
+Sebagai contoh saya menggunakan driver [pq](https://github.com/lib/pq) untuk koneksi ke server Postgres, maka connection string-nya:
+
+```go
+sql.Open("postgres", "user=postgres password=secret dbname=test sslmode=disable")
+```
+
+Selengkapya mengenai driver yang tersedia bisa dilihat di [https://github.com/golang/go/wiki/SQLDrivers](https://github.com/golang/go/wiki/SQLDrivers).
+
+---
+
+- [Go MySQL Driver](https://github.com/go-sql-driver/mysql), by Julien Schmidt, MPL-2.0 license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 46872fb1ba19780c39acfd1b6c63e9eec38335b3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:49 +0700
Subject: [PATCH 053/522] New translations a-string-format.md (English)
---
en/content-en/A-string-format.md | 279 +++++++++++++++++++++++++++++++
1 file changed, 279 insertions(+)
create mode 100644 en/content-en/A-string-format.md
diff --git a/en/content-en/A-string-format.md b/en/content-en/A-string-format.md
new file mode 100644
index 000000000..3c75e69d4
--- /dev/null
+++ b/en/content-en/A-string-format.md
@@ -0,0 +1,279 @@
+# A.38. Layout Format String
+
+Pada pembahasan-pembahasan sebelumnya kita telah banyak menggunakan layout format string seperti `%s`, `%d`, `%.2f`, dan lainnya; untuk keperluan menampilkan output ke layar ataupun untuk memformat string.
+
+Layout format string digunakan dalam konversi data ke bentuk string. Contohnya seperti `%.3f` yang untuk konversi nilai `double` ke `string` dengan 3 digit desimal.
+
+## A.38.1. Persiapan
+
+Pada chapter ini kita akan mempelajari satu per satu layout format string yang tersedia di Golang. Kode berikut adalah sample data yang akan kita digunakan sebagai contoh.
+
+```go
+type student struct {
+ name string
+ height float64
+ age int32
+ isGraduated bool
+ hobbies []string
+}
+
+var data = student{
+ name: "wick",
+ height: 182.5,
+ age: 26,
+ isGraduated: false,
+ hobbies: []string{"eating", "sleeping"},
+}
+```
+
+## A.38.2. Layout Format `%b`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 2 (biner).
+
+```go
+fmt.Printf("%b\n", data.age)
+// 11010
+```
+
+## A.38.3. Layout Format `%c`
+
+Digunakan untuk memformat data numerik yang merupakan kode unicode, menjadi bentuk string karakter unicode-nya.
+
+```go
+fmt.Printf("%c\n", 1400)
+// ո
+
+fmt.Printf("%c\n", 1235)
+// ӓ
+```
+
+## A.38.4. Layout Format `%d`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 10 (basis bilangan yang kita gunakan).
+
+```go
+fmt.Printf("%d\n", data.age)
+// 26
+```
+
+## A.38.5. Layout Format `%e` atau `%E`
+
+Digunakan untuk memformat data numerik desimal ke dalam bentuk notasi numerik standar [Scientific notation](https://en.wikipedia.org/wiki/Scientific_notation).
+
+```go
+fmt.Printf("%e\n", data.height)
+// 1.825000e+02
+
+fmt.Printf("%E\n", data.height)
+// 1.825000E+02
+```
+
+**1.825000E+02** maksudnya adalah **1.825 x 10^2**, dan hasil operasi tersebut adalah sesuai dengan data asli = **182.5**.
+
+Perbedaan antara `%e` dan `%E` hanya pada bagian huruf besar kecil karakter `e` pada hasil.
+
+## A.38.6. Layout Format `%f` atau `%F`
+
+`%F` adalah alias dari `%f`. Keduanya memiliki fungsi yang sama.
+
+Berfungsi untuk memformat data numerik desimal, dengan lebar desimal bisa ditentukan. Secara default lebar digit desimal adalah 6 digit.
+
+```go
+fmt.Printf("%f\n", data.height)
+// 182.500000
+
+fmt.Printf("%.9f\n", data.height)
+// 182.500000000
+
+fmt.Printf("%.2f\n", data.height)
+// 182.50
+
+fmt.Printf("%.f\n", data.height)
+// 182
+```
+
+## A.38.7. Layout Format `%g` atau `%G`
+
+`%G` adalah alias dari `%g`. Keduanya memiliki fungsi yang sama.
+
+Berfungsi untuk memformat data numerik desimal, dengan lebar desimal bisa ditentukan. Lebar kapasitasnya sangat besar, pas digunakan untuk data yang jumlah digit desimalnya cukup banyak.
+
+Bisa dilihat pada kode berikut perbandingan antara `%e`, `%f`, dan `%g`.
+
+```go
+fmt.Printf("%e\n", 0.123123123123)
+// 1.231231e-01
+
+fmt.Printf("%f\n", 0.123123123123)
+// 0.123123
+
+fmt.Printf("%g\n", 0.123123123123)
+// 0.123123123123
+```
+
+Perbedaan lainnya adalah pada `%g`, lebar digit desimal adalah sesuai dengan datanya, tidak bisa dicustom seperti pada `%f`.
+
+```go
+fmt.Printf("%g\n", 0.12)
+// 0.12
+
+fmt.Printf("%.5g\n", 0.12)
+// 0.12
+```
+
+## A.38.8. Layout Format `%o`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 8 (oktal).
+
+```go
+fmt.Printf("%o\n", data.age)
+// 32
+```
+
+## A.38.9. Layout Format `%p`
+
+Digunakan untuk memformat data pointer, mengembalikan alamat pointer referensi variabel-nya.
+
+Alamat pointer dituliskan dalam bentuk numerik berbasis 16 dengan prefix `0x`.
+
+```go
+fmt.Printf("%p\n", &data.name)
+// 0x2081be0c0
+```
+
+## A.38.10. Layout Format `%q`
+
+Digunakan untuk **escape** string. Meskipun string yang dipakai menggunakan literal \ akan tetap di-escape.
+
+```go
+fmt.Printf("%q\n", `" name \ height "`)
+// "\" name \\ height \""
+```
+
+## A.38.11. Layout Format `%s`
+
+Digunakan untuk memformat data string.
+
+```go
+fmt.Printf("%s\n", data.name)
+// wick
+```
+
+## A.38.12. Layout Format `%t`
+
+Digunakan untuk memformat data boolean, menampilkan nilai `bool`-nya.
+
+```go
+fmt.Printf("%t\n", data.isGraduated)
+// false
+```
+
+## A.38.13. Layout Format `%T`
+
+Berfungsi untuk mengambil tipe variabel yang akan diformat.
+
+```go
+fmt.Printf("%T\n", data.name)
+// string
+
+fmt.Printf("%T\n", data.height)
+// float64
+
+fmt.Printf("%T\n", data.age)
+// int32
+
+fmt.Printf("%T\n", data.isGraduated)
+// bool
+
+fmt.Printf("%T\n", data.hobbies)
+// []string
+```
+
+## A.38.14. Layout Format `%v`
+
+Digunakan untuk memformat data apa saja (termasuk data bertipe `interface{}`). Hasil kembaliannya adalah string nilai data aslinya.
+
+Jika data adalah objek cetakan `struct`, maka akan ditampilkan semua secara property berurutan.
+
+```go
+fmt.Printf("%v\n", data)
+// {wick 182.5 26 false [eating sleeping]}
+```
+
+## A.38.15. Layout Format `%+v`
+
+Digunakan untuk memformat struct, mengembalikan nama tiap property dan nilainya berurutan sesuai dengan struktur struct.
+
+```go
+fmt.Printf("%+v\n", data)
+// {name:wick height:182.5 age:26 isGraduated:false hobbies:[eating sleeping]}
+```
+
+## A.38.16. Layout Format `%#v`
+
+Digunakan untuk memformat struct, mengembalikan nama dan nilai tiap property sesuai dengan struktur struct dan juga bagaimana objek tersebut dideklarasikan.
+
+```go
+fmt.Printf("%#v\n", data)
+// main.student{name:"wick", height:182.5, age:26, isGraduated:false, hobbies:[]string{"eating", "sleeping"}}
+```
+
+Ketika menampilkan objek yang deklarasinya adalah menggunakan teknik *anonymous struct*, maka akan muncul juga struktur anonymous struct nya.
+
+```go
+var data = struct {
+ name string
+ height float64
+}{
+ name: "wick",
+ height: 182.5,
+}
+
+fmt.Printf("%#v\n", data)
+// struct { name string; height float64 }{name:"wick", height:182.5}
+```
+
+Format ini juga bisa digunakan untuk menampilkan tipe data lain, dan akan dimunculkan strukturnya juga.
+
+## A.38.17. Layout Format `%x` atau `%X`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 16 (heksadesimal).
+
+```go
+fmt.Printf("%x\n", data.age)
+// 1a
+```
+
+Jika digunakan pada tipe data string, maka akan mengembalikan kode heksadesimal tiap karakter.
+
+```go
+var d = data.name
+
+fmt.Printf("%x%x%x%x\n", d[0], d[1], d[2], d[3])
+// 7769636b
+
+fmt.Printf("%x\n", d)
+// 7769636b
+```
+
+`%x` dan `%X` memiliki fungsi yang sama. Perbedaannya adalah `%X` akan mengembalikan string dalam bentuk *uppercase* atau huruf kapital.
+
+## A.38.18. Layout Format `%%`
+
+Cara untuk menulis karakter `%` pada string format.
+
+```go
+fmt.Printf("%%\n")
+// %
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From bacc5ae236bfeaaec3d6800844ef6b3b37413f81 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:49 +0700
Subject: [PATCH 054/522] New translations a-strings.md (English)
---
en/content-en/A-strings.md | 167 +++++++++++++++++++++++++++++++++++++
1 file changed, 167 insertions(+)
create mode 100644 en/content-en/A-strings.md
diff --git a/en/content-en/A-strings.md b/en/content-en/A-strings.md
new file mode 100644
index 000000000..5eb0e25a9
--- /dev/null
+++ b/en/content-en/A-strings.md
@@ -0,0 +1,167 @@
+# A.44. Fungsi String
+
+Go menyediakan package `strings`, isinya banyak fungsi untuk keperluan pengolahan data string. Chapter ini berisi pembahasan mengenai beberapa fungsi yang ada di dalam package tersebut.
+
+## A.44.1. Fungsi `strings.Contains()`
+
+Dipakai untuk deteksi apakah string (parameter kedua) merupakan bagian dari string lain (parameter pertama). Nilai kembaliannya berupa `bool`.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func main() {
+ var isExists = strings.Contains("john wick", "wick")
+ fmt.Println(isExists)
+}
+```
+
+Variabel `isExists` akan bernilai `true`, karena string `"wick"` merupakan bagian dari `"john wick"`.
+
+## A.44.2. Fungsi `strings.HasPrefix()`
+
+Digunakan untuk deteksi apakah sebuah string (parameter pertama) diawali string tertentu (parameter kedua).
+
+```go
+var isPrefix1 = strings.HasPrefix("john wick", "jo")
+fmt.Println(isPrefix1) // true
+
+var isPrefix2 = strings.HasPrefix("john wick", "wi")
+fmt.Println(isPrefix2) // false
+```
+
+## A.44.3. Fungsi `strings.HasSuffix()`
+
+Digunakan untuk deteksi apakah sebuah string (parameter pertama) diakhiri string tertentu (parameter kedua).
+
+```go
+var isSuffix1 = strings.HasSuffix("john wick", "ic")
+fmt.Println(isSuffix1) // false
+
+var isSuffix2 = strings.HasSuffix("john wick", "ck")
+fmt.Println(isSuffix2) // true
+```
+
+## A.44.4. Fungsi `strings.Count()`
+
+Memiliki kegunaan untuk menghitung jumlah karakter tertentu (parameter kedua) dari sebuah string (parameter pertama). Nilai kembalian fungsi ini adalah jumlah karakternya.
+
+```go
+var howMany = strings.Count("ethan hunt", "t")
+fmt.Println(howMany) // 2
+```
+
+Nilai yang dikembalikan `2`, karena pada string `"ethan hunt"` terdapat dua buah karakter `"t"`.
+
+## A.44.5. Fungsi `strings.Index()`
+
+Digunakan untuk mencari posisi indeks sebuah string (parameter kedua) dalam string (parameter pertama).
+
+```go
+var index1 = strings.Index("ethan hunt", "ha")
+fmt.Println(index1) // 2
+```
+
+String `"ha"` berada pada posisi ke `2` dalam string `"ethan hunt"` (indeks dimulai dari 0). Jika diketemukan dua substring, maka yang diambil adalah yang pertama, contoh:
+
+```go
+var index2 = strings.Index("ethan hunt", "n")
+fmt.Println(index2) // 4
+```
+
+String `"n"` berada pada indeks `4` dan `8`. Yang dikembalikan adalah yang paling kiri (paling kecil), yaitu `4`.
+
+## A.44.6. Fungsi `strings.Replace()`
+
+Fungsi ini digunakan untuk replace atau mengganti bagian dari string dengan string tertentu. Jumlah substring yang di-replace bisa ditentukan, apakah hanya 1 string pertama, 2 string, atau seluruhnya.
+
+```go
+var text = "banana"
+var find = "a"
+var replaceWith = "o"
+
+var newText1 = strings.Replace(text, find, replaceWith, 1)
+fmt.Println(newText1) // "bonana"
+
+var newText2 = strings.Replace(text, find, replaceWith, 2)
+fmt.Println(newText2) // "bonona"
+
+var newText3 = strings.Replace(text, find, replaceWith, -1)
+fmt.Println(newText3) // "bonono"
+```
+
+Penjelasan:
+
+ 1. Pada contoh di atas, substring `"a"` pada string `"banana"` akan di-replace dengan string `"o"`.
+ 2. Pada `newText1`, hanya 1 huruf `o` saja yang tereplace karena maksimal substring yang ingin di-replace ditentukan 1.
+ 3. Angka `-1` akan menjadikan proses replace berlaku pada semua substring. Contoh bisa dilihat pada `newText3`.
+
+## A.44.7. Fungsi `strings.Repeat()`
+
+Digunakan untuk mengulang string (parameter pertama) sebanyak data yang ditentukan (parameter kedua).
+
+```go
+var str = strings.Repeat("na", 4)
+fmt.Println(str) // "nananana"
+```
+
+Pada contoh di atas, string `"na"` diulang sebanyak 4 kali. Hasilnya adalah: `"nananana"`
+
+## A.44.8. Fungsi `strings.Split()`
+
+Digunakan untuk memisah string (parameter pertama) dengan tanda pemisah bisa ditentukan sendiri (parameter kedua). Hasilnya berupa slice string.
+
+```go
+var string1 = strings.Split("the dark knight", " ")
+fmt.Println(string1) // output: ["the", "dark", "knight"]
+
+var string2 = strings.Split("batman", "")
+fmt.Println(string2) // output: ["b", "a", "t", "m", "a", "n"]
+```
+
+String `"the dark knight"` dipisah oleh karakter spasi `" "`, hasilnya kemudian ditampung oleh `string1`.
+
+Untuk memisah string menjadi slice tiap 1 string, gunakan pemisah string kosong `""`. Bisa dilihat contohnya pada variabel `string2`.
+
+## A.44.9. Fungsi `strings.Join()`
+
+Memiliki kegunaan berkebalikan dengan `strings.Split()`. Digunakan untuk menggabungkan slice string (parameter pertama) menjadi sebuah string dengan pemisah tertentu (parameter kedua.
+
+```go
+var data = []string{"banana", "papaya", "tomato"}
+var str = strings.Join(data, "-")
+fmt.Println(str) // "banana-papaya-tomato"
+```
+
+Slice `data` digabungkan menjadi satu dengan pemisah tanda *dash* (`-`).
+
+## A.44.10. Fungsi `strings.ToLower()`
+
+Mengubah huruf-huruf string menjadi huruf kecil.
+
+```go
+var str = strings.ToLower("aLAy")
+fmt.Println(str) // "alay"
+```
+
+## A.44.11. Fungsi `strings.ToUpper()`
+
+Mengubah huruf-huruf string menjadi huruf besar.
+
+```go
+var str = strings.ToUpper("eat!")
+fmt.Println(str) // "EAT!"
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 9607349ce357349db9449ea9af7e8ac945dab88c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:50 +0700
Subject: [PATCH 055/522] New translations a-struct.md (English)
---
en/content-en/A-struct.md | 419 ++++++++++++++++++++++++++++++++++++++
1 file changed, 419 insertions(+)
create mode 100644 en/content-en/A-struct.md
diff --git a/en/content-en/A-struct.md b/en/content-en/A-struct.md
new file mode 100644
index 000000000..06bc64194
--- /dev/null
+++ b/en/content-en/A-struct.md
@@ -0,0 +1,419 @@
+# A.24. Struct
+
+Go tidak memiliki class yang ada di bahasa-bahasa strict OOP lain. Tapi Go memiliki tipe data struktur yang disebut dengan Struct.
+
+Struct adalah kumpulan definisi variabel (atau property) dan atau fungsi (atau method), yang dibungkus sebagai tipe data baru dengan nama tertentu. Property dalam struct, tipe datanya bisa bervariasi. Mirip seperti `map`, hanya saja key-nya sudah didefinisikan di awal, dan tipe data tiap itemnya bisa berbeda.
+
+Dari sebuah struct, kita bisa buat variabel baru, yang memiliki atribut sesuai skema struct tersebut. Kita sepakati dalam buku ini, variabel tersebut dipanggil dengan istilah **object** atau **object struct**.
+
+> Konsep struct di golang mirip dengan konsep **class** pada OOP, meski sebenarnya berbeda. Di sini penulis menggunakan konsep OOP sebagai analogi, dengan tujuan untuk mempermudah dalam mencerna isi chapter ini.
+
+Dengan memanfaatkan struct, grouping data akan lebih mudah, selain itu rapi dan gampang untuk di-maintain.
+
+## A.24.1. Deklarasi Struct
+
+Keyword `type` digunakan untuk deklarasi struct. Di bawah ini merupakan contoh cara penggunaannya.
+
+```go
+type student struct {
+ name string
+ grade int
+}
+```
+
+Struct `student` dideklarasikan memiliki 2 property, yaitu `name` dan `grade`. Objek yang dibuat dengan struct ini nantinya memiliki skema atau struktur yang sama.
+
+## A.24.2. Penerapan Struct
+
+Struct `student` yang sudah disiapkan di atas akan kita manfaatkan untuk membuat variabel objek. Property variabel tersebut di-isi kemudian ditampilkan.
+
+```go
+func main() {
+ var s1 student
+ s1.name = "john wick"
+ s1.grade = 2
+
+ fmt.Println("name :", s1.name)
+ fmt.Println("grade :", s1.grade)
+}
+```
+
+Cara membuat variabel objek sama seperti pembuatan variabel biasa. Tinggal tulis saja nama variabel diikuti nama struct, contoh: `var s1 student`.
+
+Semua property variabel objek pada awalnya memiliki zero value sesuai tipe datanya.
+
+Property variabel objek bisa diakses nilainya menggunakan notasi titik, contohnya `s1.name`. Nilai property-nya juga bisa diubah, contohnya `s1.grade = 2`.
+
+
+
+## A.24.3. Inisialisasi Object Struct
+
+Cara inisialisasi variabel objek adalah dengan menambahkan kurung kurawal setelah nama struct. Nilai masing-masing property bisa diisi pada saat inisialisasi.
+
+Pada contoh berikut, terdapat 3 buah variabel objek yang dideklarasikan dengan cara berbeda.
+
+```go
+var s1 = student{}
+s1.name = "wick"
+s1.grade = 2
+
+var s2 = student{"ethan", 2}
+
+var s3 = student{name: "jason"}
+
+fmt.Println("student 1 :", s1.name)
+fmt.Println("student 2 :", s2.name)
+fmt.Println("student 3 :", s3.name)
+```
+
+Pada kode di atas, variabel `s1` menampung objek cetakan `student`. Variabel tersebut kemudian di-set nilai property-nya.
+
+Variabel objek `s2` dideklarasikan dengan metode yang sama dengan `s1`, pembedanya di `s2` nilai propertinya di isi langsung ketika deklarasi. Nilai pertama akan menjadi nilai property pertama (yaitu `name`), dan selanjutnya berurutan.
+
+Pada deklarasi `s3`, dilakukan juga pengisian property ketika pencetakan objek. Hanya saja, yang diisi hanya `name` saja. Cara ini cukup efektif jika digunakan untuk membuat objek baru yang nilai property-nya tidak semua harus disiapkan di awal. Keistimewaan lain menggunakan cara ini adalah penentuan nilai property bisa dilakukan dengan tidak berurutan. Contohnya:
+
+```go
+var s4 = student{name: "wayne", grade: 2}
+var s5 = student{grade: 2, name: "bruce"}
+```
+
+## A.24.4. Variabel Objek Pointer
+
+Objek yang dibuat dari tipe struct bisa diambil nilai pointer-nya, dan bisa disimpan pada variabel objek yang bertipe struct pointer. Contoh penerapannya:
+
+```go
+var s1 = student{name: "wick", grade: 2}
+
+var s2 *student = &s1
+fmt.Println("student 1, name :", s1.name)
+fmt.Println("student 4, name :", s2.name)
+
+s2.name = "ethan"
+fmt.Println("student 1, name :", s1.name)
+fmt.Println("student 4, name :", s2.name)
+```
+
+`s2` adalah variabel pointer hasil cetakan struct `student`. `s2` menampung nilai referensi `s1`, menjadikan setiap perubahan pada property variabel tersebut, akan juga berpengaruh pada variabel objek `s1`.
+
+Meskipun `s2` bukan variabel asli, property nya tetap bisa diakses seperti biasa. Inilah keistimewaan property dalam objek pointer, tanpa perlu di-dereferensi nilai asli property tetap bisa diakses. Pengisian nilai pada property tersebut juga bisa langsung menggunakan nilai asli, contohnya seperti `s2.name = "ethan"`.
+
+
+
+## A.24.5. Embedded Struct
+
+**Embedded** struct adalah mekanisme untuk menempelkan sebuah struct sebagai properti struct lain. Agar lebih mudah dipahami, mari kita bahas kode berikut.
+
+```go
+package main
+
+import "fmt"
+
+type person struct {
+ name string
+ age int
+}
+
+type student struct {
+ grade int
+ person
+}
+
+func main() {
+ var s1 = student{}
+ s1.name = "wick"
+ s1.age = 21
+ s1.grade = 2
+
+ fmt.Println("name :", s1.name)
+ fmt.Println("age :", s1.age)
+ fmt.Println("age :", s1.person.age)
+ fmt.Println("grade :", s1.grade)
+}
+
+```
+
+Pada kode di atas, disiapkan struct `person` dengan properti yang tersedia adalah `name` dan `age`. Disiapkan juga struct `student` dengan property `grade`. Struct `person` di-embed ke dalam struct `student`. Caranya cukup mudah, yaitu dengan menuliskan nama struct yang ingin di-embed ke dalam body `struct` target.
+
+Embedded struct adalah **mutable**, nilai property-nya nya bisa diubah.
+
+Khusus untuk properti yang bukan properti asli (properti turunan dari struct lain), bisa diakses dengan cara mengakses struct *parent*-nya terlebih dahulu, contohnya `s1.person.age`. Nilai yang dikembalikan memiliki referensi yang sama dengan `s1.age`.
+
+## A.24.6. Embedded Struct Dengan Nama Property Yang Sama
+
+Jika salah satu nama properti sebuah struct memiliki kesamaan dengan properti milik struct lain yang di-embed, maka pengaksesan property-nya harus dilakukan secara eksplisit atau jelas. Contoh bisa dilihat di kode berikut.
+
+```go
+package main
+
+import "fmt"
+
+type person struct {
+ name string
+ age int
+}
+
+type student struct {
+ person
+ age int
+ grade int
+}
+
+func main() {
+ var s1 = student{}
+ s1.name = "wick"
+ s1.age = 21 // age of student
+ s1.person.age = 22 // age of person
+
+ fmt.Println(s1.name)
+ fmt.Println(s1.age)
+ fmt.Println(s1.person.age)
+}
+```
+
+Struct `person` di-embed ke dalam struct `student`, dan kedua struct tersebut kebetulan salah satu nama property-nya ada yang sama, yaitu `age`. Cara mengakses property `age` milik struct `person` lewat objek struct `student`, adalah dengan menuliskan nama struct yang di-embed kemudian nama property-nya, contohnya: `s1.person.age = 22`.
+
+## A.24.7. Pengisian Nilai Sub-Struct
+
+Pengisian nilai property sub-struct bisa dilakukan dengan langsung memasukkan variabel objek yang tercetak dari struct yang sama.
+
+```go
+var p1 = person{name: "wick", age: 21}
+var s1 = student{person: p1, grade: 2}
+
+fmt.Println("name :", s1.name)
+fmt.Println("age :", s1.age)
+fmt.Println("grade :", s1.grade)
+```
+
+Pada deklarasi `s1`, property `person` diisi variabel objek `p1`.
+
+## A.24.8. Anonymous Struct
+
+Anonymous struct adalah struct yang tidak dideklarasikan di awal sebagai tipe data baru, melainkan langsung ketika pembuatan objek. Teknik ini cukup efisien untuk pembuatan variabel objek yang struct-nya hanya dipakai sekali.
+
+```go
+package main
+
+import "fmt"
+
+type person struct {
+ name string
+ age int
+}
+
+func main() {
+ var s1 = struct {
+ person
+ grade int
+ }{}
+ s1.person = person{"wick", 21}
+ s1.grade = 2
+
+ fmt.Println("name :", s1.person.name)
+ fmt.Println("age :", s1.person.age)
+ fmt.Println("grade :", s1.grade)
+}
+```
+
+Pada kode di atas, variabel `s1` langsung diisi objek anonymous struct yang memiliki property `grade`, dan property `person` yang merupakan embedded struct.
+
+Salah satu aturan yang perlu diingat dalam pembuatan anonymous struct adalah, deklarasi harus diikuti dengan inisialisasi. Bisa dilihat pada `s1` setelah deklarasi struktur struct, terdapat kurung kurawal untuk inisialisasi objek. Meskipun nilai tidak diisikan di awal, kurung kurawal tetap harus ditulis.
+
+```go
+// anonymous struct tanpa pengisian property
+var s1 = struct {
+ person
+ grade int
+}{}
+
+// anonymous struct dengan pengisian property
+var s2 = struct {
+ person
+ grade int
+}{
+ person: person{"wick", 21},
+ grade: 2,
+}
+```
+
+## A.24.9. Kombinasi Slice & Struct
+
+Slice dan `struct` bisa dikombinasikan seperti pada slice dan `map`, caranya penggunaannya-pun mirip, cukup tambahkan tanda `[]` sebelum tipe data pada saat deklarasi.
+
+```go
+type person struct {
+ name string
+ age int
+}
+
+var allStudents = []person{
+ {name: "Wick", age: 23},
+ {name: "Ethan", age: 23},
+ {name: "Bourne", age: 22},
+}
+
+for _, student := range allStudents {
+ fmt.Println(student.name, "age is", student.age)
+}
+```
+
+## A.24.10. Inisialisasi Slice Anonymous Struct
+
+Anonymous struct bisa dijadikan sebagai tipe sebuah slice. Dan nilai awalnya juga bisa diinisialisasi langsung pada saat deklarasi. Berikut adalah contohnya:
+
+```go
+var allStudents = []struct {
+ person
+ grade int
+}{
+ {person: person{"wick", 21}, grade: 2},
+ {person: person{"ethan", 22}, grade: 3},
+ {person: person{"bond", 21}, grade: 3},
+}
+
+for _, student := range allStudents {
+ fmt.Println(student)
+}
+```
+
+## A.24.11. Deklarasi Anonymous Struct Menggunakan Keyword **var**
+
+Cara lain untuk deklarasi anonymous struct adalah dengan menggunakan keyword `var`.
+
+```go
+var student struct {
+ person
+ grade int
+}
+
+student.person = person{"wick", 21}
+student.grade = 2
+```
+
+Statement `type student struct` adalah contoh cara deklarasi struct. Maknanya akan berbeda ketika keyword `type` diganti `var`, seperti pada contoh di atas `var student struct`, yang artinya dicetak sebuah objek dari anonymous struct kemudian disimpan pada variabel bernama `student`.
+
+Deklarasi anonymous struct menggunakan metode ini juga bisa dilakukan beserta inisialisasi-nya.
+
+```go
+// hanya deklarasi
+var student struct {
+ grade int
+}
+
+// deklarasi sekaligus inisialisasi
+var student = struct {
+ grade int
+} {
+ 12,
+}
+```
+
+## A.24.12. Nested struct
+
+Nested struct adalah anonymous struct yang di-embed ke sebuah struct. Deklarasinya langsung di dalam struct peng-embed. Contoh:
+
+```go
+type student struct {
+ person struct {
+ name string
+ age int
+ }
+ grade int
+ hobbies []string
+}
+```
+
+Teknik ini biasa digunakan ketika decoding data **json** yang struktur datanya cukup kompleks dengan proses decode hanya sekali.
+
+## A.24.13. Deklarasi Dan Inisialisasi Struct Secara Horizontal
+
+Deklarasi struct bisa dituliskan secara horizontal, caranya bisa dilihat pada kode berikut:
+
+```go
+type person struct { name string; age int; hobbies []string }
+```
+
+Tanda semi-colon (`;`) digunakan sebagai pembatas deklarasi poperty yang dituliskan secara horizontal. Inisialisasi nilai juga bisa dituliskan dengan metode ini. Contohnya:
+
+```go
+var p1 = struct { name string; age int } { age: 22, name: "wick" }
+var p2 = struct { name string; age int } { "ethan", 23 }
+```
+
+Bagi pengguna editor Sublime yang terinstal plugin GoSublime di dalamnya, cara ini tidak akan bisa dilakukan, karena setiap kali file di-save, kode program dirapikan. Jadi untuk mengetesnya bisa dengan menggunakan editor lain.
+
+## A.24.14. Tag property dalam struct
+
+Tag merupakan informasi opsional yang bisa ditambahkan pada masing-masing property struct.
+
+```go
+type person struct {
+ name string `tag1`
+ age int `tag2`
+}
+```
+
+Tag biasa dimanfaatkan untuk keperluan encode/decode data json. Informasi tag juga bisa diakses lewat reflect. Nantinya akan ada pembahasan yang lebih detail mengenai pemanfaatan tag dalam struct, terutama ketika sudah masuk chapter JSON.
+
+## A.24.15. Type Alias
+
+Sebuah tipe data, seperti struct, bisa dibuatkan alias baru, caranya dengan `type NamaAlias = TargetStruct`. Contoh:
+
+```go
+type Person struct {
+ name string
+ age int
+}
+type People = Person
+
+var p1 = Person{"wick", 21}
+fmt.Println(p1)
+
+var p2 = People{"wick", 21}
+fmt.Println(p2)
+```
+
+Pada kode di atas, sebuah alias bernama `People` dibuat untuk struct `Person`.
+
+Casting dari objek (yang dicetak lewat struct tertentu) ke tipe yang merupakan alias dari struct pencetak, hasilnya selalu valid. Berlaku juga sebaliknya.
+
+```go
+people := People{"wick", 21}
+fmt.Println(Person(people))
+
+person := Person{"wick", 21}
+fmt.Println(People(person))
+```
+
+Pembuatan struct baru juga bisa dilakukan lewat teknik type alias. Silakan perhatikan kode berikut.
+
+```go
+type People1 struct {
+ name string
+ age int
+}
+type People2 = struct {
+ name string
+ age int
+}
+```
+
+Struct `People1` dideklarasikan. Struct alias `People2` juga dideklarasikan, struct ini merupakan alias dari anonymous struct. Penggunaan teknik type alias untuk anonymous struct menghasilkan output yang ekuivalen dengan pendeklarasian struct.
+
+Perlu diketahui juga, dan di atas sudah sempat disinggung, bahwa teknik type alias ini tidak dirancang hanya untuk pembuatan alias pada tipe struct saja, semua jenis tipe data bisa dibuatkan alias. Di contoh berikut, dipersiapkan tipe `Number` yang merupakan alias dari tipe data `int`.
+
+```go
+type Number = int
+var num Number = 12
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From a36b61380f24b372bb2fbf6a2965996a1cad3b3d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:51 +0700
Subject: [PATCH 056/522] New translations a-time-duration.md (English)
---
en/content-en/A-time-duration.md | 131 +++++++++++++++++++++++++++++++
1 file changed, 131 insertions(+)
create mode 100644 en/content-en/A-time-duration.md
diff --git a/en/content-en/A-time-duration.md b/en/content-en/A-time-duration.md
new file mode 100644
index 000000000..c74c8fd10
--- /dev/null
+++ b/en/content-en/A-time-duration.md
@@ -0,0 +1,131 @@
+# A.42. Time Duration
+
+Pada chapter ini kita akan belajar tentang tipe waktu durasi yaitu `time.Duration`.
+
+Tipe `time.Duration` ini merepresentasikan durasi, contohnya seperti 1 menit, 2 jam 5 detik, dst. Data dengan tipe ini bisa dihasilkan dari operasi pencarian delta atau selisih dari dua buah objek `time.Time`, atau bisa juga kita buat sendiri.
+
+Tipe durasi ini sangat berguna untuk banyak hal, seperti *benchmarking* durasi ataupun operasi-operasi lainnya yang membutuhkan informasi durasi.
+
+## A.42.1. Praktek
+
+Mari kita bahas sambil praktek. Silakan tulis kode berikut lalu jalankan.
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ start := time.Now()
+
+ time.Sleep(5 * time.Second)
+
+ duration := time.Since(start)
+
+ fmt.Println("time elapsed in seconds:", duration.Seconds())
+ fmt.Println("time elapsed in minutes:", duration.Minutes())
+ fmt.Println("time elapsed in hours:", duration.Hours())
+}
+```
+
+Pada kode di atas, sebuah objek waktu bernama `start` dibuat. Tepat setelah baris tersebut, ada statement `time.Sleep()` yang digunakan untuk menghentikan proses selama X, yang durasinya di-set lewat parameter fungsi tersebut. Bisa dilihat durasi yang dipilih adalah `5 * time.Second`.
+
+Tipe data durasi adalah `time.Duration`, yang sebenarnya tipe ini merupakan tipe buatan baru dari `int64`.
+
+Ada beberapa *predefined* konstanta durasi yang perlu kita ketahui:
+
+- `time.Nanosecond` yang nilainya adalah `1`
+- `time.Microsecond` yang nilainya adalah `1000`, atau `1000` x `time.Nanosecond`
+- `time.Millisecond` yang nilainya adalah `1000000`, atau `1000` x `time.Microsecond`
+- `time.Second` yang nilainya adalah `1000000000`, atau `1000` x `time.Millisecond`
+- `time.Minute` yang nilainya adalah `1000000000000`, atau `1000` x `time.Second`
+- `time.Hour` yang nilainya adalah `1000000000000000`, atau `1000` x `time.Minute`
+
+Dari list di atas bisa dicontohkan bahwa sebuah data dengan tipe `time.Duration` yang nilainya `1`, maka artinya durasi adalah **1 nanosecond**.
+
+Kembali ke pembahasan fungsi `time.Sleep()`, fungsi ini membutuhkan argumen/parameter durasi dalam bentuk `time.Duration`. Misalnya saya tulis `time.Sleep(1)` maka yang terjadi adalah, waktu statement tersebut hanya akan menghentikan proses selama **1 nanosecond** saja. Jika ingin menghentikan selama 1 detik, maka harus ditulis `time.Sleep(1000000000)`. Nah daripada menulis angka sepanjang itu, cukup saja tulis dengan `1 * time.Second`, artinya adalah 1 detik. Cukup mudah bukan.
+
+Di atas kita gunakan `5 * time.Second` sebagai argumen `time.Sleep()`, maka dengan itu proses akan diberhentikan selama 5 detik.
+
+Sekarang jalankan program yang sudah dibuat.
+
+
+
+Bisa dilihat, hasilnya adalah semua statement di bawah `time.Sleep()` dieksekusi setelah 5 detik berlalu. Ini merupakan contoh penggunaan tipe data durasi pada fungsi `time.Sleep()`.
+
+## A.42.2. Kalkulasi Durasi Menggunakan `time.Since()`.
+
+Pada kode di atas, variabel `duration` berisi durasi atau lama waktu antara kapan variabel `start` di-inisialisasi hingga kapan variabel `duration` ini statement-nya dieksekusi.
+
+Cara menghitung durasi bisa menggunakan `time.Since()`. Isi argumen fungsi tersebut dengan variabel bertipe waktu, maka durasi antara waktu pada argument vs ketika statement `time.Since()` akan dihitung.
+
+Pada contoh di atas, karena ada statement `time.Sleep(5 * time.Second)` maka idealnya `time.Since(start)` isinya adalah 5 detik (mungkin lebih sedikit, sekian mili/micro/nano-second, karena eksekusi statement juga butuh waktu).
+
+## A.42.3. Method `time.Duration`
+
+Tipe `time.Duration` memiliki beberapa method yang sangat-sangat berguna untuk keperluan mengambil nilai durasinya dalam unit tertentu. Misalnya, objek durasi tersebut ingin di-ambil nilainya dalam satuan unit detik, maka gunakan `.Seconds()`. Jika ingin dalam bentuk menit, maka gunakan `.Minutes()`, dan lainnya.
+
+Pada contoh di atas, kita mengambil nilai durasi waktu dalam tiga bentuk, yaitu detik, menit, dan jam. Caranya cukup akses saja method-nya, maka kita akan langsung dapat nilainya, tanpa perlu memikirkan operasi aritmatik konversinya. Cukup mudah bukan.
+
+## A.42.4. Kalkulasi Durasi Antara 2 Objek Waktu
+
+Di atas kita sudah membahas cara hitung durasi menggunakan `time.Since()` antara sebuah objek waktu vs kapan statement di-eksekusi. Pada bagian ini, masih mirip, perbedannya adalah hitung durasi dilakukan pada 2 objek waktu.
+
+Silakan perhatikan contoh berikut. Kode berikut esensinya adalah sama dengan kode di atas.
+
+```go
+t1 := time.Now()
+time.Sleep(5 * time.Second)
+t2 := time.Now()
+
+duration := t2.Sub(t1)
+
+fmt.Println("time elapsed in seconds:", duration.Seconds())
+fmt.Println("time elapsed in minutes:", duration.Minutes())
+fmt.Println("time elapsed in hours:", duration.Hours())
+```
+
+Method `.Sub()` milik objek `time.Time` digunakan untuk mencari selisih waktu. Pada contoh di atas, durasi antara waktu `t1` dan waktu `t2` dihitung. Method `.Sub()` ini menghasilkan nilai balik bertipe `time.Duration`.
+
+## A.42.5. Konversi Angka ke `time.Duration`
+
+Kita bisa mengalikan angka literal dengan konstanta `time.Duration` untuk menciptakan variabel/objek bertipe durasi. Contohnya seperti yang sudah kita terapkan sebelumnya, yaitu `5 * time.Second` yang menghasilkan data durasi 5 detik. Contoh lainnya:
+
+```go
+12 * time.Minute // 12 menit
+65 * time.Hour // 65 jam
+150000 * time.Milisecond // 150k milidetik atau 150 detik
+45 * time.Microsecond // 45 microdetik
+233 * time.Nanosecond // 233 nano detik
+```
+
+Sedikit kembali ke pembahasan dasar di awal-awal chapter, operasi aritmatika di golang hanya bisa dilakukan ketika data adalah 1 tipe. Selebihnya harus ada casting atau konversi tipe data agar bisa dioperasikan.
+
+Tipe `time.Duration` diciptakan menggunakan tipe `ìnt64`. Jadi jika ingin mengalikan `time.Duration` dengan suatu angka, maka pastikan tipe-nya juga sama yaitu `time.Duration`. Jika angka tersebut tidak ditampung dalam variabel terlebih dahulu (contohnya seperti di atas) maka bisa langsung kalikan saja. Jika ditampung ke variabel terlebih dahulu, maka pastikan tipe variabelnya adalah `time.Duration`. Contoh:
+
+
+```go
+var n time.Duration = 5
+duration := n * time.Second
+```
+
+Atau bisa manfaatkan casting untuk mengkonversi data numerik ke tipe `time.Duration`. Contoh:
+
+```go
+n := 5
+duration := time.Duration(n) * time.Second
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From e80e08669131cc60cd31c1c32f390d5409bd587a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:52 +0700
Subject: [PATCH 057/522] New translations a-time-parsing-format.md (English)
---
en/content-en/A-time-parsing-format.md | 223 +++++++++++++++++++++++++
1 file changed, 223 insertions(+)
create mode 100644 en/content-en/A-time-parsing-format.md
diff --git a/en/content-en/A-time-parsing-format.md b/en/content-en/A-time-parsing-format.md
new file mode 100644
index 000000000..795ada37d
--- /dev/null
+++ b/en/content-en/A-time-parsing-format.md
@@ -0,0 +1,223 @@
+# A.40. Time, Parsing Time, & Format Time
+
+Pada chapter ini kita akan belajar tentang pemanfaatan data bertipe date-time, method-method yang disediakan, dan juga **format** & **parsing** data `string` ke tipe `time.Time` dan sebaliknya.
+
+Go menyediakan package `time` yang berisikan banyak sekali komponen yang bisa digunakan untuk keperluan pemanfaatan date-time. Salah satunya adalah `time.Time`, yang merupakan tipe untuk data tanggal dan waktu di Go.
+
+> Time di sini maksudnya adalah gabungan **date** dan **time**, bukan hanya waktu saja.
+
+## A.40.1. Penggunaan `time.Time`
+
+Tipe `time.Time` merupakan representasi untuk objek date-time. Ada 2 cara yang bisa dipilih untuk membuat data bertipe ini.
+
+ 1. Menjadikan informasi waktu sekarang sebagai objek `time.Time`, menggunakan `time.Now()`.
+ 2. Atau, membuat objek baru bertipe `time.Time` dengan informasi ditentukan sendiri, menggunakan `time.Date()`.
+
+Berikut merupakan contoh penggunannya.
+
+```go
+package main
+
+import "fmt"
+import "time"
+
+func main() {
+ var time1 = time.Now()
+ fmt.Printf("time1 %v\n", time1)
+ // time1 2015-09-01 17:59:31.73600891 +0700 WIB
+
+ var time2 = time.Date(2011, 12, 24, 10, 20, 0, 0, time.UTC)
+ fmt.Printf("time2 %v\n", time2)
+ // time2 2011-12-24 10:20:00 +0000 UTC
+}
+```
+
+Fungsi `time.Now()` mengembalikan objek `time.Time` dengan informasi adalah date-time tepat ketika statement tersebut dijalankan. Bisa dilihat pada saat di-print muncul informasi date-time sesuai dengan tanggal program tersebut dieksekusi.
+
+
+
+Fungsi `time.Date()` digunakan untuk membuat objek `time.Time` baru yang informasi date-time-nya kita tentukan sendiri. Fungsi ini memiliki 8 buah parameter *mandatory* dengan skema bisa dilihat di kode berikut:
+
+```go
+time.Date(tahun, bulan, tanggal, jam, menit, detik, nanodetik, timezone)
+```
+
+Objek cetakan fungsi `time.Now()`, informasi timezone-nya adalah relatif terhadap lokasi kita. Karena kebetulan penulis berlokasi di Jawa Timur, maka akan terdeteksi masuk dalam **GMT+7** atau **WIB**. Berbeda dengan variabel `time2` yang lokasinya sudah kita tentukan secara eksplisit yaitu **UTC**.
+
+Selain menggunakan `time.UTC` untuk penentuan lokasi, tersedia juga `time.Local` yang nilainya adalah relatif terhadap date-time lokal kita.
+
+## A.40.2. Method Milik `time.Time`
+
+Tipe data `time.Time` merupakan struct, memiliki beberapa method yang bisa dipakai.
+
+```go
+var now = time.Now()
+fmt.Println("year:", now.Year(), "month:", now.Month())
+// year: 2015 month: 8
+```
+
+Kode di atas adalah contoh penggunaan beberapa method milik objek bertipe `time.Time`. Method `Year()` mengembalikan informasi tahun, dan `Month()` mengembalikan informasi angka bulan.
+
+Selain kedua method di atas, ada banyak lagi yang bisa dimanfaatkan. Tabel berikut merupakan list method yang berhubungan dengan *date*, *time*, dan *location* yang dimiliki tipe `time.Time`.
+
+| Method | Return Type | Penjelasan |
+|:------------------ |:----------------- |:----------------------------------------------------------------------------------------------------------------------------------- |
+| `now.Year()` | `int` | Tahun |
+| `now.YearDay()` | `int` | Hari ke-? di mulai awal tahun |
+| `now.Month()` | `int` | Bulan |
+| `now.Weekday()` | `string` | Nama hari. Bisa menggunakan `now.Weekday().String()` untuk mengambil bentuk string-nya |
+| `now.ISOWeek()` | (`int`, `int`) | Tahun dan minggu ke-? mulai awal tahun |
+| `now.Day()` | `int` | Tanggal |
+| `now.Hour()` | `int` | Jam |
+| `now.Minute()` | `int` | Menit |
+| `now.Second()` | `int` | Detik |
+| `now.Nanosecond()` | `int` | Nano detik |
+| `now.Local()` | `time.Time` | Date-time dalam timezone lokal |
+| `now.Location()` | `*time.Location` | Mengambil informasi lokasi, apakah *local* atau *utc*. Bisa menggunakan `now.Location().String()` untuk mengambil bentuk string-nya |
+| `now.Zone()` | (`string`, `int`) | Mengembalikan informasi *timezone offset* dalam string dan numerik. Sebagai contoh `WIB, 25200` |
+| `now.IsZero()` | `bool` | Deteksi apakah nilai object `now` adalah `01 Januari tahun 1, 00:00:00 UTC`. Jika iya maka bernilai `true` |
+| `now.UTC()` | `time.Time` | Date-time dalam timezone `UTC` |
+| `now.Unix()` | `int64` | Date-time dalam format *unix time* |
+| `now.UnixNano()` | `int64` | Date-time dalam format *unix time*. Infomasi nano detik juga dimasukkan |
+| `now.String()` | `string` | Date-time dalam string |
+
+## A.40.3. Parsing dari `string` ke `time.Time`
+
+Data `string` bisa dikonversi menjadi `time.Time` dengan memanfaatkan `time.Parse`. Fungsi ini membutuhkan 2 parameter:
+
+ - Parameter ke-1 adalah layout format dari data waktu yang akan diparsing.
+ - Parameter ke-2 adalah data string yang ingin diparsing.
+
+Contoh penerapannya bisa dilihat di kode berikut.
+
+```go
+var layoutFormat, value string
+var date time.Time
+
+layoutFormat = "2006-01-02 15:04:05"
+value = "2015-09-02 08:04:00"
+date, _ = time.Parse(layoutFormat, value)
+fmt.Println(value, "\t->", date.String())
+// 2015-09-02 08:04:00 +0000 UTC
+
+layoutFormat = "02/01/2006 MST"
+value = "02/09/2015 WIB"
+date, _ = time.Parse(layoutFormat, value)
+fmt.Println(value, "\t\t->", date.String())
+// 2015-09-02 00:00:00 +0700 WIB
+```
+
+
+
+Layout format date-time di Go berbeda dibanding bahasa lain. Umumnya layout format yang digunakan adalah seperti `"DD/MM/YYYY"`, di Go tidak.
+
+Go memiliki standar layout format yang cukup unik, contohnya seperti pada kode di atas `"2006-01-02 15:04:05"`. Go menggunakan `2006` untuk parsing tahun, bukan `YYYY`; `01` untuk parsing bulan; `02` untuk parsing hari; dan seterusnya. Detailnya bisa dilihat di tabel berikut.
+
+| Layout Format | Penjelasan | Contoh Data |
+|:------------------ |:---------------------------------------------------------------------------- |:------------------------------- |
+| `2006` | Tahun 4 digit | `2015` |
+| `006` | Tahun 3 digit | `015` |
+| `06` | Tahun 2 digit | `15` |
+| `01` | Bulan 2 digit | `05` |
+| `1` | Bulan 1 digit jika di bawah bulan 10, selainnya 2 digit | `5`, `12` |
+| `January` | Nama bulan dalam bahasa inggris | `September`, `August` |
+| `Jan` | Nama bulan dalam bahasa inggris, 3 huruf | `Sep`, `Aug` |
+| `02` | Tanggal 2 digit | `02` |
+| `2` | Tanggal 1 digit jika di bawah bulan 10, selainnya 2 digit | `8`, `31` |
+| `Monday` | Nama hari dalam bahasa inggris | `Saturday`, `Friday` |
+| `Mon` | Nama hari dalam bahasa inggris, 3 huruf | `Sat`, `Fri` |
+| `15` | Jam dengan format **24 jam** | `18` |
+| `03` | Jam dengan format **12 jam** 2 digit | `05`, `11` |
+| `3` | Jam dengan format **12 jam** 1 digit jika di bawah jam 11, selainnya 2 digit | `5`, `11` |
+| `PM` | AM/PM, biasa digunakan dengan format jam **12 jam** | `PM`, `AM` |
+| `04` | Menit 2 digit | `08` |
+| `4` | Menit 1 digit jika di bawah menit 10, selainnya 2 digit | `8`, `24` |
+| `05` | Detik 2 digit | `06` |
+| `5` | Detik 1 digit jika di bawah detik 10, selainnya 2 digit | `6`, `36` |
+| `999999` | Nano detik | `124006` |
+| `MST` | Lokasi timezone | `UTC`, `WIB`, `EST` |
+| `Z0700` | Offset timezone | `Z`, `+0700`, `-0200` |
+
+## A.40.4. Predefined Layout Format Untuk Keperluan Parsing Time
+
+Go juga menyediakan beberapa predefined layout format umum yang bisa dimanfaatkan. Jadi tidak perlu menuliskan kombinasi komponen-komponen layout format.
+
+Salah satu predefined layout yang bisa digunakan adalah `time.RFC822`, ekuivalen dengan layout format `02 Jan 06 15:04 MST`. Berikut adalah contoh penerapannya.
+
+```go
+var date, _ = time.Parse(time.RFC822, "02 Sep 15 08:00 WIB")
+fmt.Println(date.String())
+// 2015-09-02 08:00:00 +0700 WIB
+```
+
+Ada beberapa layout format lain yang tersedia, silakan lihat tabel berikut.
+
+| Predefined Layout Format | Layout Format |
+|:------------------------ |:----------------------------------- |
+| `time.ANSIC` | Mon Jan _2 15:04:05 2006 |
+| `time.UnixDate` | Mon Jan _2 15:04:05 MST 2006 |
+| `time.RubyDate` | Mon Jan 02 15:04:05 -0700 2006 |
+| `time.RFC822` | 02 Jan 06 15:04 MST |
+| `time.RFC822Z` | 02 Jan 06 15:04 -0700 |
+| `time.RFC850` | Monday, 02-Jan-06 15:04:05 MST |
+| `time.RFC1123` | Mon, 02 Jan 2006 15:04:05 MST |
+| `time.RFC1123Z` | Mon, 02 Jan 2006 15:04:05 -0700 |
+| `time.RFC3339` | 2006-01-02T15:04:05Z07:00 |
+| `time.RFC3339Nano` | 2006-01-02T15:04:05.999999999Z07:00 |
+| `time.Kitchen` | 3:04PM |
+| `time.Stamp` | Jan _2 15:04:05 |
+| `time.StampMilli` | Jan _2 15:04:05.000 |
+| `time.StampMicro` | Jan _2 15:04:05.000000 |
+| `time.StampNano` | Jan _2 15:04:05.000000000 |
+
+## A.40.5. Format dari `time.Time` ke `string`
+
+Setelah sebelumnya kita belajar tentang cara konversi data dengan tipe `string` ke `time.Time`. Kali ini kita akan belajar kebalikannya, konversi `time.Time` ke `string`.
+
+Method `Format()` milik tipe `time.Time` digunakan untuk membentuk output `string` sesuai dengan layout format yang diinginkan. Contoh bisa dilihat pada kode berikut.
+
+```go
+var date, _ = time.Parse(time.RFC822, "02 Sep 15 08:00 WIB")
+
+var dateS1 = date.Format("Monday 02, January 2006 15:04 MST")
+fmt.Println("dateS1", dateS1)
+// Wednesday 02, September 2015 08:00 WIB
+
+var dateS2 = date.Format(time.RFC3339)
+fmt.Println("dateS2", dateS2)
+// 2015-09-02T08:00:00+07:00
+```
+
+Variabel `date` di atas berisikan hasil parsing data dengan format `time.RFC822`. Data tersebut kemudian diformat sebagai string 2 kali dengan layout format berbeda.
+
+
+
+## A.40.6. Handle Error Parsing `time.Time`
+
+Ketika parsing `string` ke `time.Time`, sangat memungkinkan bisa terjadi error karena struktur data yang akan di-parse tidak sesuai layout format yang digunakan. Error tidaknya parsing bisa diketahui lewat nilai kembalian ke-2 fungsi `time.Parse()`. Berikut adalah contoh penerapannya.
+
+```go
+var date, err = time.Parse("06 Jan 15", "02 Sep 15 08:00 WIB")
+
+if err != nil {
+ fmt.Println("error", err.Error())
+ return
+}
+
+fmt.Println(date)
+```
+
+Kode di atas menghasilkan error karena format tidak sesuai dengan skema data yang akan diparsing. Layout format yang seharusnya digunakan adalah `06 Jan 15 03:04 MST`.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 7ca0ded3fdfdad7cc30af5c22fe28f67b093edb0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:53 +0700
Subject: [PATCH 058/522] New translations a-timer-ticker-scheduler.md
(English)
---
en/content-en/A-timer-ticker-scheduler.md | 206 ++++++++++++++++++++++
1 file changed, 206 insertions(+)
create mode 100644 en/content-en/A-timer-ticker-scheduler.md
diff --git a/en/content-en/A-timer-ticker-scheduler.md b/en/content-en/A-timer-ticker-scheduler.md
new file mode 100644
index 000000000..56b6d8985
--- /dev/null
+++ b/en/content-en/A-timer-ticker-scheduler.md
@@ -0,0 +1,206 @@
+# A.41. Timer, Ticker, & Scheduler
+
+Ada beberapa fungsi dalam package `time` yang bisa dimanfaatkan untuk menunda atau mengatur jadwal eksekusi sebuah proses dalam jeda waktu tertentu.
+
+## A.41.1. Fungsi `time.Sleep()`
+
+Fungsi ini digunakan untuk menghentikan program sejenak. `time.Sleep()` bersifat **blocking**, statement di bawahnya tidak akan dieksekusi sampai pemberhentian usai. Contoh sederhana penerapan bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "time"
+
+func main () {
+ fmt.Println("start")
+ time.Sleep(time.Second * 4)
+ fmt.Println("after 4 seconds")
+}
+```
+
+Hasilnya, tulisan `"start"` muncul, lalu 4 detik kemudian tulisan `"after 4 seconds"` muncul.
+
+## A.41.2. Scheduler Menggunakan `time.Sleep()`
+
+Selain untuk blocking proses, fungsi `time.Sleep()` ini bisa dimanfaatkan untuk membuat scheduler sederhana, contohnya seperti berikut, scheduler untuk menampilkan pesan halo setiap 1 detik.
+
+```go
+for true {
+ fmt.Println("Hello !!")
+ time.Sleep(1 * time.Second)
+}
+```
+
+## A.41.3. Fungsi `time.NewTimer()`
+
+Fungsi ini sedikit berbeda dengan `time.Sleep()`. Fungsi `time.NewTimer()` mengembalikan objek bertipe `*time.Timer` yang memiliki property `C` yang bertipe channel.
+
+Cara kerja fungsi ini: setelah jeda waktu yang ditentukan sebuah data akan dikirimkan lewat channel `C`. Penggunaan fungsi ini harus diikuti dengan statement untuk penerimaan data dari channel `C`.
+
+Untuk lebih jelasnya silakan perhatikan kode berikut.
+
+```go
+var timer = time.NewTimer(4 * time.Second)
+fmt.Println("start")
+<-timer.C
+fmt.Println("finish")
+```
+
+Statement `var timer = time.NewTimer(4 * time.Second)` mengindikasikan bahwa nantinya akan ada data yang dikirimkan ke channel `timer.C` setelah 4 detik berlalu. Baris kode `<-timer.C` menandakan penerimaan data dari channel `timer.C`. Karena penerimaan channel sendiri sifatnya adalah blocking, maka statement `fmt.Println("finish")` baru akan dieksekusi setelah **4 detik**.
+
+Hasil program di atas adalah tulisan `"start"` muncul, lalu setelah 4 detik tulisan `"finish"` muncul.
+
+## A.41.4. Fungsi `time.AfterFunc()`
+
+Fungsi `time.AfterFunc()` memiliki 2 parameter. Parameter pertama adalah durasi timer, dan parameter kedua adalah *callback* nya. Callback tersebut akan dieksekusi jika waktu sudah memenuhi durasi timer.
+
+```go
+var ch = make(chan bool)
+
+time.AfterFunc(4*time.Second, func() {
+ fmt.Println("expired")
+ ch <- true
+})
+
+fmt.Println("start")
+<-ch
+fmt.Println("finish")
+```
+
+Hasil dari kode di atas, tulisan `"start"` muncul kemudian setelah 4 detik berlalu, tulisan `"expired"` muncul.
+
+Di dalam callback terdapat proses transfer data lewat channel, menjadikan tulisan `"finish"` akan muncul tepat setelah tulisan `"expired"` muncul.
+
+Beberapa hal yang perlu diketahui dalam menggunakan fungsi ini:
+
+ - Jika tidak ada serah terima data lewat channel, maka eksekusi `time.AfterFunc()` adalah asynchronous (tidak blocking).
+ - Jika ada serah terima data lewat channel, maka fungsi akan tetap berjalan asynchronous hingga baris kode di mana penerimaan data channel dilakukan. Proses blocking nya berada pada baris kode penerimaan channel.
+
+## A.41.5. Fungsi `time.After()`
+
+Kegunaan fungsi ini mirip seperti `time.Sleep()`. Perbedaannya adalah, fungsi `timer.After()` akan mengembalikan data channel, sehingga perlu menggunakan tanda `<-` dalam penerapannya.
+
+```go
+<-time.After(4 * time.Second)
+fmt.Println("expired")
+```
+
+Tulisan `"expired"` akan muncul setelah 4 detik.
+
+## A.41.6. Scheduler Menggunakan Ticker
+
+Selain fungsi-fungsi untuk keperluan timer, Go juga menyediakan fungsi scheduler (yang di sini kita sebut sebagai ticker).
+
+Cara penggunaan ticker cukup mudah, buat objek ticker baru menggunakan `time.NewTicker()` isi argument dengan durasi yang diinginkan. Dari objek tersebut kita bisa akses properti `.C` yang merupakan channel. Setiap durasi yang sudah ditentukan, objek ticker akan mengirimkan informasi date-time via channel tersebut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ done := make(chan bool)
+ ticker := time.NewTicker(time.Second)
+
+ go func() {
+ time.Sleep(10 * time.Second) // wait for 10 seconds
+ done <- true
+ }()
+
+ for {
+ select {
+ case <-done:
+ ticker.Stop()
+ return
+ case t := <-ticker.C:
+ fmt.Println("Hello !!", t)
+ }
+ }
+}
+
+```
+
+Pada contoh di atas bisa dilihat, selain ticker disiapkan juga variabel channel `done`. Variabel ini kita gunakan untuk mengontrol kapan ticker harus di stop.
+
+Cara kerja program di atas: teknik `for` - `select` pada channel digunakan untuk mengecek penerimaan data dari channel `done` dan `ticker.C`. By default, channel `ticker.C` akan menerima kiriman data setiap N duration yang di mana pada kode di atas adalah 1 detik (lihat argumen inisialisasi objek ticker).
+
+Data yang dikirimkan via channel `ticker.C` adalah data date-time kapan event itu terjadi. Pada kode di atas, setiap ada kiriman data via channel tersebut kita tampilkan.
+
+Sebelum blok kode perulangan `for`, bisa kita lihat ada goroutine baru di-dispatch, isinya adalah mengirim data ke channel `done` setelah 10 detik. Data tersebut nantinya akan diterima oleh blok kode `for` - `select`, dan ketika itu terjadi, method `.Stop()` milik objek ticker dipanggil untuk menonaktifkan scheduler pada ticker tersebut.
+
+Jadi, selama 10 detik, di setiap detiknya akan muncul pesan halo.
+
+## A.41.7. Kombinasi Timer & Goroutine
+
+Berikut merupakan contoh penerapan timer dan goroutine. Program di bawah ini adalah program tanya-jawab sederhana. Sebuah pertanyaan muncul dan user harus menginputkan jawaban dalam waktu tidak lebih dari 5 detik. Jika 5 detik berlalu dan belum ada jawaban, maka akan muncul pesan *time out*.
+
+OK langsung saja, mari kita buat programnya, pertama, import package yang diperlukan.
+
+```go
+package main
+
+import "fmt"
+import "os"
+import "time"
+```
+
+Buat fungsi `timer()`, nantinya fungsi ini dieksekusi sebagai goroutine. Di dalam fungsi `timer()` terdapat blok kode jika waktu sudah mencapai `timeout`, maka sebuah data dikirimkan lewat channel `ch`.
+
+```go
+func timer(timeout int, ch chan<- bool) {
+ time.AfterFunc(time.Duration(timeout)*time.Second, func() {
+ ch <- true
+ })
+}
+```
+
+Siapkan juga fungsi `watcher()`. Fungsi ini juga akan dieksekusi sebagai goroutine. Tugasnya cukup sederhana, yaitu menerima data dari channel `ch` (jika ada penerimaan data, berarti sudah masuk waktu timeout), lalu menampilkan pesan bahwa waktu telah habis.
+
+```go
+func watcher(timeout int, ch <-chan bool) {
+ <-ch
+ fmt.Println("\ntime out! no answer more than", timeout, "seconds")
+ os.Exit(0)
+}
+```
+
+Terakhir, buat implementasi di fungsi `main()`.
+
+```go
+func main() {
+ var timeout = 5
+ var ch = make(chan bool)
+
+ go timer(timeout, ch)
+ go watcher(timeout, ch)
+
+ var input string
+ fmt.Print("what is 725/25 ? ")
+ fmt.Scan(&input)
+
+ if input == "29" {
+ fmt.Println("the answer is right!")
+ } else {
+ fmt.Println("the answer is wrong!")
+ }
+}
+```
+
+Ketika user tidak menginputkan apa-apa dalam kurun waktu 5 detik, maka akan muncul pesan timeout, lalu program dihentikan.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 17c6eca6879518ead08ee62d6164681a071f2a75 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:54 +0700
Subject: [PATCH 059/522] New translations a-tipe-data.md (English)
---
en/content-en/A-tipe-data.md | 128 +++++++++++++++++++++++++++++++++++
1 file changed, 128 insertions(+)
create mode 100644 en/content-en/A-tipe-data.md
diff --git a/en/content-en/A-tipe-data.md b/en/content-en/A-tipe-data.md
new file mode 100644
index 000000000..cdb9ff5d0
--- /dev/null
+++ b/en/content-en/A-tipe-data.md
@@ -0,0 +1,128 @@
+# A.10. Tipe Data
+
+Go mengenal beberapa jenis tipe data, di antaranya adalah tipe data numerik (desimal & non-desimal), string, dan boolean.
+
+Pada pembahasan-pembahasan sebelumnya secara tak sadar kita sudah mengaplikasikan beberapa tipe data, seperti `string` dan tipe numerik `int`.
+
+Pada chapter ini akan dijelaskan beberapa macam tipe data standar yang disediakan oleh Go, beserta cara penggunaannya.
+
+## A.10.1. Tipe Data Numerik Non-Desimal
+
+Tipe data numerik non-desimal atau **non floating point** di Go ada beberapa jenis. Secara umum ada 2 tipe data kategori ini yang perlu diketahui.
+
+ - `uint`, tipe data untuk bilangan cacah (bilangan positif).
+ - `int`, tipe data untuk bilangan bulat (bilangan negatif dan positif).
+
+Kedua tipe data di atas kemudian dibagi lagi menjadi beberapa jenis, dengan pembagian berdasarkan lebar cakupan nilainya, detailnya bisa dilihat di tabel berikut.
+
+| Tipe data | Cakupan bilangan |
+|:---------:|:----------------------------------------------------- |
+| `uint8` | 0 ↔ 255 |
+| `uint16` | 0 ↔ 65535 |
+| `uint32` | 0 ↔ 4294967295 |
+| `uint64` | 0 ↔ 18446744073709551615 |
+| `uint` | sama dengan `uint32` atau `uint64` (tergantung nilai) |
+| `byte` | sama dengan `uint8` |
+| `int8` | -128 ↔ 127 |
+| `int16` | -32768 ↔ 32767 |
+| `int32` | -2147483648 ↔ 2147483647 |
+| `int64` | -9223372036854775808 ↔ 9223372036854775807 |
+| `int` | sama dengan `int32` atau `int64` (tergantung nilai) |
+| `rune` | sama dengan `int32` |
+
+Dianjurkan untuk tidak sembarangan dalam menentukan tipe data variabel, sebisa mungkin tipe yang dipilih harus disesuaikan dengan nilainya, karena efeknya adalah ke alokasi memori variabel. Pemilihan tipe data yang tepat akan membuat pemakaian memori lebih optimal, tidak berlebihan.
+
+```go
+var positiveNumber uint8 = 89
+var negativeNumber = -1243423644
+
+fmt.Printf("bilangan positif: %d\n", positiveNumber)
+fmt.Printf("bilangan negatif: %d\n", negativeNumber)
+```
+
+Variabel `positiveNumber` bertipe `uint8` dengan nilai awal `89`. Sedangkan variabel `negativeNumber` dideklarasikan dengan nilai awal `-1243423644`. Compiler secara cerdas akan menentukan tipe data variabel tersebut sebagai `int32` (karena angka tersebut masuk ke cakupan tipe data `int32`).
+
+Template `%d` pada `fmt.Printf()` digunakan untuk memformat data numerik non-desimal.
+
+## A.10.2. Tipe Data Numerik Desimal
+
+Tipe data numerik desimal yang perlu diketahui ada 2, `float32` dan `float64`. Perbedaan kedua tipe data tersebut berada di lebar cakupan nilai desimal yang bisa ditampung. Untuk lebih jelasnya bisa merujuk ke spesifikasi [IEEE-754 32-bit floating-point numbers](http://www.h-schmidt.net/FloatConverter/IEEE754.html).
+
+```go
+var decimalNumber = 2.62
+
+fmt.Printf("bilangan desimal: %f\n", decimalNumber)
+fmt.Printf("bilangan desimal: %.3f\n", decimalNumber)
+```
+
+Pada kode di atas, variabel `decimalNumber` akan memiliki tipe data `float32`, karena nilainya berada di cakupan tipe data tersebut.
+
+
+
+Template `%f` digunakan untuk memformat data numerik desimal menjadi string. Digit desimal yang akan dihasilkan adalah **6 digit**. Pada contoh di atas, hasil format variabel `decimalNumber` adalah `2.620000`. Jumlah digit yang muncul bisa dikontrol menggunakan `%.nf`, tinggal ganti `n` dengan angka yang diinginkan. Contoh: `%.3f` maka akan menghasilkan 3 digit desimal, `%.10f` maka akan menghasilkan 10 digit desimal.
+
+## A.10.3. Tipe Data `bool` (Boolean)
+
+Tipe data `bool` berisikan hanya 2 variansi nilai, `true` dan `false`. Tipe data ini biasa dimanfaatkan dalam seleksi kondisi dan perulangan (yang nantinya akan kita bahas pada [A.13. Seleksi Kondisi](/A-seleksi-kondisi.html) dan [A.14. Perulangan](/A-perulangan.html)).
+
+```go
+var exist bool = true
+fmt.Printf("exist? %t \n", exist)
+```
+
+Gunakan `%t` untuk memformat data `bool` menggunakan fungsi `fmt.Printf()`.
+
+## A.10.4. Tipe Data `string`
+
+Ciri khas dari tipe data string adalah nilainya di apit oleh tanda *quote* atau petik dua (`"`). Contoh penerapannya:
+
+```go
+var message string = "Halo"
+fmt.Printf("message: %s \n", message)
+```
+Selain menggunakan tanda quote, deklarasi string juga bisa dengan tanda *grave accent/backticks* (`), tanda ini terletak di sebelah kiri tombol 1. Keistimewaan string yang dideklarasikan menggunakan backtics adalah membuat semua karakter di dalamnya **tidak di escape**, termasuk`\n`, tanda petik dua dan tanda petik satu, baris baru, dan lainnya. Semua akan terdeteksi sebagai string.
+
+```go
+var message = `Nama saya "John Wick".
+Salam kenal.
+Mari belajar "Golang".`
+
+fmt.Println(message)
+```
+
+Ketika dijalankan, output akan muncul sama persisi sesuai nilai variabel `message` di atas. Tanda petik dua akan muncul, baris baru juga muncul, sama persis.
+
+
+
+## A.10.5. Nilai `nil` & Zero Value
+
+`nil` bukan merupakan tipe data, melainkan sebuah nilai. Variabel yang isi nilainya `nil` berarti memiliki nilai kosong.
+
+Semua tipe data yang sudah dibahas di atas memiliki zero value (nilai default tipe data). Artinya meskipun variabel dideklarasikan dengan tanpa nilai awal, tetap akan ada nilai default-nya.
+
+ - Zero value dari `string` adalah `""` (string kosong).
+ - Zero value dari `bool` adalah `false`.
+ - Zero value dari tipe numerik non-desimal adalah `0`.
+ - Zero value dari tipe numerik desimal adalah `0.0`.
+
+Zero value berbeda dengan `nil`. `Nil` adalah nilai kosong, benar-benar kosong. `nil` tidak bisa digunakan pada tipe data yang sudah dibahas di atas. Ada beberapa tipe data yang bisa di-set nilainya dengan `nil`, di antaranya:
+
+- pointer
+- tipe data fungsi
+- slice
+- `map`
+- `channel`
+- interface kosong atau `any` (yang merupakan alias dari `interface{}`)
+
+Nantinya kita akan sering bertemu dengan `nil` setelah masuk pada pembahasan-pembahasan tersebut.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 28618f39fe833c0f1ae5b3761d09fd37dcfebffd Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:55 +0700
Subject: [PATCH 060/522] New translations a-unit-test.md (English)
---
en/content-en/A-unit-test.md | 198 +++++++++++++++++++++++++++++++++++
1 file changed, 198 insertions(+)
create mode 100644 en/content-en/A-unit-test.md
diff --git a/en/content-en/A-unit-test.md b/en/content-en/A-unit-test.md
new file mode 100644
index 000000000..2a9655109
--- /dev/null
+++ b/en/content-en/A-unit-test.md
@@ -0,0 +1,198 @@
+# A.58. Unit Test
+
+Go menyediakan package `testing`, berisikan banyak sekali tools untuk keperluan unit test.
+
+Pada chapter ini kita akan belajar mengenai testing, benchmark, dan juga testing menggunakan [testify](https://github.com/stretchr/testify).
+
+## A.58.1. Persiapan
+
+Langsung saja kita praktek. Pertama siapkan terlebih dahulu sebuah struct `Kubus`. Variabel object hasil struct ini nantinya kita gunakan sebagai bahan testing.
+
+```go
+package main
+
+import "math"
+
+type Kubus struct {
+ Sisi float64
+}
+
+func (k Kubus) Volume() float64 {
+ return math.Pow(k.Sisi, 3)
+}
+
+func (k Kubus) Luas() float64 {
+ return math.Pow(k.Sisi, 2) * 6
+}
+
+func (k Kubus) Keliling() float64 {
+ return k.Sisi * 12
+}
+```
+
+Simpan kode di atas dengan nama `bab55.go`.
+
+## A.58.2. Testing
+
+File untuk keperluan testing dipisah dengan file utama, namanya harus berakhiran `_test.go`, dan package-nya harus sama. Pada chapter ini, file utama adalah `bab55.go`, maka file testing harus bernama `bab55_test.go`.
+
+Unit test di Go dituliskan dalam bentuk fungsi, yang memiliki parameter yang bertipe `*testing.T`, dengan nama fungsi harus diawali kata **Test** (pastikan sudah meng-import package `testing` sebelumnya). Lewat parameter tersebut, kita bisa mengakses method-method untuk keperluan testing.
+
+Pada contoh di bawah ini disiapkan 3 buah fungsi test, yang masing-masing digunakan untuk mengecek apakah hasil kalkulasi volume, luas, dan keliling kubus adalah benar.
+
+```go
+package main
+
+import "testing"
+
+var (
+ kubus Kubus = Kubus{4}
+ volumeSeharusnya float64 = 64
+ luasSeharusnya float64 = 96
+ kelilingSeharusnya float64 = 48
+)
+
+func TestHitungVolume(t *testing.T) {
+ t.Logf("Volume : %.2f", kubus.Volume())
+
+ if kubus.Volume() != volumeSeharusnya {
+ t.Errorf("SALAH! harusnya %.2f", volumeSeharusnya)
+ }
+}
+
+func TestHitungLuas(t *testing.T) {
+ t.Logf("Luas : %.2f", kubus.Luas())
+
+ if kubus.Luas() != luasSeharusnya {
+ t.Errorf("SALAH! harusnya %.2f", luasSeharusnya)
+ }
+}
+
+func TestHitungKeliling(t *testing.T) {
+ t.Logf("Keliling : %.2f", kubus.Keliling())
+
+ if kubus.Keliling() != kelilingSeharusnya {
+ t.Errorf("SALAH! harusnya %.2f", kelilingSeharusnya)
+ }
+}
+```
+
+Method `t.Logf()` digunakan untuk memunculkan log. Method ini equivalen dengan `fmt.Printf()`.
+
+Method `Errorf()` digunakan untuk memunculkan log dengan diikuti keterangan bahwa terjadi **fail** pada saat testing.
+
+Cara eksekusi testing adalah menggunakan command `go test`. Karena struct yang diuji berada dalam file `bab55.go`, maka pada saat eksekusi test menggunakan `go test`, nama file `bab55_test.go` dan `bab55.go` perlu dituliskan sebagai argument.
+
+Argument `-v` atau verbose digunakan menampilkan semua output log pada saat pengujian.
+
+Jalankan aplikasi seperti gambar di bawah ini, terlihat bahwa tidak ada test yang fail.
+
+
+
+OK, selanjutnya coba ubah rumus kalkulasi method `Keliling()`. Tujuan dari pengubahan ini adalah untuk mengetahui bagaimana penanda fail muncul ketika ada test yang gagal.
+
+```go
+func (k Kubus) Keliling() float64 {
+ return k.Sisi * 15
+}
+```
+
+Setelah itu jalankan lagi test.
+
+
+
+## A.58.3. Method Test
+
+Table berikut berisikan method standar testing yang bisa digunakan di Go.
+
+| Method | Kegunaan |
+|:------------ |:---------------------------------------------------------------------------- |
+| `Log()` | Menampilkan log |
+| `Logf()` | Menampilkan log menggunakan format |
+| `Fail()` | Menandakan terjadi `Fail()` dan proses testing fungsi tetap diteruskan |
+| `FailNow()` | Menandakan terjadi `Fail()` dan proses testing fungsi dihentikan |
+| `Failed()` | Menampilkan laporan fail |
+| `Error()` | `Log()` diikuti dengan `Fail()` |
+| `Errorf()` | `Logf()` diikuti dengan `Fail()` |
+| `Fatal()` | `Log()` diikuti dengan `failNow()` |
+| `Fatalf()` | `Logf()` diikuti dengan `failNow()` |
+| `Skip()` | `Log()` diikuti dengan `SkipNow()` |
+| `Skipf()` | `Logf()` diikuti dengan `SkipNow()` |
+| `SkipNow()` | Menghentikan proses testing fungsi, dilanjutkan ke testing fungsi setelahnya |
+| `Skiped()` | Menampilkan laporan skip |
+| `Parallel()` | Menge-set bahwa eksekusi testing adalah parallel |
+
+## A.58.4. Benchmark
+
+Package `testing` selain berisikan tools untuk testing juga berisikan tools untuk benchmarking. Cara pembuatan benchmark sendiri cukup mudah yaitu dengan membuat fungsi yang namanya diawali dengan **Benchmark** dan parameternya bertipe `*testing.B`.
+
+Sebagai contoh, kita akan mengetes performa perhitungan luas kubus. Siapkan fungsi dengan nama `BenchmarkHitungLuas()` dengan isi adalah kode berikut.
+
+```go
+func BenchmarkHitungLuas(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ kubus.Luas()
+ }
+}
+```
+
+Jalankan test menggunakan argument `-bench=.`, argumen ini digunakan untuk menandai bahwa selain testing terdapat juga benchmark yang perlu diuji.
+
+
+
+Arti dari `30000000 51.1 ns/op` adalah, fungsi di atas di-test sebanyak **30 juta** kali, hasilnya membutuhkan waktu rata-rata **51 nano detik** untuk run satu fungsi.
+
+## A.58.5. Testing Menggunakan testify
+
+Package **testify** berisikan banyak sekali tools yang bisa dimanfaatkan untuk keperluan testing di Go.
+
+Testify bisa di-download pada [github.com/stretchr/testify](https://github.com/stretchr/testify) menggunakan `go get`.
+
+Di dalam testify terdapat 5 package dengan kegunaan berbeda-beda satu dengan lainnya. Detailnya bisa dilihat pada tabel berikut.
+
+| Package | Kegunaan |
+|:------- |:--------------------------------------------------------------------------------------------------- |
+| assert | Berisikan tools standar untuk testing |
+| http | Berisikan tools untuk keperluan testing http |
+| mock | Berisikan tools untuk mocking object |
+| require | Sama seperti assert, hanya saja jika terjadi fail pada saat test akan menghentikan eksekusi program |
+| suite | Berisikan tools testing yang berhubungan dengan struct dan method |
+
+Pada chapter ini akan kita contohkan bagaimana penggunaan package assert. Silakan perhatikan contoh berikut.
+
+```go
+import "github.com/stretchr/testify/assert"
+
+...
+
+func TestHitungVolume(t *testing.T) {
+ assert.Equal(t, kubus.Volume(), volumeSeharusnya, "perhitungan volume salah")
+}
+
+func TestHitungLuas(t *testing.T) {
+ assert.Equal(t, kubus.Luas(), luasSeharusnya, "perhitungan luas salah")
+}
+
+func TestHitungKeliling(t *testing.T) {
+ assert.Equal(t, kubus.Keliling(), kelilingSeharusnya, "perhitungan keliling salah")
+}
+```
+
+Fungsi `assert.Equal()` digunakan untuk uji perbandingan. Parameter ke-2 dibandingkan nilainya dengan parameter ke-3. Jika tidak sama, maka pesan parameter ke-3 akan dimunculkan.
+
+
+
+---
+
+- [Testify](https://github.com/stretchr/testify), by "Stretchr, Inc"
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 1cf101a775da0ee0670e1251bc5ecf3cabe75c7c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:56 +0700
Subject: [PATCH 061/522] New translations a-url-parsing.md (English)
---
en/content-en/A-url-parsing.md | 48 ++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 en/content-en/A-url-parsing.md
diff --git a/en/content-en/A-url-parsing.md b/en/content-en/A-url-parsing.md
new file mode 100644
index 000000000..0e395223e
--- /dev/null
+++ b/en/content-en/A-url-parsing.md
@@ -0,0 +1,48 @@
+# A.52. URL Parsing
+
+Data string url bisa dikonversi ke dalam bentuk `url.URL`. Dengan menggunakan tipe tersebut akan ada banyak informasi yang bisa kita manfaatkan, di antaranya adalah jenis protokol yang digunakan, path yang diakses, query, dan lainnya.
+
+Berikut adalah contoh sederhana konversi string ke `url.URL`.
+
+```go
+package main
+
+import "fmt"
+import "net/url"
+
+func main() {
+ var urlString = "http://kalipare.com:80/hello?name=john wick&age=27"
+ var u, e = url.Parse(urlString)
+ if e != nil {
+ fmt.Println(e.Error())
+ return
+ }
+
+ fmt.Printf("url: %s\n", urlString)
+
+ fmt.Printf("protocol: %s\n", u.Scheme) // http
+ fmt.Printf("host: %s\n", u.Host) // kalipare.com:80
+ fmt.Printf("path: %s\n", u.Path) // /hello
+
+ var name = u.Query()["name"][0] // john wick
+ var age = u.Query()["age"][0] // 27
+ fmt.Printf("name: %s, age: %s\n", name, age)
+}
+```
+
+Fungsi `url.Parse()` digunakan untuk parsing string ke bentuk url. Mengembalikan 2 data, variabel objek bertipe `url.URL` dan error (jika ada). Lewat variabel objek tersebut pengaksesan informasi url akan menjadi lebih mudah, contohnya seperti nama host bisa didapatkan lewat `u.Host`, protokol lewat `u.Scheme`, dan lainnya.
+
+Selain itu, query yang ada pada url akan otomatis diparsing juga, menjadi bentuk `map[string][]string`, dengan key adalah nama elemen query, dan value array string yang berisikan value elemen query.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From cd1109907f0b63e9a7a5f85d9326d5496d13d089 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:57 +0700
Subject: [PATCH 062/522] New translations a-variabel.md (English)
---
en/content-en/A-variabel.md | 192 ++++++++++++++++++++++++++++++++++++
1 file changed, 192 insertions(+)
create mode 100644 en/content-en/A-variabel.md
diff --git a/en/content-en/A-variabel.md b/en/content-en/A-variabel.md
new file mode 100644
index 000000000..cd19206f6
--- /dev/null
+++ b/en/content-en/A-variabel.md
@@ -0,0 +1,192 @@
+# A.9. Variabel
+
+Go mengadopsi dua jenis penulisan variabel, yaitu yang dituliskan tipe data-nya, dan juga yang tidak. Kedua cara tersebut valid dan tujuannya sama, pembedanya hanya cara penulisannya saja.
+
+Pada chapter ini akan dikupas tuntas tentang macam-macam cara deklarasi variabel.
+
+## A.9.1. Deklarasi Variabel Beserta Tipe Data
+
+Go memiliki aturan cukup ketat dalam hal penulisan variabel. Ketika deklarasi, tipe data yg digunakan harus dituliskan juga. Istilah lain dari konsep ini adalah **manifest typing**.
+
+Berikut adalah contoh cara pembuatan variabel yang tipe datanya harus ditulis. Silakan tulis pada project baru atau pada project yang sudah ada, bebas. Pastikan saja untuk setiap project baru untuk tidak lupa inisialisasi project menggunakan command `go mod init `. Ok lanjut.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var firstName string = "john"
+
+ var lastName string
+ lastName = "wick"
+
+ fmt.Printf("halo %s %s!\n", firstName, lastName)
+}
+```
+
+Keyword `var` di atas digunakan untuk deklarasi variabel, contohnya bisa dilihat pada `firstName` dan `lastName`.
+
+Nilai variabel `firstName` diisi langsung ketika deklarasi, berbeda dibanding `lastName` yang nilainya diisi setelah baris kode deklarasi, hal seperti ini diperbolehkan di Go.
+
+
+
+## A.9.2. Deklarasi Variabel Menggunakan Keyword `var`
+
+Pada kode di atas bisa dilihat bagaimana sebuah variabel dideklarasikan dan diisi nilainya. Keyword `var` digunakan untuk membuat variabel baru.
+
+Skema penggunaan keyword var:
+
+```go
+var
+var =
+```
+
+Contoh:
+
+```go
+var lastName string
+var firstName string = "john"
+```
+
+Nilai variabel bisa di-isi langsung pada saat deklarasi variabel.
+
+#### • Penggunaan Fungsi `fmt.Printf()`
+
+Fungsi ini digunakan untuk menampilkan output dalam bentuk tertentu. Kegunaannya sama seperti fungsi `fmt.Println()`, hanya saja struktur outputnya didefinisikan di awal.
+
+Perhatikan bagian `"halo %s %s!\n"`, karakter `%s` di situ akan diganti dengan data `string` yang berada di parameter ke-2, ke-3, dan seterusnya.
+
+Contoh lain, ketiga baris kode berikut ini akan menghasilkan output yang sama, meskipun cara penulisannya berbeda.
+
+```go
+fmt.Printf("halo john wick!\n")
+fmt.Printf("halo %s %s!\n", firstName, lastName)
+fmt.Println("halo", firstName, lastName + "!")
+```
+
+Tanda plus (`+`) jika ditempatkan di antara string, fungsinya adalah untuk penggabungan string atau *string concatenation*.
+
+Fungsi `fmt.Printf()` tidak menghasilkan baris baru di akhir text, oleh karena itu digunakanlah literal *newline* yaitu `\n`, untuk memunculkan baris baru di akhir. Hal ini sangat berbeda jika dibandingkan dengan fungsi `fmt.Println()` yang secara otomatis menghasilkan new line (baris baru) di akhir.
+
+## A.9.3. Deklarasi Variabel Tanpa Tipe Data
+
+Selain *manifest typing*, Go juga mengadopsi konsep **type inference**, yaitu metode deklarasi variabel yang tipe data-nya ditentukan oleh tipe data nilainya, cara kontradiktif jika dibandingkan dengan cara pertama. Dengan metode jenis ini, keyword `var` dan tipe data tidak perlu ditulis.
+
+```go
+var firstName string = "john"
+lastName := "wick"
+
+fmt.Printf("halo %s %s!\n", firstName, lastName)
+```
+
+Variabel `lastName` dideklarasikan dengan menggunakan metode type inference. Penandanya tipe data tidak dituliskan pada saat deklarasi. Pada penggunaan metode ini, operand `=` harus diganti dengan `:=` dan keyword `var` dihilangkan.
+
+Tipe data `lastName` secara otomatis akan ditentukan menyesuaikan value atau nilai-nya. Jika nilainya adalah berupa `string` maka tipe data variabel adalah `string`. Pada contoh di atas, nilainya adalah string `"wick"`.
+
+Diperbolehkan untuk tetap menggunakan keyword `var` pada saat deklarasi meskipun tanpa menuliskan tipe data, dengan ketentuan tidak menggunakan tanda `:=`, melainkan tetap menggunakan `=`.
+
+```go
+// menggunakan var, tanpa tipe data, menggunakan perantara "="
+var firstName = "john"
+
+// tanpa var, tanpa tipe data, menggunakan perantara ":="
+lastName := "wick"
+```
+
+Kedua deklarasi di atas maksudnya sama. Silakan pilih yang nyaman di hati.
+
+Tanda `:=` hanya digunakan sekali di awal pada saat deklarasi. Untuk assignment nilai selanjutnya harus menggunakan tanda `=`, contoh:
+
+```go
+lastName := "wick"
+lastName = "ethan"
+lastName = "bourne"
+```
+
+> Perlu diketahui, deklarasi menggunakan `:=` hanya bisa dilakukan di dalam blok fungsi.
+
+## A.9.4. Deklarasi Multi Variabel
+
+Go mendukung metode deklarasi banyak variabel secara bersamaan, caranya dengan menuliskan variabel-variabel-nya dengan pembatas tanda koma (`,`). Untuk pengisian nilainya-pun diperbolehkan secara bersamaan.
+
+```go
+var first, second, third string
+first, second, third = "satu", "dua", "tiga"
+```
+
+Pengisian nilai juga bisa dilakukan bersamaan pada saat deklarasi. Caranya dengan menuliskan nilai masing-masing variabel berurutan sesuai variabelnya dengan pembatas koma (`,`).
+
+```go
+var fourth, fifth, sixth string = "empat", "lima", "enam"
+```
+
+Kalau ingin lebih ringkas:
+
+```go
+seventh, eight, ninth := "tujuh", "delapan", "sembilan"
+```
+
+Dengan menggunakan teknik type inference, deklarasi multi variabel bisa dilakukan untuk variabel-variabel yang tipe data satu sama lainnya berbeda.
+
+```
+one, isFriday, twoPointTwo, say := 1, true, 2.2, "hello"
+```
+
+## A.9.5. Variabel Underscore `_`
+
+Go memiliki aturan unik yang jarang dimiliki bahasa lain, yaitu tidak boleh ada satupun variabel yang menganggur. Artinya, semua variabel yang dideklarasikan harus digunakan. Jika ada variabel yang tidak digunakan tapi dideklarasikan, error akan muncul pada saat kompilasi dan program tidak akan bisa di-run.
+
+
+
+*Underscore* (`_`) adalah *reserved variable* yang bisa dimanfaatkan untuk menampung nilai yang tidak dipakai. Bisa dibilang variabel ini merupakan keranjang sampah.
+
+```go
+_ = "belajar Golang"
+_ = "Golang itu mudah"
+name, _ := "john", "wick"
+```
+
+Pada contoh di atas, variabel `name` akan berisikan text `john`, sedang nilai `wick` ditampung oleh variabel underscore, menandakan bahwa nilai tersebut tidak akan digunakan.
+
+Variabel underscore adalah *predefined*, jadi tidak perlu menggunakan `:=` untuk pengisian nilai, cukup dengan `=` saja. Namun khusus untuk pengisian nilai multi variabel yang dilakukan dengan metode type inference, boleh di dalamnya terdapat variabel underscore.
+
+Biasanya variabel underscore sering dimanfaatkan untuk menampung nilai balik fungsi yang tidak digunakan.
+
+Perlu diketahui, bahwa isi variabel underscore tidak dapat ditampilkan. Data yang sudah masuk variabel tersebut akan hilang. Ibaratkan variabel underscore seperti blackhole, objek apapun yang masuk ke dalamnya, akan terjebak selamanya di-dalam singularity dan tidak akan bisa keluar 😁
+
+## A.9.6. Deklarasi Variabel Menggunakan Keyword `new`
+
+Keyword `new` digunakan untuk membuat variabel **pointer** dengan tipe data tertentu. Nilai data default-nya akan menyesuaikan tipe datanya.
+
+```go
+name := new(string)
+
+fmt.Println(name) // 0x20818a220
+fmt.Println(*name) // ""
+```
+
+Variabel `name` menampung data bertipe **pointer string**. Jika ditampilkan yang muncul bukanlah nilainya melainkan alamat memori nilai tersebut (dalam bentuk notasi heksadesimal). Untuk menampilkan nilai aslinya, variabel tersebut perlu di-**dereference** terlebih dahulu, menggunakan tanda asterisk (`*`).
+
+Mungkin untuk sekarang banyak yang akan bingung tentang apa itu pointer, namun tak apa, karena nantinya pada chapter [A.23. Pointer](/A-pointer.html) akan dikupas habis topik tersebut.
+
+## A.9.7. Deklarasi Variabel Menggunakan Keyword `make`
+
+Keyword ini hanya bisa digunakan untuk pembuatan beberapa jenis variabel saja, yaitu:
+
+- channel
+- slice
+- map
+
+Dan lagi, mungkin banyak yang akan bingung. Ketika sudah masuk ke pembahasan masing-masing poin tersebut, akan terlihat apa kegunaan dari keyword `make` ini.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From b1f0a70d274849c327f07d781b5ea19da30f7e24 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:58 +0700
Subject: [PATCH 063/522] New translations a-waitgroup.md (English)
---
en/content-en/A-waitgroup.md | 81 ++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
create mode 100644 en/content-en/A-waitgroup.md
diff --git a/en/content-en/A-waitgroup.md b/en/content-en/A-waitgroup.md
new file mode 100644
index 000000000..0e218bb6d
--- /dev/null
+++ b/en/content-en/A-waitgroup.md
@@ -0,0 +1,81 @@
+# A.59. WaitGroup
+
+Sebelumnya kita telah belajar banyak mengenai channel, yang fungsi utama-nya adalah untuk sharing data antar goroutine. Selain untuk komunikasi data, channel secara tidak langsung bisa dimanfaatkan untuk kontrol goroutine.
+
+Go menyediakan package `sync`, berisi cukup banyak API untuk handle masalah multiprocessing (goroutine), salah satunya di antaranya adalah yang kita bahas pada chapter ini, yaitu `sync.WaitGroup`.
+
+Kegunaan `sync.WaitGroup` adalah untuk sinkronisasi goroutine. Berbeda dengan channel, `sync.WaitGroup` memang dirancang khusus untuk maintain goroutine, penggunaannya lebih mudah dan lebih efektif dibanding channel.
+
+> Sebenarnya kurang pas jika membandingkan `sync.WaitGroup` dan channel, karena fungsi utama dari keduanya adalah berbeda. Channel untuk keperluan sharing data antar goroutine, sedangkan `sync.WaitGroup` untuk sinkronisasi goroutine.
+
+## A.59.1. Penerapan `sync.WaitGroup`
+
+`sync.WaitGroup` digunakan untuk menunggu goroutine. Cara penggunaannya sangat mudah, tinggal masukan jumlah goroutine yang dieksekusi, sebagai parameter method `Add()` object cetakan `sync.WaitGroup`. Dan pada akhir tiap-tiap goroutine, panggil method `Done()`. Juga, pada baris kode setelah eksekusi goroutine, panggil method `Wait()`.
+
+Agar lebih jelas, silakan coba kode berikut.
+
+```go
+package main
+
+import "sync"
+import "runtime"
+import "fmt"
+
+func doPrint(wg *sync.WaitGroup, message string) {
+ defer wg.Done()
+ fmt.Println(message)
+}
+
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ var wg sync.WaitGroup
+
+ for i := 0; i < 5; i++ {
+ var data = fmt.Sprintf("data %d", i)
+
+ wg.Add(1)
+ go doPrint(&wg, data)
+ }
+
+ wg.Wait()
+}
+```
+
+Kode di atas merupakan contoh penerapan `sync.WaitGroup` untuk pengelolahan goroutine. Fungsi `doPrint()` akan dijalankan sebagai goroutine, dengan tugas menampilkan isi variabel `message`.
+
+Variabel `wg` disiapkan bertipe `sync.WaitGroup`, dibuat untuk sinkronisasi goroutines yang dijalankan.
+
+Di tiap perulangan statement `wg.Add(1)` dipanggil. Kode tersebut akan memberikan informasi kepada `wg` bahwa jumlah goroutine yang sedang di proses ditambah 1 (karena dipanggil 5 kali, maka `wg` akan sadar bahwa terdapat 5 buah goroutine sedang berjalan).
+
+Di baris selanjutnya, fungsi `doPrint()` dieksekusi sebagai goroutine. Di dalam fungsi tersebut, sebuah method bernama `Done()` dipanggil. Method ini digunakan untuk memberikan informasi kepada `wg` bahwa goroutine di mana method itu dipanggil sudah selesai. Sejumlah 5 buah goroutine dijalankan, maka method tersebut harus dipanggil 5 kali.
+
+Statement `wg.Wait()` bersifat blocking, proses eksekusi program tidak akan diteruskan ke baris selanjutnya, sebelum sejumlah 5 goroutine selesai. Jika `Add(1)` dipanggil 5 kali, maka `Done()` juga harus dipanggil 5 kali.
+
+Output program di atas.
+
+
+
+> `sync.WaitGroup` merupakan salah satu tipe yang *thread safe*. Kita tidak perlu khawatir terhadap potensi *race condition* karena variabel bertipe ini aman untuk digunakan di banyak goroutine secara paralel.
+
+## A.59.2. Perbedaan WaitGroup Dengan Channel
+
+Bukan sebuah perbandingan yang valid, tapi jika dibandingkan maka perbedaan antara channel dan `sync.WaitGroup` kurang lebih sebagai berikut:
+
+ - Channel tergantung kepada goroutine tertentu dalam penggunaannya, tidak seperti `sync.WaitGroup` yang dia tidak perlu tahu goroutine mana saja yang dijalankan, cukup tahu jumlah goroutine yang harus selesai
+ - Penerapan `sync.WaitGroup` lebih mudah dibanding channel
+ - Kegunaan utama channel adalah untuk komunikasi data antar goroutine. Sifatnya yang blocking bisa kita manfaatkan untuk manage goroutine; sedangkan WaitGroup khusus digunakan untuk sinkronisasi goroutine
+ - Performa `sync.WaitGroup` lebih baik dibanding channel, sumber: https://groups.google.com/forum/#!topic/golang-nuts/whpCEk9yLhc
+
+Kombinasi yang tepat antara `sync.WaitGroup` dan channel sangat penting, keduanya diperlukan dalam concurrent process agar bisa maksimal.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From a2f6b6562ca13a1e932767c613a45bf90f7cf41f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:58 +0700
Subject: [PATCH 064/522] New translations a-web-server.md (English)
---
en/content-en/A-web-server.md | 134 ++++++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
create mode 100644 en/content-en/A-web-server.md
diff --git a/en/content-en/A-web-server.md b/en/content-en/A-web-server.md
new file mode 100644
index 000000000..0224b3ebb
--- /dev/null
+++ b/en/content-en/A-web-server.md
@@ -0,0 +1,134 @@
+# A.51. Web Server
+
+Go menyediakan package `net/http`, berisi berbagai macam fitur untuk keperluan pembuatan aplikasi berbasis web. Termasuk di dalamnya web server, routing, templating, dan lainnya.
+
+Go memiliki web server sendiri, dan web server tersebut berada di dalam Go, tidak seperti bahasa lain yang servernya terpisah dan perlu diinstal sendiri (seperti PHP yang memerlukan Apache, .NET yang memerlukan IIS).
+
+Pada chapter ini kita akan belajar cara pembuatan aplikasi web sederhana dan pemanfaatan template untuk mendesain view.
+
+## A.51.1. Membuat Aplikasi Web Sederhana
+
+Package `net/http` memiliki banyak sekali fungsi yang bisa dimanfaatkan. Di bagian ini kita akan mempelajari beberapa fungsi penting seperti *routing* dan *start server*.
+
+Program di bawah ini merupakan contoh sederhana untuk memunculkan text di web ketika url tertentu diakses.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+
+func index(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "apa kabar!")
+}
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "halo!")
+ })
+
+ http.HandleFunc("/index", index)
+
+ fmt.Println("starting web server at http://localhost:8080/")
+ http.ListenAndServe(":8080", nil)
+}
+```
+
+Jalankan program tersebut.
+
+
+
+Jika muncul dialog **Do you want the application “bab48” to accept incoming network connections?** atau sejenis, pilih allow. Setelah itu, buka url [http://localhost/](http://localhost/) dan [http://localhost/index](http://localhost/index/) lewat browser.
+
+
+
+Fungsi `http.HandleFunc()` digunakan untuk routing aplikasi web. Maksud dari routing adalah penentuan aksi ketika url tertentu diakses oleh user.
+
+Pada kode di atas 2 rute didaftarkan, yaitu `/` dan `/index`. Aksi dari rute `/` adalah menampilkan text `"halo"` di halaman website. Sedangkan `/index` menampilkan text `"apa kabar!"`.
+
+Fungsi `http.HandleFunc()` memiliki 2 buah parameter yang harus diisi. Parameter pertama adalah rute yang diinginkan. Parameter kedua adalah *callback* atau aksi ketika rute tersebut diakses. Callback tersebut bertipe fungsi `func(w http.ResponseWriter, r *http.Request)`.
+
+Pada pendaftaran rute `/index`, callback-nya adalah fungsi `index()`, hal seperti ini diperbolehkan asalkan tipe dari fungsi tersebut sesuai.
+
+Fungsi `http.listenAndServe()` digunakan untuk menghidupkan server sekaligus menjalankan aplikasi menggunakan server tersebut. Di Go, 1 web aplikasi adalah 1 buah server berbeda.
+
+Pada contoh di atas, server dijalankan pada port `8080`.
+
+Perlu diingat, setiap ada perubahan pada file `.go`, `go run` harus dipanggil lagi.
+
+Untuk menghentikan web server, tekan **CTRL+C** pada terminal atau CMD, di mana pengeksekusian aplikasi berlangsung.
+
+## A.51.2. Penggunaan Template Web
+
+Template engine memberikan kemudahan dalam mendesain tampilan view aplikasi website. Dan kabar baiknya Go menyediakan engine template sendiri, dengan banyak fitur yang tersedia di dalamnya.
+
+Di sini kita akan belajar contoh sederhana penggunaan template untuk menampilkan data. Pertama siapkan dahulu template-nya. Buat file `template.html` lalu isi dengan kode berikut.
+
+```html
+
+
+ Go learn net/http
+
+
+
Hello {{.Name}} !
+
{{.Message}}
+
+
+```
+
+Kode `{{.Name}}` artinya memunculkan isi data property `Name` yang dikirim dari router. Kode tersebut nantinya di-replace dengan isi variabel `Name`.
+
+Selanjutnya ubah isi file `.go` dengan kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "html/template"
+import "net/http"
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ var data = map[string]string{
+ "Name": "john wick",
+ "Message": "have a nice day",
+ }
+
+ var t, err = template.ParseFiles("template.html")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ t.Execute(w, data)
+ })
+
+ fmt.Println("starting web server at http://localhost:8080/")
+ http.ListenAndServe(":8080", nil)
+}
+```
+
+Jalankan, lalu buka [http://localhost:8080/](http://localhost:8080/), maka data `Nama` dan `Message` akan muncul di view.
+
+
+
+Fungsi `template.ParseFiles()` digunakan untuk parsing template, mengembalikan 2 data yaitu instance template-nya dan error (jika ada). Pemanggilan method `Execute()` akan membuat hasil parsing template ditampilkan ke layar web browser.
+
+Pada kode di atas, variabel `data` disisipkan sebagai parameter ke-2 method `Execute()`. Isi dari variabel tersebut bisa diakses di-view dengan menggunakan notasi `{{.NAMA_PROPERTY}}` (nama variabel sendiri tidak perlu dituliskan, langsung nama property di dalamnya).
+
+Pada contoh di atas, statement di view `{{.Name}}` akan menampilkan isi dari `data.Name`.
+
+## A.51.3. Advanced Web Programming
+
+Sampai chapter ini yang kita pelajari adalah dasar-dasar pemrograman Go, dibahas per topik adalah per chapter. Nantinya jika sudah masuk [B.1. Golang Web App: Hello World](/B-golang-web-hello-world.html) hingga seterusnya akan lebih banyak membahas mengenai pemrograman web, jadi sabar dulu. Mari kita selesaikan dulu yang fundamental sebelum masuk ke web development.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From a512c34ac09368f6c903a22e517c84f2fa964672 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:40:59 +0700
Subject: [PATCH 065/522] New translations a-web-service-api.md (English)
---
en/content-en/A-web-service-api.md | 149 +++++++++++++++++++++++++++++
1 file changed, 149 insertions(+)
create mode 100644 en/content-en/A-web-service-api.md
diff --git a/en/content-en/A-web-service-api.md b/en/content-en/A-web-service-api.md
new file mode 100644
index 000000000..4ece8ea3a
--- /dev/null
+++ b/en/content-en/A-web-service-api.md
@@ -0,0 +1,149 @@
+# A.54. Web Service API Server
+
+Pada chapter ini kita akan mengkombinasikan pembahasan 2 chapter sebelumnya, yaitu web programming dan JSON, untuk membuat sebuah web service API dengan tipe data reponse berbentuk JSON.
+
+> Web Service API adalah sebuah web yang menerima request dari client dan menghasilkan response, biasa berupa JSON/XML.
+
+## A.54.1. Pembuatan Web API
+
+Pertama siapkan terlebih dahulu struct dan beberapa data sample.
+
+```go
+package main
+
+import "encoding/json"
+import "net/http"
+import "fmt"
+
+type student struct {
+ ID string
+ Name string
+ Grade int
+}
+
+var data = []student{
+ student{"E001", "ethan", 21},
+ student{"W001", "wick", 22},
+ student{"B001", "bourne", 23},
+ student{"B002", "bond", 23},
+}
+```
+
+Struct `student` di atas digunakan sebagai tipe elemen slice sample data, ditampung variabel `data`.
+
+Selanjutnya buat fungsi `users()` untuk handle endpoint `/users`. Di dalam fungsi tersebut ada proses deteksi jenis request lewat property `r.Method()`, untuk mencari tahu apakah jenis request adalah **POST** atau **GET** atau lainnya.
+
+```go
+func users(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ if r.Method == "GET" {
+ var result, err = json.Marshal(data)
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Write(result)
+ return
+ }
+
+ http.Error(w, "", http.StatusBadRequest)
+}
+```
+
+Jika request adalah GET (mengambil data), maka data yang di-encode ke JSON dijadikan sebagai response.
+
+Statement `w.Header().Set("Content-Type", "application/json")` digunakan untuk menentukan tipe response, yaitu sebagai JSON. Sedangkan `r.Write()` digunakan untuk mendaftarkan data sebagai response.
+
+Selebihnya, jika request tidak valid, response di set sebagai error menggunakan fungsi `http.Error()`.
+
+Siapkan juga handler untuk endpoint `/user`. Perbedaan endpoint ini dengan `/users` di atas adalah:
+
+ - Endpoint `/users` mengembalikan semua sample data yang ada (array).
+ - Endpoint `/user` mengembalikan satu buah data saja, diambel dari data sample berdasarkan `ID`-nya. Pada endpoint ini, client harus mengirimkan juga informasi `ID` data yang dicari.
+
+```go
+func user(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ if r.Method == "GET" {
+ var id = r.FormValue("id")
+ var result []byte
+ var err error
+
+ for _, each := range data {
+ if each.ID == id {
+ result, err = json.Marshal(each)
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Write(result)
+ return
+ }
+ }
+
+ http.Error(w, "User not found", http.StatusNotFound)
+ return
+ }
+
+ http.Error(w, "", http.StatusBadRequest)
+}
+```
+
+Method `r.FormValue()` digunakan untuk mengambil data form yang dikirim dari client, pada konteks ini data yang dimaksud adalah `ID`.
+
+Dengan menggunakan `ID` tersebut dicarilah data yang relevan. Jika ada, maka dikembalikan sebagai response. Jika tidak ada maka error **400, Bad Request** dikembalikan dengan pesan **User Not Found**.
+
+Terakhir, implementasikan kedua handler di atas.
+
+```go
+func main() {
+ http.HandleFunc("/users", users)
+ http.HandleFunc("/user", user)
+
+ fmt.Println("starting web server at http://localhost:8080/")
+ http.ListenAndServe(":8080", nil)
+}
+```
+
+Jalankan program, sekarang web server sudah live dan bisa dikonsumsi datanya.
+
+
+
+## A.54.2. Test Web Service API via Postman
+
+Setelah web server sudah berjalan, web service yang telah dibuat perlu untuk di-tes. Di sini saya menggunakan Google Chrome plugin bernama [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en) untuk mengetes API yang sudah dibuat.
+
+ - Test endpoint `/users`, apakah data yang dikembalikan sudah benar.
+
+ 
+
+ - Test endpoint `/user`, isi form data `id` dengan nilai `E001`.
+
+ 
+
+## A.54.3. Test Web Service API via `cURL`
+
+Untuk testing bisa juga memanfaatkan cURL. Apabila pembaca menggunakan Windows 10, seharusnya sudah ter-include cURL. Jika bukan pengguna Windows 10, bisa menginstall-nya dan mendaftarkannya ke path variables (agar bisa diakses melalui terminal/cmd dari mana saja).
+
+curl -X GET http://localhost:8080/users curl -X GET http://localhost:8080/user?id=B002 ```
+
+
+
+Data ID yang ingin dicari melalui endpoint /user, ditulis dengan ?id=B002 yang berarti dilewatkan melalui query parameters (umumnya data yang ingin dilampirkan melalui method GET adalah dengan query parameters).
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From e6a79c0c0fd9d11c661dc6ba410439d2d977a594 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:00 +0700
Subject: [PATCH 066/522] New translations b-ajax-json-payload.md (English)
---
en/content-en/B-ajax-json-payload.md | 201 +++++++++++++++++++++++++++
1 file changed, 201 insertions(+)
create mode 100644 en/content-en/B-ajax-json-payload.md
diff --git a/en/content-en/B-ajax-json-payload.md b/en/content-en/B-ajax-json-payload.md
new file mode 100644
index 000000000..432b0bfe8
--- /dev/null
+++ b/en/content-en/B-ajax-json-payload.md
@@ -0,0 +1,201 @@
+# B.14. AJAX JSON Payload
+
+Sebelumnya kita telah belajar bagaimana cara submit data dari front-end ke back-end menggunakan teknik **Form Data**. Kali ini kita akan belajar tentang cara request menggunakan teknik **Request Payload** dengan tipe payload adalah **JSON**.
+
+Teknik request **Form Data** digunakan salah satu nya pada request submit lewat ``. Pada chapter ini, kita tidak akan menggunakan cara submit lewat form, melainkan menggunakan teknik AJAX (Asynchronous JavaScript And XML), dengan payload ber-tipe JSON.
+
+[Perbedaan](http://stackoverflow.com/a/23152367/1467988) antara kedua jenis request tersebut adalah pada isi header `Content-Type`, dan bentuk informasi dikirimkan. Secara default, request lewat ``, content type-nya adalah `application/x-www-form-urlencoded`. Data dikirimkan dalam bentuk query string (key-value) seperti `id=n001&nama=bruce`.
+
+> Ketika di form ditambahkan atribut `enctype="multipart/form-data"`, maka content type berubah menjadi `multipart/form-data`.
+
+Request Payload JSON sedikit berbeda, `Content-Type` berisikan `application/json`, dan data disisipkan dalam `Body` dalam bentuk **JSON** string.
+
+## B.14.1. Struktur Folder Proyek
+
+OK, langsung saja, pertama siapkan proyek dengan struktur seperti pada gambar di bawah ini.
+
+
+
+> Silakan unduh file js jQuery dari situs official-nya.
+
+## B.14.2. Front End - HTML
+
+Layout dari view perlu disiapkan terlebih dahulu, tulis kode berikut pada file `view.html`.
+
+```html
+
+
+
+ JSON Payload
+
+
+
+
+
+
+
+
+```
+
+Selanjutnya, pada tag `` tambahkan tabel sederhana berisikan inputan-inputan yang diperlukan. Ada tiga buah inputan yang harus dipersiapkan, yaitu: *Name*, *Age*, dan *Gender*; dan juga sebuah button untuk submit form.
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## B.14.3. Front End - HTML
+
+Sekarang kita masuk ke bagian paling menyenangkan/menyebalkan (tergantung taste), yaitu javascript. Siapkan sebuah event `submit` pada `#user-form`. Dalam event tersebut default handler event submit milik `` di-override, diganti dengan AJAX request.
+
+```js
+$("#user-form").on("submit", function (e) {
+ e.preventDefault();
+
+ var $self = $(this);
+ var payload = JSON.stringify({
+ name: $('[name="name"]').val(),
+ age: parseInt($('[name="age"]').val(), 10),
+ gender: $('[name="gender"]').val()
+ });
+
+ $.ajax({
+ url: $self.attr("action"),
+ type: $self.attr("method"),
+ data: payload,
+ contentType: 'application/json',
+ }).then(function (res) {
+ $(".message").text(res);
+ }).catch(function (a) {
+ alert("ERROR: " + a.responseText);
+ });
+});
+```
+
+Value semua inputan diambil lalu dimasukkan dalam sebuah objek lalu di stringify (agar menjadi JSON string), untuk kemudian di jadikan sebagai payload request. Bisa dilihat pada kode AJAX di atas, `contentType` nilainya adalah `application/json`.
+
+Respon dari ajax di atas akan dimunculkan pada ``.
+
+## B.14.4. Back End
+
+3 buah rute perlu disiapkan, yang pertama adalah untuk menampilkan `view.html`, untuk keperluan submit data, dan registrasi asset.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+import "html/template"
+import "encoding/json"
+
+func main() {
+ http.HandleFunc("/", handleIndex)
+ http.HandleFunc("/save", handleSave)
+
+ http.Handle("/static/",
+ http.StripPrefix("/static/",
+ http.FileServer(http.Dir("assets"))))
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Handler `handleIndex` berisikan kode untuk parsing `view.html`.
+
+```go
+func handleIndex(w http.ResponseWriter, r *http.Request) {
+ tmpl := template.Must(template.ParseFiles("view.html"))
+ if err := tmpl.Execute(w, nil); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+```
+
+Sedangkan `handleSave` akan memproses request yang di-submit dari bagian depan.
+
+```go
+func handleSave(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "POST" {
+ decoder := json.NewDecoder(r.Body)
+ payload := struct {
+ Name string `json:"name"`
+ Age int `json:"age"`
+ Gender string `json:"gender"`
+ }{}
+ if err := decoder.Decode(&payload); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ message := fmt.Sprintf(
+ "hello, my name is %s. I'm %d year old %s",
+ payload.Name,
+ payload.Age,
+ payload.Gender,
+ )
+ w.Write([]byte(message))
+ return
+ }
+
+ http.Error(w, "Only accept POST request", http.StatusBadRequest)
+}
+```
+
+Isi payload didapatkan dengan cara men-decode body request (`r.Body`). Proses decoding tidak dilakukan menggunakan `json.Unmarshal()` melainkan lewat json decoder, karena akan [lebih efisien](http://stackoverflow.com/a/21198571/1467988) untuk jenis kasus seperti ini.
+
+> Gunakan `json.Decoder` jika data adalah stream `io.Reader`. Gunakan `json.Unmarshal()` untuk decode data sumbernya sudah ada di memory.
+
+## B.14.5. Test
+
+Jalankan program, test hasilnya di browser.
+
+
+
+Gunakan fasilitas Developer Tools pada Chrome untuk melihat detail dari request.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 4e0dff5c6aaaf0908e4423612acf3c820e85998a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:01 +0700
Subject: [PATCH 067/522] New translations b-ajax-json-response.md (English)
---
en/content-en/B-ajax-json-response.md | 94 +++++++++++++++++++++++++++
1 file changed, 94 insertions(+)
create mode 100644 en/content-en/B-ajax-json-response.md
diff --git a/en/content-en/B-ajax-json-response.md b/en/content-en/B-ajax-json-response.md
new file mode 100644
index 000000000..edb929334
--- /dev/null
+++ b/en/content-en/B-ajax-json-response.md
@@ -0,0 +1,94 @@
+# B.15. AJAX JSON Response
+
+Pada chapter sebelumnya, kita belajar cara untuk memproses request dengan payload bertipe JSON. Pada chapter ini kita akan belajar untuk membuat satu endpoint yang mengembalikan data JSON string.
+
+## B.15.1. Praktek
+
+Siapkan satu buah folder proyek baru, dengan satu buah file di dalamnya bernama `main.go`. Dalam file ini siapkan rute `/`.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+import "encoding/json"
+
+func main() {
+ http.HandleFunc("/", ActionIndex)
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Selanjutnya buat handler untuk rute `/`. Di dalam fungsi ini, data dummy ber-tipe slice object disiapkan. Data ini akan dikonversi ke JSON lalu dijadikan nilai balik endpoint `/`.
+
+```go
+func ActionIndex(w http.ResponseWriter, r *http.Request) {
+ data := [] struct {
+ Name string
+ Age int
+ } {
+ { "Richard Grayson", 24 },
+ { "Jason Todd", 23 },
+ { "Tim Drake", 22 },
+ { "Damian Wayne", 21 },
+ }
+
+ jsonInBytes, err := json.Marshal(data)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(jsonInBytes)
+}
+```
+
+Cara mengkonversi data ke bentuk json cukup mudah, bisa menggunakan `json.Marshal()`. Fungsi ini mengembalikan dua nilai balik, data json (dalam bentuk `[]byte`) dan error jika ada.
+
+> Untuk mengambil bentuk string dari hasil konversi JSON, cukup lakukan casting pada data slice bytes tersebut. Contoh: `string(jsonInBytes)`
+
+Karena nilai balik konversi sudah dalam bentuk bytes, maka langsung saja panggil method `Write()` milik `http.ResponseWriter` dan sisipkan data json sebagai argument pemanggilan method.
+
+Jangan lupa juga untuk menambahkan response header `Content-Type: application/json`.
+
+## B.15.2. Testing
+
+OK, semua sudah selesai, lakukan testing.
+
+
+
+## B.15.3. JSON Response menggunakan JSON.Encoder
+
+Pada chapter sebelumnya sudah disinggung, bahwa lebih baik menggunakan `json.Decoder` jika ingin men-decode data yang sumbernya ada di stream `io.Reader`
+
+Package json juga memiliki fungsi lain-nya yaitu `json.Encoder`, yang sangat cocok digunakan untuk meng-encode data menjadi JSON dengan tujuan objek langsung ke stream `io.Reader`.
+
+Karena tipe `http.ResponseWriter` adalah meng-embed `io.Reader`, maka jelasnya bisa kita terapkan penggunaan encoder di sini.
+
+Contohnya penerapannya sebagai berikut.
+
+```go
+w.Header().Set("Content-Type", "application/json")
+
+err := json.NewEncoder(w).Encode(data)
+if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+}
+```
+
+Kode di atas hasilnya ekuivalen dengan penggunaan `json.Marshal`.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 502af34bd73f91ad8c7723141b0fe4aa9cb73dfb Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:02 +0700
Subject: [PATCH 068/522] New translations b-ajax-multi-upload.md (English)
---
en/content-en/B-ajax-multi-upload.md | 209 +++++++++++++++++++++++++++
1 file changed, 209 insertions(+)
create mode 100644 en/content-en/B-ajax-multi-upload.md
diff --git a/en/content-en/B-ajax-multi-upload.md b/en/content-en/B-ajax-multi-upload.md
new file mode 100644
index 000000000..eecf2bc4f
--- /dev/null
+++ b/en/content-en/B-ajax-multi-upload.md
@@ -0,0 +1,209 @@
+# B.16. AJAX Multiple File Upload
+
+Pada chapter ini, kita akan belajar 3 hal dalam satu waktu, yaitu:
+
+1. Bagaiamana cara untuk upload file via AJAX.
+2. Cara untuk handle upload banyak file sekaligus.
+3. Cara handle upload file yang lebih hemat memori.
+
+Sebelumnya pada chapter [B.13. Form Upload File](/B-form-upload-file.html), pemrosesan file upload dilakukan lewat **ParseMultipartForm**, sedangkan pada chapter ini metode yang dipakai berbeda, yaitu menggunakan **MultipartReader**.
+
+Kelebihan dari `MultipartReader` adalah, file yang di upload **tidak** di simpan sebagai file temporary di lokal terlebih dahulu (tidak seperti `ParseMultipartForm`), melainkan langsung diambil dari stream `io.Reader`.
+
+Di bagian front end, upload file secara asynchronous bisa dilakukan menggunakan objek [FormData](https://developer.mozilla.org/en/docs/Web/API/FormData). Semua file dimasukkan dalam objek `FormData`, setelah itu objek tersebut dijadikan sebagai payload AJAX request.
+
+## B.16.1. Struktur Folder Proyek
+
+Mari langsung kita praktekkan, pertama siapkan proyek dengan struktur seperti gambar di bawah ini.
+
+
+
+> Silakan unduh file js jQuery dari situs official jQuery.
+
+## B.16.2. Front End
+
+Buka `view.html`, siapkan template dasar view. Dalam file ini terdapat satu buah inputan upload file yang mendukung multi upload, dan satu buah tombol submit.
+
+Untuk meng-enable kapabilitas multi upload, cukup tambahkan atribut `multiple` pada input file.
+
+```html
+
+
+
+ Multiple Upload
+
+
+
+
+
+
+
+```
+
+Override event `submit` pada form `#user-form`, handler event ini berisikan proses mulai pembentukan objek `FormData` dari file-file yang telah di upload, hingga eksekusi AJAX.
+
+```js
+$("#user-form").on("submit", function (e) {
+ e.preventDefault();
+
+ var $self = $(this);
+ var files = $("#upload-file")[0].files;
+ var formData = new FormData();
+
+ for (var i = 0; i < files.length; i++) {
+ formData.append("files", files[i]);
+ }
+
+ $.ajax({
+ url: $self.attr("action"),
+ type: $self.attr("method"),
+ data: formData,
+ processData: false,
+ contentType: false,
+ }).then(function (res) {
+ alert(res);
+ $("#user-form").trigger("reset");
+ }).catch(function (a) {
+ alert("ERROR: " + a.responseText);
+ });
+});
+```
+
+Objek inputan files (yang didapat dari `$("#upload-file")[0].files`) memiliki property `.files` yang isinya merupakan array dari semua file yang dipilih oleh user ketika upload. File-file tersebut di-loop, dimasukkan ke dalam objek `FormData` yang telah dibuat.
+
+AJAX dilakukan lewat `jQuery.ajax`. Berikut adalah penjelasan mengenai konfigurasi `processData` dan `contentType` dalam AJAX yang sudah dibuat.
+
+ - Konfigurasi `contentType` perlu di set ke `false` agar header Content-Type yang dikirim bisa menyesuaikan data yang disisipkan.
+ - Konfigurasi `processData` juga perlu di set ke `false`, agar data yang akan di kirim tidak otomatis dikonversi ke query string atau json string (tergantung `contentType`). Pada konteks ini kita memerlukan payload tetap dalam tipe `FormData`.
+
+## B.16.3. Back End
+
+Ada 2 route handler yang harus dipersiapkan di back end. Pertama adalah rute `/` yang menampilkan form upload, dan rute `/upload` untuk pemrosesan upload sendiri.
+
+Buka file `main.go`, isi dengan package yang dibutuhkan, lalu lakukan registrasi dua rute yang dimaksud di atas, beserta satu buah rute untuk static assets.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+import "html/template"
+import "path/filepath"
+import "io"
+import "os"
+
+func main() {
+ http.HandleFunc("/", handleIndex)
+ http.HandleFunc("/upload", handleUpload)
+ http.Handle("/static/",
+ http.StripPrefix("/static/",
+ http.FileServer(http.Dir("assets"))))
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Buat handler rute `/`, parsing template view `view.html`.
+
+```go
+func handleIndex(w http.ResponseWriter, r *http.Request) {
+ tmpl := template.Must(template.ParseFiles("view.html"))
+ if err := tmpl.Execute(w, nil); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+```
+
+Sebelumnya, pada chapter [B.13. Form Upload File](/B-form-upload-file.html), metode yang digunakan untuk handle file upload adalah menggunakan `ParseMultipartForm`, file di proses dalam memori dengan alokasi tertentu, dan jika melebihi alokasi maka akan disimpan pada temporary file.
+
+Metode tersebut kurang tepat guna jika digunakan untuk memproses file yang ukurannya besar (file size melebihi `maxMemory`) atau jumlah file-nya sangat banyak (memakan waktu, karena isi dari masing-masing file akan ditampung pada file *temporary* sebelum benar-benar di-copy ke file tujuan).
+
+Solusinya dari dua masalah di atas adalah menggunakan `MultipartReader` untuk handling file upload. Dengan metode ini, file destinasi isinya akan di-copy lagsung dari stream `io.Reader`, tanpa butuh file temporary untuk perantara.
+
+Kembali ke bagian perkodingan, siapkan fungsi `handleUpload`, isinya kode berikut.
+
+```go
+func handleUpload(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ http.Error(w, "Only accept POST request", http.StatusBadRequest)
+ return
+ }
+
+ basePath, _ := os.Getwd()
+ reader, err := r.MultipartReader()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // ...
+}
+```
+
+Bisa dilihat, method `.MultipartReader()` dipanggil dari objek request milik handler. Mengembalikan dua objek, pertama `*multipart.Reader` dan `error` (jika ada).
+
+Selanjutnya lakukan perulangan terhadap objek `reader`. Setiap file yang di-upload di proses di masing-masing perulangan. Setelah looping berakhir. idealnya semua file sudah terproses dengan benar.
+
+```go
+for {
+ part, err := reader.NextPart()
+ if err == io.EOF {
+ break
+ }
+
+ fileLocation := filepath.Join(basePath, "files", part.FileName())
+ dst, err := os.Create(fileLocation)
+ if dst != nil {
+ defer dst.Close()
+ }
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if _, err := io.Copy(dst, part); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+w.Write([]byte(`all files uploaded`))
+```
+
+Method `.NextPart()` mengembalikan 2 informasi, yaitu objek stream `io.Reader` (dari file yg di upload), dan `error`.
+
+File destinasi dipersiapkan, kemudian diisi dengan data dari stream file, menggunakan `io.Copy()`.
+
+Jika `reader.NextPart()` mengembalikan error `io.EOF`, menandakan bahwa semua file sudah di proses, maka hentikan perulangan.
+
+OK, semua persiapan sudah cukup.
+
+## B.16.4. Testing
+
+Buka browser, test program yang telah dibuat. Coba lakukan pengujian dengan beberapa buah file.
+
+
+
+Cek apakah file sudah terupload.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 4a326599ab62a65dce8da65344488e9dca169700 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:03 +0700
Subject: [PATCH 069/522] New translations b-cookie.md (English)
---
en/content-en/B-cookie.md | 135 ++++++++++++++++++++++++++++++++++++++
1 file changed, 135 insertions(+)
create mode 100644 en/content-en/B-cookie.md
diff --git a/en/content-en/B-cookie.md b/en/content-en/B-cookie.md
new file mode 100644
index 000000000..419456b97
--- /dev/null
+++ b/en/content-en/B-cookie.md
@@ -0,0 +1,135 @@
+# B.21. HTTP Cookie
+
+Cookie adalah data dalam bentuk teks yang disimpan pada komputer (oleh web browser) ketika pengunjung sedang surfing ke sebuah situs. Cookie dapat dibuat dari sisi front end (javascript) maupun back end (dalam konteks ini Go).
+
+Cookie merupakan salah satu aspek penting dalam pengembangan aplikasi web. Sangat sering kita membutuhkan sebuah data bisa disimpan dan diakses untuk keperluan aplikasi web kita, seperti pengecekan preferensi pengunjung, pengecekan status login tidak nya user.
+
+Pada chapter ini kita akan belajar bagaimana cara membuat dan mengakses cookie di Go.
+
+## B.21.1. Praktek
+
+Buat sebuah folder proyek, siapkan satu buah file `main.go`. Buat fungsi `main()`, registrasikan dua buah rute.
+
+```go
+package main
+
+import (
+ "fmt"
+ gubrak "github.com/novalagung/gubrak/v2"
+ "net/http"
+ "time"
+)
+
+type M map[string]interface{}
+
+var cookieName = "CookieData"
+
+func main() {
+ http.HandleFunc("/", ActionIndex)
+ http.HandleFunc("/delete", ActionDelete)
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Variabel `cookieName` berisikan string, nantinya digunakan sebagai nama cookie.
+
+ - Rute `/` bertugas untuk membuat cookie baru (jika belum ada atau cookie sudah ada namun expired).
+ - Rute `/delete` mempunyai tugas untuk menghapus cookie, lalu redirect ke `/` sehingga cookie baru akan dibuat
+
+OK, sekarang buat fungsi handler `ActionIndex()`. Di dalam fungsi ini, data berupa random string disimpan dalam cookie.
+
+```go
+func ActionIndex(w http.ResponseWriter, r *http.Request) {
+ cookieName := "CookieData"
+
+ c := &http.Cookie{}
+
+ if storedCookie, _ := r.Cookie(cookieName); storedCookie != nil {
+ c = storedCookie
+ }
+
+ if c.Value == "" {
+ c = &http.Cookie{}
+ c.Name = cookieName
+ c.Value = gubrak.RandomString(32)
+ c.Expires = time.Now().Add(5 * time.Minute)
+ http.SetCookie(w, c)
+ }
+
+ w.Write([]byte(c.Value))
+}
+```
+
+Cookie bisa dikases lewat method `.Cookie()` milik objek `*http.Request`. Method ini mengembalikan 2 informasi:
+
+ - Objek cookie
+ - Error, jika ada
+
+Pada kode di atas, ketika `storedCookie` nilainya bukanlah `nil` (berarti cookie dengan nama `cookieName` sudah dibuat), maka objek cookie tersebut disimpan dalam `c`.
+
+Pembuatan cookie cukup mudah, tinggal cetak saja objek baru dari struct `http.Cookie`.
+
+Jika `c.Value` adalah kosong, kita asumsikan bahwa cookie belum pernah dibuat (atau expired), maka kita buat cookie baru dengan data adalah random string.
+
+> Untuk mempermudah generate random string, kita gunakan library bernama [Gubrak v2](https://github.com/novalagung/gubrak). Fungsi `gubrak.RandomString(32)` akan menghasilkan string 32 karakter.
+
+Cookie bisa expired. Lama cookie aktif ditentukan lewat property `Expires`. Pada kode di atas expiration duration kita set selama 5 menit.
+
+Gunakan `http.SetCookie()` untuk menyimpan cookie yang baru dibuat.
+
+OK, selanjutnya buat handler `ActionDelete()`, seperti yang sudah disinggung di atas. Handler ini difungsikan untuk menghapus cookie dengan nama `cookieName`, lalu redirect ke `/` agar cookie baru diciptakan.
+
+```go
+func ActionDelete(w http.ResponseWriter, r *http.Request) {
+ c := &http.Cookie{}
+ c.Name = cookieName
+ c.Expires = time.Unix(0, 0)
+ c.MaxAge = -1
+ http.SetCookie(w, c)
+
+ http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+}
+```
+
+Cara menghapus cookie adalah dengan menge-set ulang cookie dengan nama yang sama, dengan isi property `Expires = time.Unix(0, 0)` dan `MaxAge = -1`. Tujuannya agar cookie expired.
+
+## B.21.2. Testing
+
+Jalankan aplikasi, lalu akses `/`. Sebuah random string akan muncul di layar, dan jika kita cek pada bagian response header, informasi cookie nya juga tampil.
+
+
+
+Coba refresh page beberapa kali, informasi header cookie dan data yang muncul adalah tetap sama. Karena ketika cookie sudah pernah dibuat, maka seterusnya endpoint ini akan menggunakan data cookie yang sudah tersimpan tersebut.
+
+Selanjutnya, buka url `/delete`, halaman akan di redirect kembali ke `/`, dan random string baru beserta cookie baru terbuat. Dalam endpoint ini, cookie di hapus, dan karena step selanjutnya adalah redirect ke `/`, maka proses pengecekan dan pembuatan cookie akan dimulai kembali. Pengunjung akan mendapatkan data cookie baru dengan nama yang sama.
+
+## B.21.3. Properties Object `http.Cookie`
+
+Objek cookie memiliki beberapa property, beberapa di antaranya:
+
+| Property | Tipe Data | Deskripsi |
+| -------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Value | `string` | Data yang disimpan di cookie |
+| Path | `string` | Scope path cookie |
+| Domain | `string` | Scope domain cookie |
+| Expires | `time.Time` | Durasi cookie, ditulis dalam tipe `time.Time` |
+| MaxAge | `int` | Durasi cookie, ditulis dalam detik (numerik) |
+| Secure | `bool` | Scope cookie dalam konteks protocol yang digunakan ketika pengaksesan web. Property ini hanya berguna pada saat web server SSL/TLS enabled.
Jika false, maka cookie yang disimpan ketika web diakses menggunakan protocol http://, tetap bisa diakses lewat https://, dan berlaku juga untuk kebalikannya.
Jika true, pada saat pengaksesan lewat protokol https://, maka data cookie akan di-enkripsi. Sedangkan pada pengaksesan lewat protokol http:// cookie disimpan seperti biasa (tanpa dienkripsi). Jika dalam satu web server, dua protokol tersebut bisa diakses, https:// dan https://, maka aturan di atas tetap berlaku untuk masing-masing protokol, dengan catatan data yang disimpan lewat https:// hanya bisa diakses lewat protokol tersebut.
|
+| HttpOnly | `bool` |
Jika false, maka cookie bisa dibuat lewat back end (Go), maupun lewat front end (javascript)
Jika true, maka cookie hanya bisa diciptakan dari back end
|
+
+---
+
+ - [Gubrak v2](https://github.com/novalagung/gubrak), by Noval Agung, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 87129e1ff54f743e336bd78f652a4d4d189dcc2e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:04 +0700
Subject: [PATCH 070/522] New translations b-custom-mux-multiplexer.md
(English)
---
en/content-en/B-custom-mux-multiplexer.md | 91 +++++++++++++++++++++++
1 file changed, 91 insertions(+)
create mode 100644 en/content-en/B-custom-mux-multiplexer.md
diff --git a/en/content-en/B-custom-mux-multiplexer.md b/en/content-en/B-custom-mux-multiplexer.md
new file mode 100644
index 000000000..dec0f2e46
--- /dev/null
+++ b/en/content-en/B-custom-mux-multiplexer.md
@@ -0,0 +1,91 @@
+# B.20. Custom Multiplexer
+
+Pada chapter ini, kita akan belajar membuat custom multiplexer sendiri, lalu memanfaatkannya untuk mempermudah manajemen middleware.
+
+Silakan salin project sebelumnya, chapter [B.19. Middleware http.Handler](/B-middleware-using-http-handler.html), ke folder baru untuk keperluan pembelajaran.
+
+## B.20.1. Pembuatan Custom Mux
+
+Pada chapter sebelumnya, default mux milik Go digunakan untuk routing dan implementasi middleware. Kali ini default mux tersebut tidak digunakan, kita akan buat mux baru.
+
+Namun pembuatan mux baru tidaklah cukup, karena *naturally* mux baru tersebut tidak akan ada beda dengan default mux. Oleh karena itu agar lebih berguna, kita akan buat tipe mux baru, meng-embed `http.ServeMux` ke dalamnya, lalu membuat beberapa hal dalam struct tersebut.
+
+OK, langsung saja kita praktekan. Ubah isi fungsi main menjadi seperti berikut.
+
+```go
+mux := new(CustomMux)
+
+mux.HandleFunc("/student", ActionStudent)
+
+mux.RegisterMiddleware(MiddlewareAuth)
+mux.RegisterMiddleware(MiddlewareAllowOnlyGet)
+
+server := new(http.Server)
+server.Addr = ":9000"
+server.Handler = mux
+
+fmt.Println("server started at localhost:9000")
+server.ListenAndServe()
+```
+
+Objek `mux` dicetak dari struct `CustomMux` yang jelasnya akan di buat. Struct ini di dalamnya meng-embed `http.ServeMux`.
+
+Registrasi middleware juga diubah, sekarang menggunakan method `.RegisterMiddleware()` milik mux.
+
+Pada file `middleware.go`, siapkan struct `CustomMux`. Selain meng-embed objek mux milik Go, siapkan juga satu variabel bertipe slice-dari-tipe-fungsi-middleware.
+
+```go
+type CustomMux struct {
+ http.ServeMux
+ middlewares []func(next http.Handler) http.Handler
+}
+```
+
+Buat fungsi `RegisterMiddleware()`. Middleware yang didaftarkan ditampung oleh slice `.middlewares`.
+
+```go
+func (c *CustomMux) RegisterMiddleware(next func(next http.Handler) http.Handler) {
+ c.middlewares = append(c.middlewares, next)
+}
+```
+
+Lalu buat method `ServeHTTP`. Method ini diperlukan dalam custom mux agar memenuhi kriteria interface `http.Handler`.
+
+```go
+func (c *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ var current http.Handler = &c.ServeMux
+
+ for _, next := range c.middlewares {
+ current = next(current)
+ }
+
+ current.ServeHTTP(w, r)
+}
+```
+
+Method `ServeHTTP()` milik mux adalah method yang pasti dipanggil pada web server, di setiap request yang masuk.
+
+Dengan perubahan di atas, setiap kali ada request masuk pasti akan melewati middleware-middleware terlebih dahulu secara berurutan. Jika lolos middleware ke-1, lanjut ke-2; jika lolos middleware ke-2, lanjut ke-3; dan seterusnya.
+
+## B.20.2. Testing
+
+Jalankan aplikasi.
+
+
+
+Lalu test menggunakan `curl`, hasilnya adalah sama dengan pada chapter sebelumnya.
+
+
+
+Jika ada keperluan untuk menambahkan middleware baru lainnya, cukup registrasikan lewat `.RegisterMiddleware()`. Source code menjadi lebih rapi dan nyaman untuk dilihat.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 43de9f91b5079b55e0ae8709559939790cc283c2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:05 +0700
Subject: [PATCH 071/522] New translations b-download-file.md (English)
---
en/content-en/B-download-file.md | 229 +++++++++++++++++++++++++++++++
1 file changed, 229 insertions(+)
create mode 100644 en/content-en/B-download-file.md
diff --git a/en/content-en/B-download-file.md b/en/content-en/B-download-file.md
new file mode 100644
index 000000000..c6e19629d
--- /dev/null
+++ b/en/content-en/B-download-file.md
@@ -0,0 +1,229 @@
+# B.17. Download File
+
+Setelah sebelumnya belajar cara untuk handle upload file, kali ini kita akan belajar bagaimana cara membuat handler yang hasilnya adalah download file.
+
+Sebenarnya download file bisa dengan mudah di-implementasikan menggunakan teknik routing static file, langsung akses url dari public assets di browser. Namun outcome dari teknik ini sangat tergantung pada browser. Tiap browser memiliki behaviour berbeda, ada yang file tidak di-download melainkan dibuka di tab, ada yang ter-download.
+
+Dengan menggunakan teknik berikut, file pasti akan ter-download.
+
+## B.17.1. Struktur Folder Proyek
+
+OK, pertama siapkan terlebih dahulu proyek dengan struktur seperti gambar berikut.
+
+
+
+File yang berada di folder `files` adalah dummy, jadi anda bisa gunakan file apapun dengan jumlah berapapun untuk keperluan belajar.
+
+## B.17.2. Front End
+
+Kali ini di bagian front end kita tidak menggunakan jQuery, cukup javascript saja tanpa library.
+
+Pertama siapkan dahulu template nya, isi file `view.html` dengan kode berikut.
+
+```html
+
+
+
+ Download file
+
+
+
+
+
+
+```
+
+Tag `
` nantinya akan berisikan list semua file yang ada dalam folder `files`. Data list file didapat dari back end. Diperlukan sebuah AJAX untuk pengambilan data tersebut.
+
+Siapkan sebuah fungsi dengan nama `Yo` atau bisa lainnya, fungsi ini berisikan closure `renderData()`, `getAllListFiles()`, dan method `init()`. Buat instance object baru dari `Yo`, lalu akses method `init()`, tempatkan dalam event `window.onload`.
+
+```js
+function Yo() {
+ var self = this;
+ var $ul = document.getElementById("list-files");
+
+ var renderData = function (res) {
+ // do stuff
+ };
+
+ var getAllListFiles = function () {
+ // do stuff
+ };
+
+ self.init = function () {
+ getAllListFiles();
+ };
+};
+
+window.onload = function () {
+ new Yo().init();
+};
+```
+
+Closure `renderData()` bertugas untuk melakukan rendering data JSON ke HTML. Berikut adalah isi dari fungsi ini.
+
+```js
+var renderData = function (res) {
+ res.forEach(function (each) {
+ var $li = document.createElement("li");
+ var $a = document.createElement("a");
+
+ $li.innerText = "download ";
+ $li.appendChild($a);
+ $ul.appendChild($li);
+
+ $a.href = "/download?path=" + encodeURI(each.path);
+ $a.innerText = each.filename;
+ $a.target = "_blank";
+ });
+};
+```
+
+Sedangkan closure `getAllListFiles()`, memiliki tugas untuk request ke back end, mengambil data list semua file. Request dilakukan dalam bentuk AJAX, nilai baliknya adalah data JSON. Setelah data sudah di tangan, fungsi `renderData()` dipanggil.
+
+```js
+var getAllListFiles = function () {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "/list-files");
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState == 4 && xhr.status == 200) {
+ var json = JSON.parse(xhr.responseText);
+ renderData(json);
+ }
+ };
+ xhr.send();
+};
+```
+
+## B.17.3. Back End
+
+Pindah ke bagian back end. Siapkan beberapa hal pada `main.go`, import package, siapkan fungsi main, dan buat beberapa rute.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+import "html/template"
+import "path/filepath"
+import "io"
+import "encoding/json"
+import "os"
+
+type M map[string]interface{}
+
+func main() {
+ http.HandleFunc("/", handleIndex)
+ http.HandleFunc("/list-files", handleListFiles)
+ http.HandleFunc("/download", handleDownload)
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Buat handler untuk rute `/`.
+
+```go
+func handleIndex(w http.ResponseWriter, r *http.Request) {
+ tmpl := template.Must(template.ParseFiles("view.html"))
+ if err := tmpl.Execute(w, nil); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+```
+
+
+Lalu siapkan juga route handler `/list-files`. Isi dari handler ini adalah membaca semua file yang ada pada folder `files` untuk kemudian dikembalikan sebagai output berupa JSON. Endpoint ini akan diakses oleh AJAX dari front end.
+
+```go
+func handleListFiles(w http.ResponseWriter, r *http.Request) {
+ files := []M{}
+ basePath, _ := os.Getwd()
+ filesLocation := filepath.Join(basePath, "files")
+
+ err := filepath.Walk(filesLocation, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if info.IsDir() {
+ return nil
+ }
+
+ files = append(files, M{"filename": info.Name(), "path": path})
+ return nil
+ })
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ res, err := json.Marshal(files)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(res)
+}
+```
+
+Fungsi `os.Getwd()` mengembalikan informasi absolute path di mana aplikasi di-eksekusi. Path tersebut kemudian di gabung dengan folder bernama `files` lewat fungsi `filepath.Join`.
+
+> Fungsi `filepath.Join` akan menggabungkan item-item dengan path separator sesuai dengan sistem operasi di mana program dijalankan. `\` untuk Windows dan `/` untuk Linux/Unix.
+
+Fungsi `filepath.Walk` berguna untuk membaca isi dari sebuah direktori, apa yang ada di dalamnya (file maupun folder) akan di-loop. Dengan memanfaatkan callback parameter kedua fungsi ini (yang bertipe `filepath.WalkFunc`), kita bisa mengamil informasi tiap item satu-per satu.
+
+Selanjutnya siapkan handler untuk `/download`. Implementasi teknik download pada dasarnya sama pada semua bahasa pemrograman, yaitu dengan memainkan header **Content-Disposition** pada response.
+
+```go
+func handleDownload(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ path := r.FormValue("path")
+ f, err := os.Open(path)
+ if f != nil {
+ defer f.Close()
+ }
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ contentDisposition := fmt.Sprintf("attachment; filename=%s", f.Name())
+ w.Header().Set("Content-Disposition", contentDisposition)
+
+ if _, err := io.Copy(w, f); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+```
+
+**Content-Disposition** adalah salah satu ekstensi MIME protocol, berguna untuk menginformasikan browser bagaimana dia harus berinteraksi dengan output. Ada banyak jenis value content-disposition, salah satunya adalah `attachment`. Pada kode di atas, header `Content-Disposition: attachment; filename=filename.json` menghasilkan output response berupa attachment atau file, yang kemudian akan di-download oleh browser.
+
+Objek file yang direpresentasikan variabel `f`, isinya di-copy ke objek response lewat statement `io.Copy(w, f)`.
+
+## B.17.4. Testing
+
+Jalankan program, akses rute `/`. List semua file dalam folder `files` muncul di sana. Klik salah satu file untuk men-download-nya.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From a85ce653e25a121ea3639702c09c4c0c469b82d0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:06 +0700
Subject: [PATCH 072/522] New translations b-form-upload-file.md (English)
---
en/content-en/B-form-upload-file.md | 181 ++++++++++++++++++++++++++++
1 file changed, 181 insertions(+)
create mode 100644 en/content-en/B-form-upload-file.md
diff --git a/en/content-en/B-form-upload-file.md b/en/content-en/B-form-upload-file.md
new file mode 100644
index 000000000..c58b8b770
--- /dev/null
+++ b/en/content-en/B-form-upload-file.md
@@ -0,0 +1,181 @@
+# B.13. Form Upload File
+
+Pada bagian ini kita akan belajar bagaimana cara meng-handle upload file lewat form. Di beberapa bagian caranya mirip seperti pada chapter sebelumnya, hanya perlu ditambahkan proses untuk handling file yang di-upload. File tersebut disimpan ke dalam path/folder tertentu.
+
+## B.13.1. Struktur Folder Proyek
+
+Sebelum mulai masuk ke bagian koding, siapkan terlebih dahulu file dan folder dengan struktur seperti gambar berikut.
+
+
+
+Program sederhana yang akan kita buat, memiliki satu form dengan 2 inputan, alias dan file. Data file nantinya disimpan pada folder `files` yang telah dibuat, dengan nama sesuai nama file aslinya. Kecuali ketika user mengisi inputan alias, maka nama tersebut yang akan digunakan sebagai nama file tersimpan.
+
+## B.13.2. Front End
+
+Di bagian front end, isi file `view.html` dengan kode berikut. Template file ini nantinya yang dimunculkan sebagai landing page.
+
+```html
+
+
+
+ Input Message
+
+
+
+
+
+```
+
+Perlu diperhatikan, pada tag `
+```
+
+Test output yang dihasilkan di browser dengan cukup me-refresg halaman. Tulisan `
Hello
` akan di-escape, dimunculkan sebagai text.
+
+
+
+Bisa dilihat bahwa cara untuk menggunakan fungsi pada file template, adalah cukup dengan menuliskan nama fungsinya dalam notasi `{{namaFungsi}}`. Jika fungsi tersebut membutuhkan parameter (seperti fungsi `html`), maka parameternya dituliskan tepat setelah nama fungsi dengan pembatas spasi.
+
+```html
+{{namaFungsi param1 param2 param3 param4}}
+```
+
+Selain fungsi `html`, ada juga beberapa fungsi lain yang sudah disediakan oleh Go.
+
+ - Fungsi `js` digunakan untuk meng-escape string **javascript**
+ - Fungsi `urlquery` digunakan untuk meng-escape string url query
+
+## B.7.3. Fungsi Operator Perbandingan
+
+Pada chapter sebelumnya telah dibahas bagaimana penggunaan operator `ne` pada actions `if`. `eq` dan `ne` adalah contoh dari fungsi operator perbandingan. Jika digunakan pada seleksi kondisi yang nilai kondisinya bertipe `bool`, maka cukup dengan menuliskannya seletah operator, contohnya.
+
+```html
+{{if eq true}}
+ benar
+{{end}}
+```
+
+> Nilai kondisi yang bertipe bool hanya bisa digunakan pada `eq` dan `ne` saja
+
+Jika nilai kondisinya merupakan perbandingan, maka nilai yang dibandingkan harus dituliskan, sebagai contoh di bawah ini adalah seleksi kondisi memanfaatkan operator `gt` untuk deteksi apakah nilai di atas 60.
+
+```html
+{{if gt $value 60}}
+ lulus
+{{end}}
+```
+
+Pada kode di atas, nilai variabel `$value` akan dibandingkan dengan angka `60`, apakah nilainya lebih besar atau tidak.
+
+> `gt` merupakan kependekan dari **greater than**
+
+Praktekan kode berikut, tulis ke dalam file `view.html`.
+
+```html
+{{if eq .Name "Bruce Wayne"}}
+
I'm the Batman!
+{{else if ne .Name "Clark Kent"}}
+
I'm neither Batman or Superman
+{{end}}
+```
+
+Lihat hasilnya pada browser.
+
+
+
+Berikut merupakan daftar operator perbandingan yang didukung oleh template view.
+
+| Operator | Penjelasan | Analogi |
+|:--------:| ----------------------------------------------------- |:-----------:|
+| eq | *equal*, sama dengan | `a == b` |
+| ne | *not equal*, tidak sama dengan | `a != b` |
+| lt | *lower than*, lebih kecil | `a < b` |
+| le | *lower than or equal*, lebih kecil atau sama dengan | `a <= b` |
+| gt | *greater than*, lebih besar | `a > b` |
+| ge | *greater than or equal*, lebih besar atau sama dengan | `a >= b` |
+
+## B.7.4. Pemanggilan Method
+
+Cara memanggil method yang disisipkan ke view sama dengan cara pemanggilan fungsi, hanya saja perlu ditambahkan tanda titik `.` (menyesuaikan scope variabelnya). Contohnya bisa dilihat seperti pada kode berikut.
+
+```html
+
+ {{.SayHello "Gotham citizen" "You are our hero!"}}
+
+```
+
+Test hasilnya pada browser.
+
+
+
+## B.7.5. Fungsi String
+
+Go juga menyediakan beberapa fungsi string yang bisa dimanfaatkan, yaitu:
+
+ - `print` (merupakan alias dari `fmt.Sprint`)
+ - `printf` (merupakan alias dari `fmt.Sprintf`)
+ - `println` (merupakan alias dari `fmt.Sprintln`)
+
+Cara penggunannya juga masih sama.
+
+```html
+
+```
+
+Output:
+
+
+
+Jika merasa sedikit bingung memahami statement di atas, mungkin analogi berikut cukup membantu.
+
+```go
+// template view
+printf "%s because I'm %s" "You know why?" "Batman!"
+
+// go
+fmt.Sprintf("%s because I'm %s", "You know why?", "Batman!")
+```
+
+Kedua statement di atas menghasilkan output yang sama.
+
+## B.7.6. Fungsi `len` dan `index`
+
+Kegunaan dari fungsi `len` seperti yang sudah diketahui adalah untuk menghitung jumlah elemen. Sedangkan fungsi `index` digunakan jika elemen tertentu ingin diakses.
+
+Sebagai contoh, `Friends` yang merupakan array, diakses elemen indeks ke-1 menggunakan `index`, maka caranya:
+
+```html
+{{index .Friends 1}}
+```
+
+Berikut merupakan contoh penerapan fungsi `len` dan `index`.
+
+```html
+
+ Batman have many friends. {{len .Friends}} of them are:
+ {{index .Friends 0}},
+ {{index .Friends 1}}, and
+ {{index .Friends 2}}
+
+```
+
+Output:
+
+
+
+## B.7.7. Fungsi Operator Logika
+
+Selain fungsi operator perbandingan, terdapat juga operator logika `or`, `and`, dan `not`. Cara penggunaannya adalah dengan dituliskan setelah actions `if` atau `elseif`, sebagai fungsi dengan parameter adalah nilai yang ingin dibandingkan.
+
+> Fungsi `not` ekuivalen dengan `ne`
+
+```html
+{{$cond1 := true}}
+{{$cond2 := false}}
+
+{{if or $cond1 $cond2}}
+
+
+---
+
+
From 455fd981669d59868ab4b3caf3728723117704ee Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:18 +0700
Subject: [PATCH 087/522] New translations b-template-render-html.md (English)
---
en/content-en/B-template-render-html.md | 148 ++++++++++++++++++++++++
1 file changed, 148 insertions(+)
create mode 100644 en/content-en/B-template-render-html.md
diff --git a/en/content-en/B-template-render-html.md b/en/content-en/B-template-render-html.md
new file mode 100644
index 000000000..f3b9e6929
--- /dev/null
+++ b/en/content-en/B-template-render-html.md
@@ -0,0 +1,148 @@
+# B.4. Template: Render HTML Template
+
+Pada bagian ini kita akan belajar bagaimana cara render file **template** ber-tipe **HTML**, untuk ditampilkan pada browser.
+
+Terdapat banyak jenis template pada Go, yang akan kita pakai adalah template HTML. Package `html/template` berisi banyak sekali fungsi untuk kebutuhan rendering dan parsing file template jenis ini.
+
+## B.4.1. Struktur Aplikasi
+
+Buat project baru, siapkan file dan folder dengan struktur sesuai dengan gambar berikut.
+
+
+
+## B.4.2. Back End
+
+Hal pertama yang perlu dilakukan adalah mempersiapkan back end. Buka file `main.go`, import package `net/http`, `html/template`, dan `path`. Siapkan juga rute `/`.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+import "html/template"
+import "path"
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ // not yet implemented
+ })
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Handler rute `/` akan kita isi dengan proses untuk rendering template html untuk ditampilkan ke layar browser. Beberapa data disisipkan dalam proses rendering template.
+
+Silakan tulis kode berikut di dalam handler rute `/`.
+
+```go
+var filepath = path.Join("views", "index.html")
+var tmpl, err = template.ParseFiles(filepath)
+if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+}
+
+var data = map[string]interface{}{
+ "title": "Learning Golang Web",
+ "name": "Batman",
+}
+
+err = tmpl.Execute(w, data)
+if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+}
+```
+
+Package `path` berisikan banyak fungsi yang berhubungan dengan lokasi folder atau path, yang salah satu di antaranya adalah fungsi `path.Join()`. Fungsi ini digunakan untuk menggabungkan folder atau file atau keduanya menjadi sebuah path, dengan separator relatif terhadap OS yang digunakan.
+
+> Separator yang digunakan oleh `path.Join()` adalah `\` untuk wind\*ws dan `/` untuk un\*x.
+
+Contoh penerapan `path.Join()` bisa dilihat di kode di atas, `views` di-join dengan `index.html`, menghasilkan `views/index.html`.
+
+Sedangkan `template.ParseFiles()`, digunakan untuk parsing file template, dalam contoh ini file `view/index.html`. Fungsi ini mengembalikan 2 data, yaitu hasil dari proses parsing yang bertipe `*template.Template`, dan informasi `error` jika ada.
+
+Fungsi `http.Error()` digunakan untuk menandai response (`http.ResponseWriter`) bahwa terjadi error, dengan kode error dan pesan error bisa ditentukan. Pada contoh di atas yang digunakan adalah **500 - internal server error** yang direpresentasikan oleh variabel `http.StatusInternalServerError`.
+
+Method `Execute()` milik `*template.Template`, digunakan untuk menyisipkan data pada template, untuk kemudian ditampilkan ke browser. Data bisa disipkan dalam bentuk `struct`, `map`, atau `interface{}`.
+
+ - Jika dituliskan dalam bentuk `map`, maka **key** akan menjadi nama variabel dan **value** menjadi nilainya
+ - Jika dituliskan dalam bentuk variabel objek cetakan `struct`, nama **property** akan menjadi nama variabel
+
+Pada contoh di atas, data map yang berisikan key `title` dan `name` disisipkan ke dalam template yang sudah di parsing.
+
+## B.4.3. Front End
+
+OK, back end sudah siap, selanjutnya kita masuk ke bagian user interface. Pada file `views/index.html`, tuliskan kode html sederhana berikut.
+
+```html
+
+
+
+ {{.title}}
+
+
+
Welcome {{.name}}
+
+
+```
+
+Untuk menampilkan variabel yang disisipkan ke dalam template, gunakan notasi `{{.namaVariabel}}`. Pada contoh di atas, data `title` dan `name` yang dikirim dari back end ditampilkan.
+
+Tanda titik "\." pada \{\{\.namaVariabel\}\} menerangkan bahwa variabel tersebut diakses dari **current scope**. Dan current scope default adalah data `map` atau objek yang dilempar back end.
+
+## B.4.4. Testing
+
+Semua sudah siap, maka jalankan program lalu lakukan testing via browser.
+
+
+
+## B.4.5. Static File CSS
+
+Kita akan coba tambahkan sebuah stylesheet di sini. Langsung saja, buat file statis `assets/site.css`, isi dengan kode berikut.
+
+```css
+body {
+ font-family: "Helvetica Neue";
+ font-weight: bold;
+ font-size: 24px;
+ color: #07c;
+}
+```
+
+Pada `views/index.html`, include-kan file css.
+
+```html
+
+```
+
+Terakhir pada fungsi `main()`, tambahkan router untuk handling file statis.
+
+```go
+func main() {
+ // ...
+
+ http.Handle("/static/",
+ http.StripPrefix("/static/",
+ http.FileServer(http.Dir("assets"))))
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Jalankan aplikasi untuk test hasil.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 3ad46e7f0ba90d00c3e77f41f48c700250f616f9 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:19 +0700
Subject: [PATCH 088/522] New translations b-template-render-partial-html.md
(English)
---
.../B-template-render-partial-html.md | 238 ++++++++++++++++++
1 file changed, 238 insertions(+)
create mode 100644 en/content-en/B-template-render-partial-html.md
diff --git a/en/content-en/B-template-render-partial-html.md b/en/content-en/B-template-render-partial-html.md
new file mode 100644
index 000000000..6bba2d5ba
--- /dev/null
+++ b/en/content-en/B-template-render-partial-html.md
@@ -0,0 +1,238 @@
+# B.5. Template: Render Partial HTML Template
+
+Satu buah halaman yang berisikan html, bisa terbentuk dari banyak template html (parsial). Pada chapter ini kita akan belajar bagaimana membuat, mem-parsing, dan me-render semua file tersebut.
+
+Ada beberapa metode yang bisa digunakan, dari ke semuanya akan kita bahas 2 di antaranya, yaitu:
+
+ - Menggunakan fungsi `template.ParseGlob()`.
+ - Menggunakan fungsi `template.ParseFiles()`.
+
+## B.5.1. Struktur Aplikasi
+
+Mari langsung kita praktekan. Buat project baru, siapkan file dan folder dengan susunan seperti dengan gambar berikut.
+
+
+
+## B.5.2. Back End
+
+Buka `main.go`, isi dengan kode berikut.
+
+```go
+package main
+
+import (
+ "net/http"
+ "html/template"
+ "fmt"
+)
+
+type M map[string]interface{}
+
+func main() {
+ var tmpl, err = template.ParseGlob("views/*")
+ if err != nil {
+ panic(err.Error())
+ return
+ }
+}
+```
+
+Tipe `M` merupakan alias dari `map[string]interface{}`, disiapkan untuk mempersingkat penulisan tipe map tersebut. Pada pembahasan-pembahasan selanjutnya kita akan banyak menggunakan tipe ini.
+
+Pada kode di atas, di dalam fungsi `main()`, fungsi `template.ParseGlob()` dipanggil, dengan parameter adalah pattern path `"views/*"`. Fungsi ini digunakan untuk memparsing semua file yang match dengan pattern yang ditentukan, dan fungsi ini mengembalikan 2 objek: `*template.Template` & `error`.
+
+> Pattern path pada fungsi `template.ParseGlob()` nantinya akan di proses oleh `filepath.Glob()`
+
+Proses parsing semua file html dalam folder `views` dilakukan di awal, agar ketika mengakses rute-tertentu-yang-menampilkan-html, tidak terjadi proses parsing lagi.
+
+> Parsing semua file menggunakan `template.ParseGlob()` yang dilakukan di luar handler, tidak direkomendasikan dalam fase development. Karena akan mempersulit testing html. Lebih detailnya akan dibahas di bagian bawah.
+
+Selanjutnya, masih di dalam fungsi `main()`, siapkan 2 buah rute.
+
+```go
+http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
+ var data = M{"name": "Batman"}
+ err = tmpl.ExecuteTemplate(w, "index", data)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+})
+
+http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
+ var data = M{"name": "Batman"}
+ err = tmpl.ExecuteTemplate(w, "about", data)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+})
+
+fmt.Println("server started at localhost:9000")
+http.ListenAndServe(":9000", nil)
+```
+
+Kedua rute tersebut sama, pembedanya adalah template yang di-render. Rute `/index` me-render template bernama `index`, dan rute `/about` me-render template bernama `about`.
+
+Karena semua file html sudah diparsing di awal, maka untuk render template tertentu cukup dengan memanggil method `ExecuteTemplate()`, dengan menyisipkan 3 parameter berikut:
+
+ 1. Parameter ke-1, objek `http.ResponseWriter`
+ 2. Parameter ke-2, nama template
+ 3. Parameter ke-3, data
+
+> Nama template bukanlah nama file. Setelah masuk ke bagian front-end, akan diketahui apa yang dimaksud dengan nama template.
+
+## B.5.3. Front End
+
+## B.5.3.1. Template `index.html`
+
+OK, sekarang waktunya untuk mulai menyiapkan template view. Ada 4 buah template yang harus kita siapkan satu per satu.
+
+Buka file `index.html`, lalu tulis kode berikut.
+
+```html
+{{define "index"}}
+
+
+
+ {{template "_header"}}
+
+
+ {{template "_message"}}
+
Page: Index
+
Welcome {{.name}}
+
+
+{{end}}
+```
+
+Pada kode di atas terlihat bahwa ada beberapa kode yang ditulis dengan notasinya `{{ }}`. Berikut adalah penjelasannya.
+
+ - Statement \{\{define "index"\}\}, digunakan untuk mendefinisikan nama template. Semua blok kode setelah statement tersebut (batasnya adalah hingga statement \{\{end\}\}) adalah milik template dengan nama `index`. keyword `define` digunakan dalam penentuan nama template.
+ - Statement \{\{template "_header"\}\} artinya adalah template bernama `_header` di-include ke bagian itu. keyword `template` digunakan untuk include template lain.
+ - Statement \{\{template "_message"\}\}, sama seperti sebelumnya, template bernama `_message` akan di-include.
+ - Statement \{\{.name\}\} akan memunculkan data, `name`, yang data ini sudah disisipkan oleh back end pada saat rendering.
+ - Statement \{\{end\}\} adalah penanda batas akhir pendefinisian template.
+
+## B.5.3.2. Template `about.html`
+
+Template ke-2, `about.html` diisi dengan dengan kode yang sama seperti pada `index.html`, hanya berbeda di bagian nama template dan beberapa text.
+
+```html
+{{define "about"}}
+
+
+
+ {{template "_header"}}
+
+
+ {{template "_message"}}
+
Page: About
+
Welcome {{.name}}
+
+
+{{end}}
+```
+
+## B.5.3.3. Template `_header.html`
+
+Buka file `_header.html`, definisikan template bernama `_header` dengan isi adalah judul halaman.
+
+```html
+{{define "_header"}}
+Learn Golang Template
+{{end}}
+```
+
+> Nama file bisa ditulis dengan diawali karakter underscore atau `_`. Pada chapter ini, nama file yang diawali `_` kita asumsikan sebagai template parsial, template yang nantinya di-include-kan ke template utama.
+
+## B.5.3.4. Template `_message.html`
+
+Definisikan juga template `_message` pada file `_message.html` dengan isi sebuah text.
+
+```html
+{{define "_message"}}
+
Welcome
+{{end}}
+```
+
+## B.5.5. Run & Test
+
+Jalankan aplikasi, test via browser.
+
+
+
+Bisa dilihat pada gambar di atas, ketika rute `/index` dan `/about` di akses, konten yang keluar adalah berbeda, sesuai dengan template yang di-render di masing-masing rute.
+
+## B.5.6. Parsing Banyak File HTML Menggunakan `template.ParseFiles()`
+
+Metode parsing menggunakan `template.ParseGlob()` memiliki kekurangan yaitu sangat tergantung terhadap pattern path yang digunakan. Jika dalam suatu proyek terdapat sangat banyak file html dan folder, sedangkan hanya beberapa yang digunakan, pemilihan pattern path yang kurang tepat akan menjadikan file lain ikut ter-parsing dengan sia-sia.
+
+Dan juga, karena statement `template.ParseGlob()` dieksekusi diluar handler, maka ketika ada perubahan pada salah satu view, lalu halaman di refresh, output yang dihasilkan akan tetap sama. Solusi dari masalah ini adalah dengan memanggil `template.ParseGlob()` di tiap handler rute-rute yang diregistrasikan.
+
+> Best practices yang bisa diterapkan, ketika environment adalah production, maka tempatkan `template.ParseGlob()` di luar (sebelum) handler. Sedangkan pada environment development, taruh `template.ParseGlob()` di dalam masing-masing handler. Gunakan seleksi kondisi untuk mengakomodir skenario ini.
+
+Alternatif metode lain yang bisa digunakan, yang lebih efisien, adalah dengan memanfaatkan fungsi `template.ParseFiles()`. Fungsi ini selain bisa digunakan untuk parsing satu buah file saja (seperti yang sudah dicontohkan pada chapter sebelumnya), bisa digunakan untuk parsing banyak file.
+
+Mari kita praktekan. Ubah handler rute `/index` dan `/about`. Gunakan `template.ParseFiles()` dengan isi parameter (variadic) adalah path dari file-file html yang akan dipergunakan di masing-masing rute. Lalu hapus statement `template.ParseGlob()`
+
+ - Rute `/index` dan handlernya.
+
+ ```go
+ http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
+ var data = M{"name": "Batman"}
+ var tmpl = template.Must(template.ParseFiles(
+ "views/index.html",
+ "views/_header.html",
+ "views/_message.html",
+ ))
+
+ var err = tmpl.ExecuteTemplate(w, "index", data)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ })
+ ```
+
+ - Rute `/about` dan handlernya.
+
+ ```go
+ http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
+ var data = M{"name": "Batman"}
+ var tmpl = template.Must(template.ParseFiles(
+ "views/about.html",
+ "views/_header.html",
+ "views/_message.html",
+ ))
+
+ var err = tmpl.ExecuteTemplate(w, "about", data)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ })
+ ```
+
+ - **Hapus** statement `template.ParseGlob()`.
+
+ ```go
+ var tmpl, err = template.ParseGlob("views/*")
+ if err != nil {
+ panic(err.Error())
+ return
+ }
+ ```
+
+Rute `/index` memakai view `_header.html`, `_message.html`, dan `index.html`; sedangkan rute `/about` tidak memakai `index.html`, melainkan menggunakan `about.html`.
+
+Wrap fungsi `template.ParseFiles()` dalam `template.Must()`. Fungsi ini berguna untuk deteksi error pada saat membuat instance `*template.Template` baru atau ketika sedang mengolahnya. Ketika ada error, `panic` dimunculkan.
+
+Jalankan aplikasi untuk test hasilnya.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From c4be4c2ec0e855bf0ed1d8c8950ff7e60ab69429 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:20 +0700
Subject: [PATCH 089/522] New translations c-advanced-configuration-viper.md
(English)
---
.../C-advanced-configuration-viper.md | 154 ++++++++++++++++++
1 file changed, 154 insertions(+)
create mode 100644 en/content-en/C-advanced-configuration-viper.md
diff --git a/en/content-en/C-advanced-configuration-viper.md b/en/content-en/C-advanced-configuration-viper.md
new file mode 100644
index 000000000..1c5d4a012
--- /dev/null
+++ b/en/content-en/C-advanced-configuration-viper.md
@@ -0,0 +1,154 @@
+# C.10. Advanced Configuration Menggunakan Viper
+
+Pada chapter ini kita akan belajar cara mudah manajemen konfigurasi file JSON menggunakan [Viper](http://github.com/spf13/viper) library. Inti dari chapter ini sebenarnya adalah sama dengan yang sudah dibahas pada [B.22. Simple Configuration](/B-simple-configuration.html), hanya saja di sini proses parsing di-handle oleh 3rd party dengan tidak menggunakan struct untuk pengaksesannya.
+
+Kekurangan dari teknik menyimpan konfigurasi dalam object struct adalah, pada saat ada kebutuhan untuk menambah atau merubah isi konfigurasi file, maka mengharuskan developer juga mengubah skema struct penampung. Pada bagian ini, pengaksesan property konfigurasi dilakukan lewat notasi string konfigurasinya.
+
+## C.10.1. JSON Configuration
+
+Mari langsung kita praktekan. Buat project baru seperti biasa, buat file konfigurasi `app.config.json`, isi dengan data berikut.
+
+```json
+{
+ "appName": "SimpleApp",
+
+ "server": {
+ "port": 9000
+ }
+}
+```
+
+Property `appName` berisi nama aplikasi, sedangkan `server.port` representasi dari port web server.
+
+Selanjutnya buat `main.go`, lakukan parsing pada file konfigurasi.
+
+```go
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/spf13/viper"
+ "net/http"
+)
+
+func main() {
+ e := echo.New()
+
+ viper.SetConfigType("json")
+ viper.AddConfigPath(".")
+ viper.SetConfigName("app.config")
+
+ err := viper.ReadInConfig()
+ if err != nil {
+ e.Logger.Fatal(err)
+ }
+
+ // ...
+}
+```
+
+Kode di atas adalah contoh penggunaan dasar viper, untuk parsing file konfigurasi bertipe `JSON`. Fungsi `viper.SetConfigType()` digunakan untuk set jenis file konfigurasi.
+
+Berikut merupakan list format yang didukung oleh viper.
+
+ - json
+ - toml
+ - yaml
+ - yml
+ - properties
+ - props
+ - prop
+
+Fungsi `.AddConfigPath()` digunakan untuk mendaftarkan path folder di mana file-file konfigurasi berada. Fungsi ini bisa dipanggil beberapa kali, jika memang ada banyak file konfigurasi tersimpan dalam path berbeda.
+
+Statement `.SetConfigName()` dieksekusi dengan parameter berisi nama file konfigurasi secara eksplisit tanpa ekstensi. Misalkan nama file adalah `app.config.json`, maka parameter cukup ditulis `app.config`.
+
+Fungsi `.ReadInConfig()` digunakan untuk memproses file-file konfigurasi sesuai dengan path dan nama yang sudah ditentukan.
+
+OK, kembali ke bagian tulis-menulis kode. Tambahkan beberapa kode untuk print nama aplikasi, sebuah rute, dan start web server.
+
+```go
+e.GET("/index", func(c echo.Context) (err error) {
+ return c.JSON(http.StatusOK, true)
+})
+
+e.Logger.Print("Starting", viper.GetString("appName"))
+e.Logger.Fatal(e.Start(":" + viper.GetString("server.port")))
+```
+
+Cara pengaksesan konfigurasi bisa dilihat pada kode di atas. Statement `viper.GetString("appName")` mengembalikan string `"SimpleApp"`, sesuai dengan isi pada file konfigurasi.
+
+Selain `.GetString()`, masih banyak lagi fungsi lain yang bisa digunakan, sesuaikan dengan tipe data property yang akan diambil.
+
+| Fungsi | Return type |
+| ------------------------------- | ----------------------- |
+| Get(string) | interface{} |
+| GetBool(string) | bool |
+| GetDuration(string) | time.Duration |
+| GetFloat64(string) | float64 |
+| GetInt(string) | int |
+| GetInt32(string) | int32 |
+| GetInt64(string) | int64 |
+| GetSizeInBytes(string) | uint |
+| GetString(string) | string |
+| GetStringMap(string) | map[string]interface{} |
+| GetStringMapString(string) | map[string]string |
+| GetStringMapStringSlice(string) | map\[string\]\[\]string |
+| GetStringSlice(string) | []string |
+| GetTime(string) | time.Time |
+
+Pengaksesan property nested seperti `server.port` juga mudah, tinggal tulis saja skema property yang ingin diambil nilainya dengan separator tanda titik (`.`).
+
+Jalankan aplikasi untuk test hasilnya.
+
+
+
+## C.10.2. YAML Configuration
+
+Cara penerapan viper pada file konfigurasi bertipe `.yaml` kurang lebih sama seperti pada file `.json`. Cukup ubah config type nya dan semua akan beres dengan sendirinya.
+
+Mari kita langsung praktekan saja. Buat file konfigurasi baru `app.config.yaml` dengan isi berikut.
+
+```yaml
+appName: SimpleApp
+server:
+ port: 9000
+```
+
+Pada bagian kode golang, cukup ubah argumen pemanggilan fungsi set config type.
+
+```go
+viper.SetConfigType("yaml")
+```
+
+Jalankan aplikasi, dan hasilnya sama seperti sebelumnya.
+
+## C.10.3. Watcher Configuration
+
+Viper memiliki banyak fitur, satu di antaranya adalah mengaktifkan watcher pada file konfigurasi. Dengan adanya watcher, maka kita bisa membuat callback yang akan dipanggil setiap kali ada perubahan konfigurasi.
+
+```go
+viper.WatchConfig()
+viper.OnConfigChange(func(e fsnotify.Event) {
+ fmt.Println("Config file changed:", e.Name)
+})
+```
+
+Penggunaan fasilitas watcher memerlukan tambahan 3rd party library [fsnotify](https://github.com/fsnotify/fsnotify), jadi jangan lupa juga untuk meng-*import*-nya.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [fsnotify](https://github.com/fsnotify/fsnotify), by fsnotify team, BSD-3-Clause license
+ - [Viper](https://github.com/spf13/viper), by Steve Francia, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 7fa7343c686b5b61a83bbb59edaf8bf3ef7dc10e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:21 +0700
Subject: [PATCH 090/522] New translations c-advanced-middleware-and-logging.md
(English)
---
.../C-advanced-middleware-and-logging.md | 280 ++++++++++++++++++
1 file changed, 280 insertions(+)
create mode 100644 en/content-en/C-advanced-middleware-and-logging.md
diff --git a/en/content-en/C-advanced-middleware-and-logging.md b/en/content-en/C-advanced-middleware-and-logging.md
new file mode 100644
index 000000000..2fbd6c6ed
--- /dev/null
+++ b/en/content-en/C-advanced-middleware-and-logging.md
@@ -0,0 +1,280 @@
+# C.8. Advanced Middleware & Logging (Logrus, Echo Logger)
+
+Middleware adalah sebuah blok kode yang dipanggil sebelum ataupun sesudah http request di-proses. Middleware biasanya dibuat per-fungsi-nya, contohnya: middleware autentikasi, middleware untuk logging, middleware untuk gzip compression, dan lainnya.
+
+Pada chapter ini kita akan belajar cara membuat dan me-manage middleware.
+
+## C.8.1. Custom Middleware
+
+Pembuatan middleware pada echo sangat mudah, cukup gunakan method `.Use()` milik objek echo untuk registrasi middleware. Method ini bisa dipanggil berkali-kali, dan eksekusi middleware-nya sendiri adalah berurutan sesuai dengan urutan registrasi.
+
+OK, langsung saja, buat folder project baru dengan isi sebauh file `main.go` seperti biasanya. Lalu tulis kode berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/labstack/echo"
+ "net/http"
+)
+
+func main() {
+ e := echo.New()
+
+ // middleware here
+
+ e.GET("/index", func(c echo.Context) (err error) {
+ fmt.Println("threeeeee!")
+
+ return c.JSON(http.StatusOK, true)
+ })
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Kode di atas merupakan aplikasi web kecil, berisi satu buah rute `/index` yang ketika di akses akan print log `"threeeeee!"` ke console.
+
+Selanjutnya, buat dua middleware, `middlewareOne` dan `middlewareTwo`. Isinya juga menampilkan log.
+
+```go
+func middlewareOne(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ fmt.Println("from middleware one")
+ return next(c)
+ }
+}
+
+func middlewareTwo(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ fmt.Println("from middleware two")
+ return next(c)
+ }
+}
+```
+
+Registrasikan kedua middleware di atas. Kode di bawah ini adalah contoh cara registrasinya.
+
+```go
+func main() {
+ e := echo.New()
+
+ e.Use(middlewareOne)
+ e.Use(middlewareTwo)
+
+ // ...
+```
+
+Jalankan aplikasi, lihat hasilnya.
+
+
+
+## C.8.2. Integrasi Middleware ber-skema Non-Echo-Middleware
+
+Di echo, fungsi middleware harus memiliki skema `func(echo.HandlerFunc)echo.HandlerFunc`. Untuk 3rd party middleware, tetap bisa dikombinasikan dengan echo, namun membutuhkan sedikit penyesuaian tentunya.
+
+Echo menyediakan solusi mudah untuk membantu integrasi 3rd party middleware, yaitu dengan menggunakan fungsi `echo.WrapMiddleware()` untuk mengkonversi middleware menjadi echo-compatible-middleware, dengan syarat skema harus dalam bentuk `func(http.Handler)http.Handler`.
+
+Silakan praktekan kode berikut.
+
+```go
+func middlewareSomething(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("from middleware something")
+ next.ServeHTTP(w, r)
+ })
+}
+
+func main() {
+ e := echo.New()
+
+ e.Use(echo.WrapMiddleware(middlewareSomething))
+
+ // ...
+```
+
+Bisa dilihat, fungsi `middlewareSomething` tidak menggunakan skema middleware milik echo, namun tetap bisa digunakan dalam `.Use()` dengan cara dibungkus fungsi `echo.WrapMiddleware()`.
+
+## C.8.3. Echo Middleware: Logger
+
+Seperti yang sudah penulis jelaskan pada awal chapter B, bahwa echo merupakan framework besar, di dalamnya terdapat banyak dependency dan library, salah satunya adalah logging middleware.
+
+Cara menggunakan logging middleware (ataupun middleware lainnya milik echo) adalah dengan meng-import package `github.com/labstack/echo/middleware`, lalu panggil nama middleware nya. Lebih detailnya silakan baca dokumentasi echo mengenai middleware di https://echo.labstack.com/middleware.
+
+Berikut merupakan contoh penggunaan echo logging middleware.
+
+```go
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+ "net/http"
+)
+
+func main() {
+ e := echo.New()
+
+ e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
+ Format: "method=${method}, uri=${uri}, status=${status}\n",
+ }))
+
+ e.GET("/index", func(c echo.Context) (err error) {
+ return c.JSON(http.StatusOK, true)
+ })
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Cara menggunakan echo logging middleware adalah dengan membuat objek logging baru lewat statement `middleware.Logger()`, lalu membungkusnya dengan `e.Use()`. Atau bisa juga menggunakan `middleware.LoggerWithConfig()` jika logger yang dibuat memerlukan beberapa konfigurasi (tulis konfigurasinya sebagai property objek cetakan `middleware.LoggerConfig`, lalu tempatkan sebagai parameter method pemanggilan `.LoggerWithConfig()`).
+
+Jalankan aplikasi, lalu lihat hasilnya.
+
+
+
+---
+
+Berikut merupakan list middleware yang disediakan oleh echo, atau cek https://echo.labstack.com/middleware untuk lebih detailnya.
+
+- Basic Auth
+- Body Dump
+- Body Limit
+- CORS
+- CSRF
+- Casbin Auth
+- Gzip
+- JWT
+- Key Auth
+- Logger
+- Method Override
+- Proxy
+- Recover
+- Redirect
+- Request ID
+- Rewrite
+- Secure
+- Session
+- Static
+- Trailing Slash
+
+## C.8.4. 3rd Party Logging Middleware: Logrus
+
+Selain dengan membuat middleware sendiri, ataupun menggunakan echo middleware, kita juga bisa menggunakan 3rd party middleware lain. Tinggal sesuaikan sedikit agar sesuai dengan skema fungsi middleware milik echo untuk bisa digunakan.
+
+Next, kita akan coba untuk meng-implementasi salah satu golang library terkenal untuk keperluan logging, yaitu [logrus](https://github.com/sirupsen/logrus).
+
+Buat file baru, import packages yang diperlukan, lalu buat fungsi `makeLogEntry()`, fungsi ini menerima satu parameter bertipe `echo.Context` dan mengembalikan objek logrus `*log.Entry`.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/labstack/echo"
+ log "github.com/sirupsen/logrus"
+ "net/http"
+ "time"
+)
+
+func makeLogEntry(c echo.Context) *log.Entry {
+ if c == nil {
+ return log.WithFields(log.Fields{
+ "at": time.Now().Format("2006-01-02 15:04:05"),
+ })
+ }
+
+ return log.WithFields(log.Fields{
+ "at": time.Now().Format("2006-01-02 15:04:05"),
+ "method": c.Request().Method,
+ "uri": c.Request().URL.String(),
+ "ip": c.Request().RemoteAddr,
+ })
+}
+```
+
+Fungsi `makeLogEntry()` bertugas membuat basis log objek yang akan ditampilkan. Informasi standar seperti waktu, dibentuk di dalam fungsi ini. Khusus untuk log yang berhubungan dengan http request, maka informasi yang lebih detail dimunculkan (http method, url, dan IP).
+
+Selanjutnya, buat fungsi `middlewareLogging()` dan `errorHandler()`.
+
+```go
+func middlewareLogging(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ makeLogEntry(c).Info("incoming request")
+ return next(c)
+ }
+}
+
+func errorHandler(err error, c echo.Context) {
+ report, ok := err.(*echo.HTTPError)
+ if ok {
+ report.Message = fmt.Sprintf("http error %d - %v", report.Code, report.Message)
+ } else {
+ report = echo.NewHTTPError(http.StatusInternalServerError, err.Error())
+ }
+
+ makeLogEntry(c).Error(report.Message)
+ c.HTML(report.Code, report.Message.(string))
+}
+```
+
+Fungsi `middlewareLogging()` bertugas untuk menampilkan log setiap ada http request masuk. Dari objek `*log.Entry` -yang-dicetak-lewat-fungsi-`makeLogEntry()`-, panggil method `Info()` untuk menampilkan pesan log dengan level adalah INFO.
+
+Sedang fungsi `errorHandler` akan digunakan untuk meng-override default http error handler milik echo. Dalam fungsi ini log dengan level ERROR dimunculkan lewat pemanggilan method `Error()` milik `*log.Entry`.
+
+Buat fungsi `main()`, implementasikan semua fungsi tersebut, siapkan yang harus disiapkan.
+
+```go
+func main() {
+ e := echo.New()
+
+ e.Use(middlewareLogging)
+ e.HTTPErrorHandler = errorHandler
+
+ e.GET("/index", func(c echo.Context) error {
+ return c.JSON(http.StatusOK, true)
+ })
+
+ lock := make(chan error)
+ go func(lock chan error) { lock <- e.Start(":9000") }(lock)
+
+ time.Sleep(1 * time.Millisecond)
+ makeLogEntry(nil).Warning("application started without ssl/tls enabled")
+
+ err := <-lock
+ if err != nil {
+ makeLogEntry(nil).Panic("failed to start application")
+ }
+}
+```
+
+Fungsi `main()` di atas berisikan beberapa kode yang jarang kita gunakan, pada saat men-start web server.
+
+Web server di start dalam sebuah goroutine. Karena method `.Start()` milik echo adalah blocking, kita manfaatkan nilai baliknya untuk di kirim ke channel `lock`.
+
+Selanjutnya dengan delay waktu 1 milidetik, log dengan level WARNING dimunculkan. Ini hanya simulasi saja, karena memang aplikasi tidak di start menggunakan ssl/tls. Dengan memberi delay 1 milidetik, maka log WARNING bisa muncul setelah log default dari echo muncul.
+
+Nah pada bagian penerimaan channel, jika nilai baliknya tidak `nil` maka pasti terjadi error pada saat start web server, dan pada saat itu juga munculkan log dengan level PANIC.
+
+OK, sekarang jalankan lalu test aplikasi.
+
+
+
+Satu kata, *cantik*.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [Logrus](https://github.com/sirupsen/logrus), by Simon Eskildsen, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 031da64f82859bded9e5372cf4d59c1e1ed37e96 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:22 +0700
Subject: [PATCH 091/522] New translations
c-best-practice-configuration-env-var.md (English)
---
.../C-best-practice-configuration-env-var.md | 166 ++++++++++++++++++
1 file changed, 166 insertions(+)
create mode 100644 en/content-en/C-best-practice-configuration-env-var.md
diff --git a/en/content-en/C-best-practice-configuration-env-var.md b/en/content-en/C-best-practice-configuration-env-var.md
new file mode 100644
index 000000000..495d8c9c5
--- /dev/null
+++ b/en/content-en/C-best-practice-configuration-env-var.md
@@ -0,0 +1,166 @@
+# C.11. Best Practice Configuration Menggunakan Environment Variable
+
+Pada bagian ini kita akan mempelajari penerapan konfigurasi pada *environment variable*.
+
+## C.11.1. Definisi
+
+*Environment variable* merupakan variabel yang berada di lapisan *runtime* sistem operasi. Karena *env var* atau *environment variable* merupakan variabel seperti pada umumnya, maka kita bisa melakukan operasi seperti mengubah nilainya atau mengambil nilainya.
+
+Salah satu *env var* yang mungkin sering temen-temen temui adalah `PATH`. `PATH` sendiri merupakan variabel yang digunakan oleh sistem operasi untuk men-*specify* direktori tempat di mana *binary* atau *executable* berada.
+
+Default-nya, sistem operasi pasti mempunyai beberapa *env var* yang sudah ada tanpa kita set, salah satunya seperti `PATH` tadi, juga lainnya. Variabel-variabel tersebut digunakan oleh sistem operasi untuk keperluan mereka. Tapi karena variabel juga bisa diakses oleh kita (selaku developer), maka kita pun juga bisa mempergunakannya untuk kebutuhan tertentu.
+
+Selain *reserved env var*, kita bisa juga membuat variabel baru yang hanya digunakan untuk keperluan program secara spesifik.
+
+## C.11.2. Penggunaan *env var* Sebagai Media Untuk Definisi Konfigurasi Program
+
+Pada chapter [B.22. Simple Configuration](/B-simple-configuration.html) dan juga [C.10. Advanced Configuration: Viper](/C-advanced-configuration-viper.html), kita telah belajar cara pendefinisian konfigurasi dengan memanfaatkan file seperti JSON maupun YAML.
+
+Pada chapter kali ini kita akan mendefinisikan konfigurasi yang sama tapi tidak di file, melainkan di *environment variable*.
+
+Definisi konfigurasi di env var banyak manfaatnya, salah satunya:
+
+- Di support secara *native* oleh **semua sistem operasi**.
+- Sudah sangat umum diterapkan di banyak aplikasi dan platform.
+- *Straightforward* dan tidak tergantung ke file tertentu.
+- Sharing konfigurasi dengan aplikasi/service lain menjadi lebih mudah.
+- Mudah untuk di maintain, tidak perlu repot buka file kemudian edit lalu simpan ulang.
+- ... dan banyak lagi lainnya.
+
+Jadi bisa dibilang penulisan konfigurasi di env var merupakan *best practice* untuk banyak jenis kasus, terutama pada *microservice*, pada aplikasi/service yang *distributable*, maupun pada aplikasi monolith yang manajemenya ter-automatisasi.
+
+Memang kalau dari sisi readability sangat kalah kalau dibandingkan dengan JSON atau YAML, tapi saya sampaikan bahwa meski effort koding bakal lebih banyak, akan ada sangat banyak manfaat yang bisa didapat dengan menuliskan konfigurasi di *env var*, terutama pada bagian **devops**.
+
+## C.11.3. Praktek
+
+Mari kita praktekan, buat 1 folder project baru, kemudian `main.go`, lalu isi file tersebut dengan kode berikut.
+
+```go
+package main
+
+import (
+ "net/http"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/labstack/echo"
+)
+
+func main() {
+ e := echo.New()
+
+ // ...
+}
+```
+
+Pada bagian main, tepat di bawah *statement* pembuatan objek `echo`, ambil nilai konfigurasi nama aplikasi dari *env var*. Caranya kurang lebih seperti berikut.
+
+```go
+confAppName := os.Getenv("APP_NAME")
+if confAppName == "" {
+ e.Logger.Fatal("APP_NAME config is required")
+}
+```
+
+Jadi `APP_NAME` di situ merupakan nama *env var*-nya. Umumnya env var tidak dituliskan dalam bentuk `camelCase`, tapi dalam bentuk `UPPERCASE` dengan separator kata adalah underscore. Untuk *value*-nya nanti tinggal kita siapkan saja sebelum proses eksekusi program.
+
+> `man bash`: name A word consisting only of alphanumeric characters and underscores, and beginning with an alphabetic character or an underscore. Also referred to as an identifier.
+
+Statement `os.Getenv` digunakan untuk pengambilan *env var*. Pada contoh di atas, terdapat pengecekan jika nilai `APP_NAME` adalah kosong, maka munculkan fatal error.
+
+Kemudian tambahkan lagi statement pengambilan nilai *env var* `SERVER_PORT`.
+
+```go
+confServerPort := os.Getenv("SERVER_PORT")
+if confServerPort == "" {
+ e.Logger.Fatal("SERVER_PORT config is required")
+}
+```
+
+Setelah itu, tambahkan routing untuk untuk `GET /index` lalu definisi objek server yang nantinya digunakan untuk keperluan *start* webserver. Nilai `server.Addr` diambil dari *env var* `SERVER_PORT`.
+
+```go
+e.GET("/index", func(c echo.Context) (err error) {
+ return c.JSON(http.StatusOK, true)
+})
+
+server := new(http.Server)
+server.Addr = ":" + confServerPort
+```
+
+Kemudian tambahkan setting untuk *timeout* webserver, tapi hanya ketika memang timeout didefinisikan konfigurasinya.
+
+```go
+if confServerReadTimeout := os.Getenv("SERVER_READ_TIMEOUT_IN_MINUTE"); confServerReadTimeout != "" {
+ duration, _ := strconv.Atoi(confServerReadTimeout)
+ server.ReadTimeout = time.Duration(duration) * time.Minute
+}
+
+if confServerWriteTimeout := os.Getenv("SERVER_WRITE_TIMEOUT_IN_MINUTE"); confServerWriteTimeout != "" {
+ duration, _ := strconv.Atoi(confServerWriteTimeout)
+ server.WriteTimeout = time.Duration(duration) * time.Minute
+}
+```
+
+Bisa dilihat di atas, jika *env var* `SERVER_READ_TIMEOUT_IN_MINUTE` ada nilainya, maka diambil kemudian di konversi ke bentuk `time.Duration` untuk dipergunakan pada `server.ReadTimeout`. Nilai balik dari `os.Getenv()` pasti berupa `string`, oleh karena itu jika konfigurasi dibutuhkan dalam bentuk lain, tambahkan saja statement untuk konversi datanya.
+
+Memang penerapan konfigurasi pada *env var* ini membutuhkan sedikit effort lebih, hehe.
+
+Terakhir, tambahkan statement untuk start webserver.
+
+```go
+e.Logger.Print("Starting", confAppName)
+e.Logger.Fatal(e.StartServer(server))
+```
+
+## C.11.4. Eksekusi Program
+
+Program sudah siap, betul, tetapi konfigurasi nya belum. Nah salah satu kelebihan dari kontrol konfigurasi lewat *env var* adalah kita bisa definisikan sewaktu eksekusi program (sebelum statement `go run`).
+
+Ada satu hal yang penting untuk diketahui. Cara set *env var* untuk Windows dibanding sistem operasi lainnya adalah berbeda. Untuk non-Windows, gunakan `export`.
+
+```bash
+export APP_NAME=SimpleApp
+export SERVER_PORT=9000
+export SERVER_READ_TIMEOUT_IN_MINUTE=2
+export SERVER_WRITE_TIMEOUT_IN_MINUTE=2
+go run main.go
+```
+
+Untuk Windows, gunakan `set`.
+
+```bash
+set APP_NAME=SimpleApp
+set SERVER_PORT=9000
+set SERVER_READ_TIMEOUT_IN_MINUTE=2
+set SERVER_WRITE_TIMEOUT_IN_MINUTE=2
+go run main.go
+```
+
+Agak sedikit report memang untuk bagian ini, tapi mungkin bisa diperingkas dengan membuat file `.sh` untuk non-Windows, dan file `.bat` untuk Windows. Jadi nanti bisa tinggal eksekusi sh/bat-nya saja. Atau temen-temen bisa tulis saja dalam `Makefile`. Untuk windows bisa kok eksekusi command `make` caranya dengan install [make](https://chocolatey.org/packages/make) lewat [Chocolatey](https://chocolatey.org/).
+
+Berikut adalah penampakan contoh run program lewat bat-file di Windows.
+
+
+
+## C.11.5. Penutup
+
+Memang saya setuju jika lebih butuh *effort* baik dari sisi programming maupun dari sisi eksekusi program-nya. Tapi *trust me*, pada production yang notabene *deployment* di-automatisasi (entah itu container based, pakai orchestrator, maupun tidak), pasti lebih mudah.
+
+Mungkin dari sini temen-temen bisa lanjut ke chapter [C.35. Dockerize Aplikasi Golang](/C-dockerize-golang.html) untuk melihat praktek nyata penerapan konfigurasi via *env var*.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From fa2017e0a91817ed14bdb5e0067f832a8fbc7036 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:23 +0700
Subject: [PATCH 092/522] New translations c-client-http-request-advanced.md
(English)
---
.../C-client-http-request-advanced.md | 198 ++++++++++++++++++
1 file changed, 198 insertions(+)
create mode 100644 en/content-en/C-client-http-request-advanced.md
diff --git a/en/content-en/C-client-http-request-advanced.md b/en/content-en/C-client-http-request-advanced.md
new file mode 100644
index 000000000..b6c29f0ae
--- /dev/null
+++ b/en/content-en/C-client-http-request-advanced.md
@@ -0,0 +1,198 @@
+# C.26. Advanced Client HTTP Request
+
+Pada chapter ini kita akan belajar tentang topik yang sedikit berbeda dibanding chapter sebelumnya, yaitu cara untuk melakukan http request ke sebuah web server.
+
+Dua aplikasi akan dibuat, server dan client. Server merupakan aplikasi web server kecil, memiliki satu endpoint. Lalu dari client http request di-trigger, dengan tujuan adalah server.
+
+## C.26.1. Aplikasi Server
+
+Buat project baru seperti biasa, lalu buat `server.go`. Import package yang dibutuhkan.
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+)
+
+type M map[string]interface{}
+```
+
+Pada fungsi `main()`, siapkan mux dengan isi satu buah handler, jalankan pada port `:9000`.
+
+```go
+func main() {
+ mux := new(http.ServeMux)
+ mux.HandleFunc("/data", ActionData)
+
+ server := new(http.Server)
+ server.Handler = mux
+ server.Addr = ":9000"
+
+ log.Println("Starting server at", server.Addr)
+ err := server.ListenAndServe()
+ if err != nil {
+ log.Fatalln("Failed to start web server", err)
+ }
+}
+```
+
+Buat fungsi `ActionData()` yang merupakan handler dari rute `/data`. Handler ini hanya menerima method `POST`, dan mewajibkan consumer endpoint untuk menyisipkan payload dalam request-nya, dengan isi adalah JSON.
+
+```go
+func ActionData(w http.ResponseWriter, r *http.Request) {
+ log.Println("Incoming request with method", r.Method)
+
+ if r.Method != "POST" {
+ http.Error(w, "Method not allowed", http.StatusBadRequest)
+ return
+ }
+
+ payload := make(M)
+ err := json.NewDecoder(r.Body).Decode(&payload)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ if _, ok := payload["Name"]; !ok {
+ http.Error(w, "Payload `Name` is required", http.StatusBadRequest)
+ return
+ }
+
+ // ...
+}
+```
+
+Isi dari `r.Body` kita decode ke objek `payload` yang bertipe `map[string]interface{}`. Setelah proses decoding selesai, terdapat pengecekan ada tidaknya property `Name` dalam payload. Jika tidak ada maka dianggap bad request.
+
+Setelah itu, buat objek `data` dengan 2 property, yang salah satunya berisi kombinasi string dari payload `.Name`.
+
+```go
+data := M{
+ "Message": fmt.Sprintf("Hello %s", payload["Name"]),
+ "Status": true,
+}
+
+w.Header().Set("Content-Type", "application/json")
+err = json.NewEncoder(w).Encode(data)
+if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+}
+```
+
+Cara render output JSON biasanya kita lakukan menggunakan statement `w.Write()` dengan isi adalah `[]byte` milik JSON. Ada cara lain, yaitu menggunakan json encoder. Penerapannya bisa dilihat pada kode di atas.
+
+Aplikasi server sudah siap. Selanjutnya kita masuk ke bagian pembuatan aplikasi client.
+
+## C.26.2. Aplikasi Client
+
+Tugas dari aplikasi client: melakukan http request ke aplikasi server, pada endpoint `/data` sesuai dengan spesifikasi yang sudah dijelaskan di atas (ber-method POST, dan memiliki JSON payload).
+
+Buat file baru, `client.go`, import package yang diperlukan.
+
+```go
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+type M map[string]interface{}
+```
+
+Buat fungsi `doRequest()`. Fungsi ini kita gunakan men-trigger http request.
+
+```go
+func doRequest(url, method string, data interface{}) (interface{}, error) {
+ var payload *bytes.Buffer = nil
+
+ if data != nil {
+ payload = new(bytes.Buffer)
+ err := json.NewEncoder(payload).Encode(data)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ request, err := http.NewRequest(method, url, payload)
+ if err != nil {
+ return nil, err
+ }
+
+ // ...
+}
+```
+
+Fungsi tersebut menerima 3 buah parameter.
+
+ - Parameter `url`, adalah alamat tujuan request.
+ - Parameter `method`, bisa GET, POST, PUT, ataupun method valid lainnya sesuai spesifikasi [RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html).
+ - Parameter `data`, isinya boleh kosong. Jika ada isinya, data tersebut di-encode ke bentuk JSON untuk selanjutnya disisipkan pada body request.
+
+Buat objek request lewat `http.NewRequest()`. Sisipkan ke-3 parameter tersebut.
+
+Selanjutnya buat objek client. Dari client ini request di-trigger, menghasilkan objek response.
+
+```go
+client := new(http.Client)
+
+response, err := client.Do(request)
+if response != nil {
+ defer response.Body.Close()
+}
+if err != nil {
+ return nil, err
+}
+
+responseBody := make(M)
+err = json.NewDecoder(response.Body).Decode(&responseBody)
+if err != nil {
+ return nil, err
+}
+
+return responseBody, nil
+```
+
+Decode response tersebut ke tipe `M`, lalu tampilkan hasilnya.
+
+Buat fungsi `main()`. Panggil fungsi `doRequest()` yang sudah dibuat. Untuk payload silakan isi sesuka hati, asalkan ada item dengan key `Name`. Lalu tampilkan response body hasil pemanggilan fungsi `doRequest()`.
+
+```go
+func main() {
+ baseURL := "http://localhost:9000"
+ method := "POST"
+ data := M{"Name": "Noval Agung"}
+
+ responseBody, err := doRequest(baseURL+"/data", method, data)
+ if err != nil {
+ log.Println("ERROR", err.Error())
+ return
+ }
+
+ log.Printf("%#v \n", responseBody)
+}
+```
+
+## C.26.3. Testing
+
+Jalankan aplikasi server, buka prompt terminal/CMD baru, lalu jalankan aplikasi client.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 612ddfbea87a8938adfd18d98eb52c14e18d9ffb Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:23 +0700
Subject: [PATCH 093/522] New translations c-convert-html-to-pdf.md (English)
---
en/content-en/C-convert-html-to-pdf.md | 181 +++++++++++++++++++++++++
1 file changed, 181 insertions(+)
create mode 100644 en/content-en/C-convert-html-to-pdf.md
diff --git a/en/content-en/C-convert-html-to-pdf.md b/en/content-en/C-convert-html-to-pdf.md
new file mode 100644
index 000000000..1472738ed
--- /dev/null
+++ b/en/content-en/C-convert-html-to-pdf.md
@@ -0,0 +1,181 @@
+# C.21. Convert HTML to PDF (go-wkhtmltopdf)
+
+Library gofpdf hanya bisa digunakan untuk pembuatan PDF. Biasanya dalam sebuah aplikasi, report berupa pdf diunduh dengan sumber data adalah halaman web report itu sendiri. Nah, pada chapter ini kita akan belajar cara konversi file HTML ke bentuk PDF menggunakan library golang [wkhtmltopdf](https://github.com/wkhtmltopdf/wkhtmltopdf).
+
+> Sebenarnya di jaman ini, export HTML to PDF sudah bisa dilakukan di layer front end menggunakan cukup javascript saja. Apalagi jika menggunakan framework terkenal seperti Kendo UI, report yang dimunculkan menggunakan KendoGrid bisa dengan mudah di export ke excel maupun pdf.
Namun pada chapter ini akan tetap kita bahas cara tradisional ini, konversi HTML ke PDF pada back end (golang). Semoga berguna.
+
+## C.21.1. Konversi File HTML ke PDF
+
+Buat file html bernama `input.html`, isi dengan apa saja, kalau bisa ada gambarnya juga. Contohnya seperti berikut.
+
+```html
+
+
+
+ Testing
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Proin tempor ut ipsum et feugiat. Phasellus porttitor,
+ felis et gravida aliquam,
+ eros orci dignissim magna, at tristique elit massa at magna.
+ Etiam et dignissim mi. Phasellus laoreet nulla non aliquam imperdiet.
+ Aenean varius turpis at orci posuere, ut bibendum lorem porta.
+ Maecenas ullamcorper posuere ante quis ultricies. Aliquam erat volutpat.
+ Vestibulum ante ipsum primis in faucibus orci luctus et
+ ultrices posuere cubilia Curae;
+ Pellentesque eu tellus ante. Vivamus varius nisi non nulla imperdiet,
+ vitae pellentesque nibh varius. Fusce diam magna, iaculis eget felis id,
+ accumsan convallis elit.
+ Phasellus in magna placerat, aliquet ante sed, luctus massa.
+ Sed fringilla bibendum feugiat. Suspendisse tempus, purus sit amet
+ accumsan consectetur, ipsum odio commodo nisi,
+ vel dignissim massa mi ac turpis. Ut fringilla leo ut risus facilisis,
+ nec malesuada nunc ornare. Nulla a dictum augue.
+
+
+
+
+
+
+
+```
+
+File html di atas akan kita konversi menjadi sebuah file baru bertipe PDF. Konversi dilakukan menggunakan library wkhtmltopdf. Library ini sebenarnya adalah aplikasi CLI yang dibuat menggunakan bahasa **C++**. Untuk bisa menggunakannya kita harus mengunduh lalu meng-install-nya terlebih dahulu.
+
+Silakan unduh installer wkhtmltopdf di https://wkhtmltopdf.org/downloads.html, pilih sesuai dengan sistem operasi yang digunakan.
+
+karena wkhtmltopdf merupakan sebuah aplikasi CLI, maka penggunaannya bisa lewat dua cara.
+
+ - Cara ke-1: Menggunakan `exec.Command()` untuk mengeksekusi binary. Path file html target disisipkan sebagai argumen command. Silakan merujuk ke referensi pada chapter [A.49. Exec](/A-exec.html) untuk mempelajari cara penggunaan exec.
+ - Cara ke-2: Menggunakan golang wrapper [go-wkhtmltopdf](github.com/SebastiaanKlippert/go-wkhtmltopdf). Cara ini adalah yang kita pilih.
+
+Secara teknis, go-wkhtmltopdf melakukan hal yang sama dengan cara pertama, yaitu mengeksekusi binary wkhtmltopdf menggunakan `exec.Command()`.
+
+Mari langsung kita praktekan, buat folder project baru. Siapkan file main. Isi dengan kode berikut.
+
+```go
+package main
+
+import (
+ "github.com/SebastiaanKlippert/go-wkhtmltopdf"
+ "log"
+ "os"
+)
+
+func main() {
+ pdfg, err := wkhtmltopdf.NewPDFGenerator()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // ...
+}
+```
+
+Pembuatan objek PDF dilakukan lewat `wkhtmltopdf.NewPDFGenerator()`. Fungsi tersebut mengembalikan dua buah objek, objek dokumen dan error (jika ada).
+
+Satu objek dokumen merepresentasikan 1 buah file PDF.
+
+Kemudian tambahkan kode untuk membaca file `input.html`. Gunakan `os.Open`, agar file tidak langsung dibaca, melainkan dijadikan sebagai `io.Reader`. Masukan objek reader ke fungsi `wkhtmltopdf.NewPageReader()`, lalu sisipkan kembaliannya sebagai page baru di objek PDF.
+
+Kurang lebih kode-nya seperti berikut.
+
+```go
+f, err := os.Open("./input.html")
+if f != nil {
+ defer f.Close()
+}
+if err != nil {
+ log.Fatal(err)
+}
+
+pdfg.AddPage(wkhtmltopdf.NewPageReader(f))
+
+pdfg.Orientation.Set(wkhtmltopdf.OrientationPortrait)
+pdfg.Dpi.Set(300)
+```
+
+Method `.AddPage()` milik objek PDF, digunakan untuk menambahkan halaman baru. Halaman baru sendiri dibuat lewat `wkhtmltopdf.NewPageReader()`. Jika ternyata konten pada halaman terlalu panjang untuk dijadikan 1 buah page, maka akan secara otomatis dibuatkan page selanjutnya menyesuaikan konten.
+
+Statement `pdfg.Orientation.Set()` digunakan untuk menentukan orientasi dokumen, apakah portrait atau landscape. Statement `pdfg.Dpi.Set()` digunakan untuk menge-set DPI dokumen.
+
+Gunakan API yang tersedia milik go-wkhtmltopdf sesuai kebutuhan. Silakan merujuk ke https://godoc.org/github.com/SebastiaanKlippert/go-wkhtmltopdf untuk melihat API apa saja yang tersedia.
+
+Untuk menyimpan objek dokumen menjadi file fisik PDF, ada beberapa step yang harus dilakukan. Pertama, buat dokumen dalam bentuk buffer menggunakan method `.Create()`.
+
+```go
+err = pdfg.Create()
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+Setelah itu, outputkan buffer tersebut sebagai file fisik menggunakan `.WriteFile()`. Sisipkan path destinasi file sebagai parameter method tersebut.
+
+```go
+err = pdfg.WriteFile("./output.pdf")
+if err != nil {
+ log.Fatal(err)
+}
+
+log.Println("Done")
+```
+
+Test aplikasi yang sudah kita buat, lihat hasil generated PDF-nya.
+
+
+
+Bisa dilihat, dalam satu PDF dua page muncul, hal ini karena memang isi `input.html` terlalu panjang untuk dijadikan sebagai satu page.
+
+Cara yang kita telah pelajari ini cocok digunakan pada file html yang isinya sudah pasti pada saat file tersebut di-load.
+
+## C.21.2. Konversi HTML dari URL Menjadi PDF
+
+Bagaimana untuk HTML yang sumber nya bukan dari file fisik, melainkan dari URL? tetap bisa dilakukan. Caranya dengan mendaftarkan url sebagai objek page lewat `wkhtmltopdf.NewPage()`, lalu memasukannya ke dalam dokumen sebagai page. Contoh penerapannya bisa dilihat pada kode di bawah ini.
+
+```go
+pdfg, err := wkhtmltopdf.NewPDFGenerator()
+if err != nil {
+ log.Fatal(err)
+}
+
+pdfg.Orientation.Set(wkhtmltopdf.OrientationLandscape)
+
+page := wkhtmltopdf.NewPage("http://localhost:9000")
+page.FooterRight.Set("[page]")
+page.FooterFontSize.Set(10)
+pdfg.AddPage(page)
+
+err = pdfg.Create()
+if err != nil {
+ log.Fatal(err)
+}
+
+err = pdfg.WriteFile("./output.pdf")
+if err != nil {
+ log.Fatal(err)
+}
+
+log.Println("Done")
+```
+
+Cara ini cocok digunakan untuk konversi data HTML yang isinya muncul pada saat page load. Untuk konten-konten yang munculnya asynchronous, seperti di event `documen.onload` ada AJAX lalu setelahnya konten baru ditulis, tidak bisa menggunakan cara ini. Solusinya bisa menggunakan teknik export ke PDF dari sisi front end.
+
+---
+
+- [gofpdf](https://github.com/jung-kurt/gofpdf), by Kurt Jung, MIT license
+- [wkhtmltopdf](https://github.com/wkhtmltopdf/wkhtmltopdf), by Ashish Kulkarni, LGPL-3.0 license
+- [go-wkhtmltopdf](https://github.com/SebastiaanKlippert/go-wkhtmltopdf), by Sebastiaan Klippert, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 7843ec382b293e9226b29f413cf4e9d7a7f7db1a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:24 +0700
Subject: [PATCH 094/522] New translations c-cors-preflight-request.md
(English)
---
en/content-en/C-cors-preflight-request.md | 301 ++++++++++++++++++++++
1 file changed, 301 insertions(+)
create mode 100644 en/content-en/C-cors-preflight-request.md
diff --git a/en/content-en/C-cors-preflight-request.md b/en/content-en/C-cors-preflight-request.md
new file mode 100644
index 000000000..b1aa8c0f5
--- /dev/null
+++ b/en/content-en/C-cors-preflight-request.md
@@ -0,0 +1,301 @@
+# C.14. CORS & Preflight Request
+
+Pada chapter ini kita akan belajar tentang Cross-Origin Resource Sharing (CORS) dan Preflight Request.
+
+## C.14.1. Teori & Penerapan
+
+CORS adalah mekanisme untuk memberi tahu browser, apakah sebuah request yang di-dispatch dari aplikasi web domain lain atau origin lain, ke aplikasi web kita itu diperbolehkan atau tidak. Jika aplikasi kita tidak mengijinkan maka akan muncul error, dan request pasti digagalkan oleh browser.
+
+CORS hanya berlaku pada request-request yang dilakukan lewat browser, dari javascript; dan tujuan request-nya berbeda domain/origin. Jadi request yang dilakukan dari curl maupun dari back end, tidak terkena dampak aturan CORS.
+
+> Request jenis ini biasa disebut dengan istilah cross-origin HTTP request.
+
+Konfigurasi CORS dilakukan di **response header** aplikasi web. Penerapannya di semua bahasa pemrograman yang web-based adalah sama, yaitu dengan memanipulasi response header-nya. Berikut merupakan list header yang bisa digunakan untuk konfigurasi CORS.
+
+ - Access-Control-Allow-Origin
+ - Access-Control-Allow-Methods
+ - Access-Control-Allow-Headers
+ - Access-Control-Allow-Credentials
+ - Access-Control-Max-Age
+
+Konfigurasi CORS berada di sisi server, di aplikasi web tujuan request.
+
+Permisalan: aplikasi kita di local mengambil data dari google.com, maka konfigurasi CORS berada di google.com; Jika kita terkena error CORS maka tak ada lagi yang bisa dilakukan, karena CORS aplikasi tujuan dikontrol oleh orang-orang yang ada di google.com.
+
+Agar lebih mudah untuk dipahami bagaimana penerapannya, mari langsung kita praktekan seperti biasanya.
+
+## C.14.2. Aplikasi dengan konfigurasi CORS sederhana
+
+Buat project baru, lalu isi fungsi `main()` dengan kode berikut. Aplikasi sederhana ini akan kita jalankan pada domain atau origin `http://localhost:3000/`, lalu akan kita coba akses dari domain berbeda.
+
+```go
+package main
+
+import (
+ "log"
+ "net/http"
+)
+
+func main() {
+ http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "https://www.google.com")
+ w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT")
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token")
+
+ if r.Method == "OPTIONS" {
+ w.Write([]byte("allowed"))
+ return
+ }
+
+ w.Write([]byte("hello"))
+ })
+
+ log.Println("Starting app at :9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Seperti yang sudah dijelaskan, bahwa konfigurasi CORS berada di header response. Pada kode di atas 3 buah property header untuk keperluan CORS digunakan.
+
+Header `Access-Control-Allow-Origin` digunakan untuk menentukan domain mana saja yang diperbolehkan mengakses aplikasi ini. Kita bisa set value-nya dengan banyak origin, hal ini diperbolehkan dalam [spesifikasi CORS](https://www.w3.org/TR/cors/#access-control-allow-origin-response-header) namun sayangnya banyak browser yang tidak support.
+
+```http
+Access-Control-Allow-Origin: https://www.google.com
+```
+
+Kode di atas artinya request yang di-dispatch dari https://www.google.com diijinkan untuk masuk; Penulis memilih domain google karena testing akan dilakukan dari sana, dengan tujuan destinasi request adalah `http://localhost:3000/`.
+
+Simulasi pada chapter ini adalah **aplikasi web localhost:3000 diakses dari google.com** (eksekusi request sendiri kita lakukan dari browser dengan memanfaatkan developer tools milik chrome). BUKAN google.com diakses dari aplikasi web localhost:3000, jangan sampai dipahami terbalik.
+
+Kembali ke pembahasan source code. Dua header CORS lainnya digunakan untuk konfigurasi yang lebih mendetail.
+
+```http
+Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT
+Access-Control-Allow-Headers: Content-Type, X-CSRF-Token
+```
+
+Header `Access-Control-Allow-Methods` menentukan HTTP Method mana saja yang diperbolehkan masuk (penulisannya dengan pembatas koma).
+
+> Dianjurkan untuk selalu memasukan method `OPTIONS` karena method ini dibutuhkan oleh preflight request.
+
+Header `Access-Control-Allow-Headers` menentukan key header mana saja yang diperbolehkan di-dalam request.
+
+Jika request tidak memenuhi salah satu saja dari ke-tiga rules di atas, maka request bisa dipastikan gagal. Contoh:
+
+ - Request dari https://novalagung.com ke http://localhost:3000, hasilnya pasti gagal, karena origin novalagung.com tidak diijinkan untuk mengakses http://localhost:3000.
+ - Request dari https://www.google.com ke http://localhost:3000 dengan method adalah `DELETE`, hasilnya pasti gagal. Method `DELETE` adalah tidak di-ijinkan. hanya empat method `OPTIONS`, `GET`, `POST`, `PUT` yang diijinkan.
+ - Request dari https://www.google.com ke http://localhost:3000 dengan method GET, degan header `Authorization: Basic xxx` dan `X-CSRF-Token: xxxx`, hasilnya adalah gagal. Karena salah satu dari kedua header tersebut tidak diijinkan (header `Authorization`).
+ - Request dari https://www.google.com ke http://localhost:3000 dengan method `GET` dan memiliki header `Content-Type` adalah diijinkan masuk, karena memenuhi semua aturan yang kita definiskan.
+
+Khusus untuk beberapa header seperti `Accept`, `Origin`, `Referer`, dan `User-Agent` tidak terkena efek CORS, karena header-header tersebut secara otomatis di-set di setiap request.
+
+## C.14.3. Testing CORS
+
+#### • Persiapan
+
+Ada beberapa hal yang perlu dipersiapkan. Pertama, pastikan punya google chrome. Lalu install extension [jQuery Injector](https://chrome.google.com/webstore/detail/jquery-injector/ekkjohcjbjcjjifokpingdbdlfekjcgi?hl=en). Buka https://www.google.com lalu inject jQuery. Dengan melakukan inject jQuery secara paksa maka dari situs google kita bisa menggunakan jQuery.
+
+Buka chrome developer tools, klik tab console. Lalu jalankan perintah jQuery AJAX berikut.
+
+```js
+$.ajax({
+ url: "http://localhost:9000/index",
+})
+```
+
+Silakan lihat gambar berikut untuk memperjelas.
+
+
+
+Bisa dilihat, tidak ada error, karena memang request dari google diijinkan. Silakan coba-coba melakukan request AJAX lainnya dengan method POST, DELETE, atau lainnya; atau ditambah dengan menyisipkan header tertentu dalam ajax request.
+
+#### • Akses http://localhost:9000 dari Origin yang Tidak Didaftarkan di CORS
+
+Selanjutnya coba buka tab baru, buka https://novalagung.com, lalu jalankan script yang sama.
+
+
+
+
Failed to load http://localhost:9000/index: The 'Access-Control-Allow-Origin' header has a value 'https://www.google.com' that is not equal to the supplied origin. Origin 'https://novalagung.com' is therefore not allowed access.
+
+Dari screenshot dan error log di atas, bisa dilihat bahwa request gagal. Hal ini dikarenakan origin https://novalagung.com tidak diijinkan untuk mengakses http://localhost:9000.
+
+#### • CORS Multiple Origin
+
+Sekarang coba tambahkan situs https://novalagung.com ke CORS header.
+
+```go
+http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin",
+ "https://www.google.com, https://novalagung.com")
+
+ // ...
+```
+
+Jalankan ulang aplikasi, lalu dispatch lagi AJAX dari situs tersebut.
+
+
+
+Masih tetap error, tapi berbeda dengan error sebelumnya.
+
+
Failed to load http://localhost:9000/index: The 'Access-Control-Allow-Origin' header contains multiple values 'https://www.google.com, https://novalagung.com', but only one is allowed. Origin 'https://novalagung.com' is therefore not allowed access.
+
+Sebenarnya sudah kita singgung juga di atas, bahwa di spesifikasi adalah diperbolehkan isi header `Access-Control-Allow-Origin` lebih dari satu website. Namun, kebanyakan browser tidak mendukung bagian ini. Oleh karena itu error di atas muncul. Konfigurasi ini termasuk tidak valid, hasilnya kedua website tersebut tidak punya ijin masuk.
+
+#### • Allow All
+
+Gunakan tanda asteriks (`*`) sebagai nilai ketiga CORS header untuk memberi ijin ke semua.
+
+```go
+// semua origin mendapat ijin akses
+w.Header().Set("Access-Control-Allow-Origin", "*")
+
+// semua method diperbolehkan masuk
+w.Header().Set("Access-Control-Allow-Methods", "*")
+
+// semua header diperbolehkan untuk disisipkan
+w.Header().Set("Access-Control-Allow-Headers", "*")
+```
+
+## C.14.4. Preflight Request
+
+#### • Teori
+
+Dalam konteks CORS, request dikategorikan menjadi 2 yaitu, **Simple Request** dan **Preflighted Request**. Beberapa contoh request yang sudah kita pelajari di atas termasuk simple request.
+
+Sedangkan mengenai preflighted request sendiri, mungkin pembaca secara tidak langsung juga pernah menerapkannya, terutama ketika bekerja di bagian front-end yang mengonsumsi data dari RESTful API yang server nya terpisah antara layer front end dan back end.
+
+Ketika melakukan cross origin request dengan payload adalah JSON, atau request jenis lainnya, biasanya di developer tools -> network log muncul 2 kali request, request pertama method-nya `OPTIONS` dan request ke-2 adalah actual request.
+
+Request ber-method `OPTIONS` tersebut disebut dengan **Preflight Request**. Request ini akan otomatis muncul ketika http request yang kita dispatch memenuhi kriteria preflighted request.
+
+Tujuan dari preflight request adalah untuk mengecek apakah destinasi url mendukung CORS. Tiga buah informasi dikirimkan `Access-Control-Request-Method`, `Access-Control-Request-Headers`, dan `Origin`, dengan method adalah `OPTIONS`.
+
+Berikut merupakan kriteria preflighted request.
+
+ - Method yang digunakan adalah salah satu dari method berikut:
+ - `PUT`
+ - `DELETE`
+ - `CONNECT`
+ - `OPTIONS`
+ - `TRACE`
+ - `PATCH`
+ - Terdapat header SELAIN yang otomatis di-set dalam http request. Contoh header untuk kriteria ini adalah `Authorization`, `X-CSRF-Token`, atau lainnya.
+ - Isi header `Content-Type` adalah SELAIN satu dari 3 berikut.
+ - application/x-www-form-urlencoded
+ - multipart/form-data
+ - text/plain
+ - Ada event yang ter-registrasi dalam objek `XMLHttpRequestUpload` yang digunakan dalam request.
+ - Menggunakan objek `ReadableStream` dalam request.
+
+> Lebih detailnya mengenai simple dan preflighted request silakan baca https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.
+
+Pada kode yang kita tulis, terdapat pengecekan method `OPTIONS`. Pengecekan ini digunakan untuk mencegah eksekusi statement selanjutnya. Hal ini dikarenakan preflight request tidak membutuhkan kembalian data, tugas si dia hanya mengecek apakah cross origin request didukung atau tidak. Jadi pada handler, ketika method nya adalah `OPTIONS`, langsung saja intercept proses utamanya.
+
+> Header `Access-Control-Max-Age` diisi dengan data waktu, digunakan untuk menentukan sebearapa lama informasi preflight request di-cache. Jika diisi dengan `-1` maka cache di-non-aktifkan.
+
+```go
+http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
+ // ...
+
+ if r.Method == "OPTIONS" {
+ w.Write([]byte("allowed"))
+ return
+ }
+
+ // ...
+})
+```
+
+#### • Praktek
+
+Langsung saja buka google.com lalu lakukan AJAX request yang memenuhi alah satu kriteria preflighted request, misalnya, gunakan header `Content-Type: application/json`.
+
+
+
+Bisa dilihat pada screenshot, dua request muncul, yang pertama adalah preflight yang kedua adalah actual request.
+
+## C.14.5. CORS Handling Menggunakan Golang CORS Library dan Echo
+
+Pada bagian ini kita akan mengkombinasikan library CORS golang buatan Olivier Poitrey, dan Echo, untuk membuat back end yang mendukung cross origin request.
+
+Pertama `go get` dulu library-nya.
+
+```bash
+go get https://github.com/rs/cors
+```
+
+Buat file baru, import library yang diperlukan lalu buat fungsi main.
+
+```go
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/rs/cors"
+ "net/http"
+)
+
+func main() {
+ e := echo.New()
+
+ // ...
+
+ e.GET("/index", func(c echo.Context) error {
+ return c.String(http.StatusOK, "hello")
+ })
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Siapkan objek `corsMiddleware`, cetak dari fungsi `cors.New()` . Pada parameter konfigurasi, isi spesifikasi CORS sesuai kebutuhan.
+
+Gaya konfigurasi library ini menarik, mudah sekali untuk dipahami.
+
+```go
+corsMiddleware := cors.New(cors.Options{
+ AllowedOrigins: []string{"https://novalagung.com", "https://www.google.com"},
+ AllowedMethods: []string{"OPTIONS", "GET", "POST", "PUT"},
+ AllowedHeaders: []string{"Content-Type", "X-CSRF-Token"},
+ Debug: true,
+})
+e.Use(echo.WrapMiddleware(corsMiddleware.Handler))
+```
+
+Pada kode di atas, kita meng-allow dua buah origin. Sebelumnya sudah kita bahas bahwa kebanyakan browser tidak mendukung ini. Dengan menggunakan CORS library, hal itu bisa teratasi.
+
+> Sebenarnya mekanisme yang diterapkan oleh CORS library adalah meng-allow semua origin, lalu kemudian mem-filter sesuai dengan spesifikasi yang kita buat, lalu memodifikasi response header `Access-Control-Allow-Origin`-nya.
+
+Jalankan aplikasi, coba test dari dua domain, https://novalagung.com dan https://www.google.com.
+
+
+
+Berikut adalah list konfigurasi yang bisa dimanfaatkan dari library ini.
+
+| Key | Description |
+| ------------------ | ---------------------------------------------------------------------------------------------------- |
+| AllowedOrigins | list origin/domain yang diperbolehkan mengakses, gunakan `*` untuk allow all |
+| AllowOriginFunc | callback untuk validasi origin. cocok digunakan untuk menge-set CORS header origin dengan ijin rumit |
+| AllowedMethods | list HTTP method yang diperbolehkan untuk pengaksesan |
+| AllowedHeaders | list header yang diperbolehkan untuk pengaksesan |
+| ExposedHeaders | menentukan header mana saja yang di-expose ke consumer |
+| AllowCredentials | enable/disable credentials |
+| MaxAge | durasi cache preflight request |
+| OptionsPassthrough | digunakan untuk menginstruksikan handler selanjutnya untuk memproses OPTIONS method |
+| Debug | aktifkan properti ini pada stage development, agar banyak informasi log tambahan bisa muncul |
+
+---
+
+ - [CORS](https://github.com/rs/cors), by Olivier Poitrey, MIT license
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 061769e5e45f71faebcb28567209e2ae5833b45f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:25 +0700
Subject: [PATCH 095/522] New translations c-csrf.md (English)
---
en/content-en/C-csrf.md | 229 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 229 insertions(+)
create mode 100644 en/content-en/C-csrf.md
diff --git a/en/content-en/C-csrf.md b/en/content-en/C-csrf.md
new file mode 100644
index 000000000..5ecf26860
--- /dev/null
+++ b/en/content-en/C-csrf.md
@@ -0,0 +1,229 @@
+# C.15. CSRF
+
+Pada chapter ini kita akan belajar tentang serangan Cross-Site Request Forgery (CSRF) dan cara mengantisipasinya.
+
+## C.15.1. Teori
+
+Cross-Site Request Forgery atau CSRF adalah salah satu teknik hacking yang dilakukan dengan cara mengeksekusi perintah yang seharusnya tidak diizinkan, tetapi output yang dihasilkan sesuai dengan yang seharusnya. Contoh serangan jenis ini: mencoba untuk login lewat media selain web browser, seperti menggunakan CURL, menembak langsung endpoint login. Masih banyak contoh lainnya yang lebih ekstrim.
+
+Ada beberapa cara untuk mencegah serangan ini, salah satunya adalah dengan memanfaatkan csrf token. Di setiap halaman yang ada form nya, csrf token di-generate. Pada saat submit form, csrf disisipkan di request, lalu di sisi back end dilakukan pengecekan apakah csrf yang dikirim valid atau tidak.
+
+Csrf token sendiri merupakan sebuah random string yang di-generate setiap kali halaman form muncul. Biasanya di tiap POST request, token tersebut disisipkan sebagai header, atau form data, atau query string.
+
+Lebih detailnya silakan merujuk ke https://en.wikipedia.org/wiki/Cross-site_request_forgery.
+
+## C.15.2. Praktek: Back End
+
+Di golang, pencegahan CSRF bisa dilakukan dengan membuat middleware untuk pengecekan setiap request POST yang masuk. Cukup mudah sebenarnya, namun agar lebih mudah lagi kita akan gunakan salah satu middleware milik echo framework untuk belajar.
+
+Di setiap halaman, jika di dalam html nya terdapat form, maka harus disisipkan token csrf. Token tersebut di-generate oleh middleware.
+
+Di tiap POST request hasil dari form submit, token tersebut harus ikut dikirimkan. Proses validasi token sendiri di-handle oleh middleware.
+
+Mari kita praktekkan, siapkan project baru. Buat file `main.go`, isi dengan kode berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+ "html/template"
+ "net/http"
+)
+
+type M map[string]interface{}
+
+func main() {
+ tmpl := template.Must(template.ParseGlob("./*.html"))
+
+ e := echo.New()
+
+ // ...
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Nantinya akan ada endpoint `/index`, isinya menampilkan html form. Objek `tmpl` kita gunakan untuk rendering form tersebut. API echo renderer tidak digunakan dalam chapter ini.
+
+Siapkan routing untuk `/index`, dan registrasikan middleware CSRF.
+
+```go
+const CSRFTokenHeader = "X-CSRF-Token"
+const CSRFKey = "csrf"
+
+e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
+ TokenLookup: "header:" + CSRFTokenHeader,
+ ContextKey: CSRFKey,
+}))
+
+e.GET("/index", func(c echo.Context) error {
+ data := make(M)
+ data[CSRFKey] = c.Get(CSRFKey)
+ return tmpl.Execute(c.Response(), data)
+})
+```
+
+Objek middleware CSRF dibuat lewat statement `middleware.CSRF()`, konfigurasi default digunakan. Atau bisa juga dibuat dengan disertakan konfigurasi custom, lewat `middleware.CSRFWithConfig()` seperti pada kode di atas.
+
+Property `ContextKey` digunakan untuk mengakses token csrf yang tersimpan di `echo.Context`, pembuatan token sendiri terjadi pada saat ada http request GET masuk.
+
+Property tersebut kita isi dengan konstanta `CSRFKey`, maka dalam pengambilan token cukup panggil `c.Get(CSRFKey)`. Token kemudian disisipkan sebagai data pada saat rendering `view.html`.
+
+Property `TokenLookup` adalah acuan di bagian mana informasi csrf disisipkan dalam objek request, apakah dari header, query string, atau form data. Ini penting karena dibutuhkan oleh middleware yang bersangkutan untuk memvalidasi token tersebut. Bisa dilihat, kita memilih `header:X-CSRF-Token`, artinya csrf token dalam request akan disisipkan dalam header dengan key adalah `X-CSRF-Token`.
+
+> Isi value `TokenLookup` dengan `"form:"` jika token disispkan dalam form data request, dan `"query:"` jika token disisipkan dalam query string.
+
+Selanjutnya siapkan satu endpoint lagi, yaitu `/sayhello`, endpoint ini nantinya menjadi tujuan request yang di-dispatch dari event submit form.
+
+```go
+e.POST("/sayhello", func(c echo.Context) error {
+ data := make(M)
+ if err := c.Bind(&data); err != nil {
+ return err
+ }
+
+ message := fmt.Sprintf("hello %s", data["name"])
+ return c.JSON(http.StatusOK, message)
+})
+```
+
+Pada handler endpoint `/sayhello` tidak ada pengecekan token csrf, karena sudah ditangani secara implisit oleh middleware.
+
+## C.15.3. Front End
+
+Buat `view.html`, lalu isi kode berikut.
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Bisa dilihat, sebuah form disiapkan dengan isi 2 inputan dan satu buah tombol submit.
+
+Sebenarnya ada tiga inputan, namun yang satu adalah hidden. Inputan tersebut berisi nilai `csrf` yang disisipkan dari back end.
+
+Pada saat tombol submit di-klik, token tersebut harus disisipkan dalam AJAX request yang mengarah ke `/sayhello`.
+
+Sekarang buat script JS-nya. Siapkan sebuah event listener `submit` untuk element `form`, isinya adalah AJAX. Ambil informasi inputan nama dan gender, jadikan sebagai payload AJAX tersebut.
+
+```js
+$(function () {
+ $('form').on('submit', function (e) {
+ e.preventDefault()
+
+ var self = $(this)
+
+ var formData = {
+ name: self.find('[name="name"]').val(),
+ gender: self.find('[name="gender"]').val(),
+ }
+
+ var url = self.attr('action')
+ var method = self.attr('method')
+ var payload = JSON.stringify(formData)
+
+ $.ajax({
+ url: url,
+ type: method,
+ contentType: 'application/json',
+ data: payload,
+ beforeSend: function(req) {
+ var csrfToken = self.find('[name=csrf]').val()
+ req.setRequestHeader("X-CSRF-Token", csrfToken)
+ },
+ }).then(function (res) {
+ alert(res)
+ }).catch(function (err) {
+ alert('ERROR: ' + err.responseText)
+ console.log('err', err)
+ })
+ })
+})
+```
+
+Tambahkan header `X-CSRF-Token` di AJAX request seperti pada kode di atas, isinya diambil dari inputan hidden `csrf`. Nama header sendiri menggunakan `X-CSRF-Token`.
+
+Karena di konfigurasi middleware csrf di back end `TokenLookup` adalah `header:X-CSRF-Token`, maka header dengan nama `X-CSRF-Token` dipilih.
+
+## C.15.4. Testing
+
+Sekarang jalankan aplikasi lalu akses `/index` untuk mengetes hasilnya. Silakan melakukan skenario testing berikut.
+
+ 1. Buka laman `/index`, form akan muncul.
+ 2. Pada saat rendering output `/index`, disisipkan juga token csrf yang di-generate oleh middleware pada saat endpoint ini diakses.
+ 3. OK, sekarang laman form sudah muncul.
+ 4. Isi inputan form.
+ 5. Klik tombol submit.
+ 6. Di event submit tersebut, sebuah AJAX dipersiapkan dengan tujuan adalah `/sayhello`.
+ 7. Di dalam AJAX ini, token csrf yang sebelumnya disisipkan, diambil lalu ditempelkan ke AJAX.
+ 8. AJAX di-dispatch ke `/sayhello`.
+ 9. Sebelum benar-benar diterima oleh handler endpoint, middleware secara otomatis melakukan pengecekan atas token csrf yang disisipkan.
+ 10. Jika pengecekan sukses, maka request diteruskan ke tujuan.
+ 11. Jika tidak suses, error message dikembalikan.
+ 12. Pengecekan adalah sukses, alert message muncul.
+
+Hasilnya:
+
+
+
+Coba tembak langsung endpoint nya lewat CURL.
+
+```bash
+$ curl -X POST http://localhost:9000/sayhello \
+ -H 'Content-Type: application/json' \
+ -d '{"name":"noval","gender":"male"}'
+```
+
+
+
+Hasilnya error, karena token csrf tidak di-sisipkan.
+
+Lewat teknik pencegahan ini, bukan berarti serangan CSRF tidak bisa dilakukan, si hacker masih bisa menembak endpoint secara paksa lewat CURL, hanya saja membutuhkan usaha ekstra jika ingin sukses.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From b836ee0dd696b2ac10c3774e70edf6261abfe4e9 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:26 +0700
Subject: [PATCH 096/522] New translations c-dockerize-golang.md (English)
---
en/content-en/C-dockerize-golang.md | 329 ++++++++++++++++++++++++++++
1 file changed, 329 insertions(+)
create mode 100644 en/content-en/C-dockerize-golang.md
diff --git a/en/content-en/C-dockerize-golang.md b/en/content-en/C-dockerize-golang.md
new file mode 100644
index 000000000..9d7589909
--- /dev/null
+++ b/en/content-en/C-dockerize-golang.md
@@ -0,0 +1,329 @@
+# C.35. Dockerize Aplikasi Golang
+
+Pada chapter ini kita akan praktek men-*dockerize* aplikasi Go, membungkus aplikasi hello world sebagai docker image untuk kemudian di jalankan sebagai container.
+
+Kita tidak akan membahas docker secara detail ya, hanya pengenalannya saja. Untuk teman-teman yang tertarik belajar docker secara komprehensif mulai dari awal, hingga masuk ke docker compose kemudian kubernetes *from zero to hero*, bisa *enroll* course Udemy [Praktis Belajar Docker dan Kubernetes untuk Pemula](https://www.udemy.com/course/praktis-belajar-docker-dan-kubernetes-untuk-pemula/) berikut.
+
+
+
+
+
+## C.35.1. Prerequisities
+
+Pastikan Docker Engine ter-*install* untuk pengguna Windows atau MacOS. Untuk pengguna Linux/Unix, install saja Docker Engine. Silakan merujuk ke laman panduan instalasi https://docs.docker.com/get-docker/ jika belum meng-*install* Docker-nya.
+
+## C.35.2. Istilah Dalam Docker
+
+#### • Container
+
+Container adalah sebuah environment ter-isolasi, merupakan bentuk virtualisasi yang lebih kecil dan ringan dibanding VM (Virtual Machine). Virtualisasi pada container disebut dengan *Containerization*.
+
+#### • Docker Container
+
+Docker container adalah sebuah container yang di-manage oleh Docker Engine.
+
+#### • Docker Engine
+
+Docker engine merupakan *daemon* yang bertugas untuk manajemen container-container.
+
+#### • Docker Image
+
+Docker Image adalah sebuah file yang di-*generate* oleh docker, yang file tersebut nantinya digunakan untuk basis pembuatan dan eksekusi container.
+
+#### • Containerize dan Dockerize
+
+Containerize merupakan istilah terhadap aplikasi yang di-*build* ke bentuk Image. Sedangkan Dockerize merupakan istilah untuk containerize menggunakan Docker. Perlu diketahui bahwa penyedia container tidak hanya Docker saja, ada banyak engine container lainnya yang bisa dipergunakan.
+
+## C.35.3. Pembuatan Aplikasi Hello World
+
+Sebelum masuk ke aspek docker, mari kita siapkan dulu aplikasi web sederhana yang nantinya akan di-*build* ke bentuk Image. O iya, jangan lupa inisialisasi project-nya ya menggunakan perintah `go mod init hello-world`.
+
+Siapkan folder project baru dengan isi file `main.go`. Tulis kode berikut.
+
+
+```go
+package main
+
+import (
+ "log"
+ "net/http"
+ "os"
+)
+
+func main() {
+ // ...
+}
+```
+
+Dalam fungsi main, tambahkan statement untuk ambil informasi port dari *env var* `PORT`, dan informasi id instance dari *env var* `INSTANCE_ID`. Kedua variabel ini akan dipergunakan dalam web server yang akan kita buat.
+
+* *env var* `PORT` digunakan sebagai port web server.
+* *env var* `INSTANCE_ID` untuk *instance identifier*, hanya sebagai info saja dan variabel ini opsional.
+
+```go
+port := os.Getenv("PORT")
+if port == "" {
+ log.Fatal("PORT env is required")
+}
+
+instanceID := os.Getenv("INSTANCE_ID")
+```
+
+Selanjutnya siapkan satu buah *multiplexor* dengan isi satu buah route `GET /`, yang *handler*-nya mengembalikan sebuah pesan teks `Hello world`. Jika *env var* `INSTANCE_ID` di set, maka akan ditampilkan isinya sebagai bagian dari respon handler ini.
+
+```go
+mux := http.NewServeMux()
+mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" {
+ http.Error(w, "http method not allowed", http.StatusBadRequest)
+ return
+ }
+
+ text := "Hello world"
+ if instanceID != "" {
+ text = text + ". From " + instanceID
+ }
+
+ w.Write([]byte(text))
+})
+```
+
+Lanjut siapkan objek `http.Server`-nya, gunakan objek `mux` yang sudah dibuat sebagai basis handler web server, kemudian start web server-nya.
+
+```go
+server := new(http.Server)
+server.Handler = mux
+server.Addr = "0.0.0.0:" + port
+
+log.Println("server starting at", server.Addr)
+err := server.ListenAndServe()
+if err != nil {
+ log.Fatal(err.Error())
+}
+```
+
+## C.35.4. Testing Aplikasi Hello World
+
+Kita akan coba test aplikasi hello world yang baru dibuat. Untuk Windows gunakan command berikut.
+
+```bat
+set PORT=8080
+set INSTANCE_ID="my first instance"
+go run main.go
+```
+
+Untuk sistem operasi non-Windows, gunakan:
+
+```sh
+export PORT=8080
+export INSTANCE_ID="my first instance"
+go run main.go
+```
+
+
+
+Ok bisa dilihat aplikasi berjalan sesuai harapan. Selanjutnya kita akan *dockerize* aplikasi hello world ini.
+
+## C.35.5. Pembuatan Dockerfile
+
+Aplikasi hello world yang sudah dibuat akan kita *build* ke bentuk Docker Image untuk kemudian di-*run* sebagai container. Nah untuk pembuatan Image, salah satu syaratnya adalah mempersiapkan `Dockerfile`.
+
+Jadi sekarang buat file baru bernama `Dockerfile`, lalu isi dengan kode berikut.
+
+```dockerfile
+FROM golang:alpine
+
+RUN apk update && apk add --no-cache git
+
+WORKDIR /app
+
+COPY . .
+
+RUN go mod tidy
+
+RUN go build -o binary
+
+ENTRYPOINT ["/app/binary"]
+```
+
+Berikut adalah penjelasan per baris dari kode di atas.
+
+#### 1. Statement `FROM golang:alpine`
+
+Keyword `FROM` ini digunakan untuk inisialisasi *build stage* dan juga menentukan basis Image yang digunakan. Informasi `golang:alpine` di sini adalah basis image yang dimaksud, yaitu image bernama `golang` dengan tag bernama `alpine` yang tersedia di laman officila Docker Hub Golang https://hub.docker.com/_/golang.
+
+Dalam Image `golang:alpine` sudah tersedia beberapa utilitas untuk keperluan *build* aplikasi Golang. Image `golang:alpine` basisnya adalah Alpine OS.
+
+#### 2. Statement `RUN apk update && apk add --no-cache git`
+
+Keyword `RUN` digunakan untuk menjalankan shell comamnd. Argument setelahnya, yaitu `apk update && apk add --no-cache git` akan dijalankan di Image `golang:alpine` yang sudah di-set sebelumnya. Command tersebut merupakan command Alpine OS yang kurang lebih gunanya adalah berikut:
+
+* Command `apk update` digunakan untuk meng-*update* *index packages* pada OS.
+* Command `apk add --no-cache git` digunakan untuk meng-*install* Git. Kebetulan pada basis image `golang:alpine` *by default* Git adalah tidak tersedia. Jadi harus di-*install* terlebih dahulu. Git ini nantinya digunakan sewaktu `go get` dependensi lewat command `go mod tidy`. Meskipun pada contoh aplikasi hello world tidak menggunakan dependensi eksternal, *install* saja tidak apa.
+
+#### 3. Statement `WORKDIR /app`
+
+Digunakan untuk menentukan *working directory* yang pada konteks ini adalah `/app`. Statement ini menjadikan semua statement `RUN` di bawahnya akan dieksekusi pada *working directory*.
+
+#### 4. Statement `COPY . .`
+
+Digunakan untuk meng-*copy* file pada argument pertama yaitu `.` yang merepresentasikan direktori yang aktif pada host atau komputer kita (yang isinya file `main.go`, `go.mod`, dan `Dockerfile`), untuk kemudian di-*paste* ke dalam Image ke *working directory* yaitu `/app`.
+
+Dengan ini isi `/app` adalah sama persis seperti isi folder project hello world.
+
+#### 5. Statement `RUN go mod tidy`
+
+Digunakan untuk validasi dependensi, dan meng-automatisasi proses *download* jika dependensi yang ditemukan belum ter-*download*. Command ini akan mengeksekusi `go get` jika butuh untuk unduh dependensi, makanya kita perlu install Git.
+
+#### 6. Statement `RUN go build -o binary`
+
+Command `go build` digunakan untuk build *binary* atau *executable* dari kode program Go. Dengan ini *source code* dalam *working directory* akan di-*build* ke *executable* dengan nama `binary`.
+
+#### 7. Statement `ENTRYPOINT ["/app/binary"]`
+
+Statement ini digunakan untuk menentukan entrypoint container sewaktu dijalankan. Jadi khusus statement `ENTRYPOINT` ini pada contoh di atas adalah yang efeknya baru kelihatan ketika Image di-*run* ke container. Sewaktu proses *build* aplikasi ke Image maka efeknya belum terlihat.
+
+Dengan statement tersebut nantinya sewaktu container jalan, maka executable `binary` yang merupakan aplikasi hello world kita, itu dijalankan di container sebagai entrypoint.
+
+Ok, file `Dockerfile` sudah siap, mari kita lanjut ke proses *build* dan *start container*.
+
+## C.35.6. *Build Image* dan *Create Container*
+
+#### • Build Image
+
+Pertama masuk ke direktori folder project, lalu jalankan *command* `docker build` berikut.
+
+```bash
+cd folder-project
+docker build -t my-image-hello-world .
+```
+
+*Command* di atas akan melakukan proses *build* Image pada file yang ada di dalam `.` yang merupakan isi folder project. Project akan di-*build* ke sebuah Image dengan nama adalah `my-image-hello-world`. Flag `-t` digunakan untuk menentukan nama Image.
+
+Kurang lebih outputnya seperti gambar berikut. O iya gunakan *command* `docker images` untuk menampilkan list semua image yang ada di lokal.
+
+
+
+#### • Create Container
+
+Image sudah siap, sekarang mari kita buat container baru menggunakan basis image `my-image-hello-world`. *Command*-nya kurang lebih berikut:
+
+```bash
+docker container create --name my-container-hello-world -e PORT=8080 -e INSTANCE_ID="my first instance" -p 8080:8080 my-image-hello-world
+```
+
+Command di atas akan menjalankan sebuah proses yang isinya kurang lebih berikut:
+
+1. Buat container baru dengan nama `my-container-hello-world`.
+2. Flag `--name` digunakan untuk menentukan nama container.
+3. Sewaktu pembuatan container, *env var* `PORT` di-set dengan nilai adalah `8080`.
+4. *env var* `INSTANCE_ID` juga di set di-set, nilai adalah teks `my first instance`.
+5. Flag `-e` digunakan untuk menge-*set* *env var*. Flag ini bisa dituliskan banyak kali sesuai kebutuhan.
+6. Kemudian port `8080` yang ada di luar network docker (yaitu di host/laptop/komputer kita) di map ke port `8080` yang ada di dalam container.
+7. Flag `-p` digunakan untuk mapping port antara host dan container. Bagian ini biasa disebut dengan *expose port*.
+8. Proses pembuatan container dilakukan dengan Image `my-image-hello-world` digunakan sebagai basis image.
+
+Semoga cukup jelas penjabaran di atas. Setelah container berhasil dibuat, cek menggunakan *command* `docker container ls -a` untuk menampilkan list semua container baik yang sedang running maupun tidak.
+
+
+
+#### • Start Container
+
+Ok, sekarang container juga sudah dibuat, lanjut untuk *start* container tersebut, caranya menggunakan command `docker container start`. Jika sudah, coba cek di browser aplikasi web hello world, harusnya sudah bisa diakses.
+
+```bash
+docker container start my-container-hello-world
+docker container ls
+```
+
+
+
+Bisa dilihat, sekarang aplikasi web hello world sudah bisa diakses dari host/komputer yang aplikasi tersebut running dalam container docker.
+
+Jika mengalami error saat start container, bisa jadi karena port `8080` sudak dialokasikan untuk proses lain. Solusi untuk kasus ini adalah *kill* saja proses yang menggunakan port tersebut, atau *rebuild image* dan *create container* ulang tapi menggunakan port lainnya, selain `8080`.
+
+O iya, pada image di atas juga bisa dilihat penggunaan *command* `docker container ls` untuk memunculkan list container yang sedand *running* atau aktif. Untuk menampilkan semua container (aktif maupun non-aktif), cukup dengan menambahkan flag `-a` atau `--all`.
+
+#### • Stop Container
+
+Untuk stop container bisa dengan *command* `docker container stop `.
+
+```bash
+docker container stop my-container-hello-world
+docker container ls
+```
+
+#### • Hapus Container
+
+Untuk hapus container bisa dengan *command* `docker container rm `.
+
+```bash
+docker container rm my-container-hello-world
+docker container ls
+```
+
+#### • Hapus Image
+
+Untuk hapus image bisa dengan *command* `docker image rm `. O iya, untuk penghapusan image, harus dipastikan terlebih dahulu tidak ada container yang running menggunakan basis image yang ingin dihapus.
+
+```bash
+docker image rm my-image-hello-world
+docker images
+```
+
+## C.35.7. Run Container
+
+Untuk run container sebenarnya ada dua cara ya, yang pertama seperti contoh di atas dengan membuat container nya terlebih dahulu menggunakan *command* `docker container create` kemudian di start menggunakan *command* `docker container start`.
+
+Atau bisa juga menggunakan *command* `docker run`. *Command* ini akan membuat container baru kemudian otomatis menjalankannya. Tapi saya sampaikan bahwa lewat cara ini tidak ada pengecekan apakah container sudah dibuat atau tidak sebelumnya, pasti akan dibuat container baru.
+
+Mungkin perbandingannya seperti ini:
+
+#### • Jalankan container lewat `create` lalu `start`
+
+```bash
+docker container create --name my-container-hello-world -e PORT=8080 -e INSTANCE_ID="my first instance" -p 8080:8080 my-image-hello-world
+docker container start my-container-hello-world
+```
+
+#### • Jalankan container lewat `run`
+
+```bash
+docker container run --name my-container-hello-world -e PORT=8080 -e INSTANCE_ID="my first instance" -p 8080:8080 my-image-hello-world
+```
+
+Bisa dilihat bukan bedanya, hanya sedikit.
+
+O iya, khusus untuk *command* `docker run` biasanya dijalankan dengan tambahan beberapa flag agar lebih mudah kontrol-nya, yaitu ditambahkan flag `--rm` dan `-it`.
+
+```bash
+docker container run --name my-container-hello-world --rm -it -e PORT=8080 -e INSTANCE_ID="my first instance" -p 8080:8080 my-image-hello-world
+```
+
+#### • Flag `--rm`
+
+Flag ini digunakan untuk meng-automatisasi proses penghapusan container sewaktu container tersebut di stop. Jadi kita tidak perlu delete manual pakai `docker container rm`. Hal ini sangat membantu karena *command* `docker run` akan membuat container baru setiap dijalankan. Tapi sebenarnya pada contoh sebelumnya kita tidak perlu khawatir akan dibuat container baru karena sudah ada flag `--name`. Flag tersebut digunakan untuk menentukan nama container, yang di mana nama container harus unik. Jadi kalau ada duplikasi pasti langsung error. Nah dari sini berarti kalau temen-temen tidak pakai `--name` sangat dianjurkan paka `--rm` dalam penerapan `docker run`.
+
+#### • Flag `-it`
+
+Flag ini merupakan flag gabungan antara `-i` yang digunakan untuk meng-enable *interactive mode* dan `-t` untuk *enable* `TTY`. Dengan ini kita bisa masuk ke mode interaktif yang di mana jika kita terminate atau kill command menggunakan `CTRL + C` atau `CMD + C` (untuk mac), maka otomatis container akan di stop.
+
+Nah dengan menggabungkan flag `--rm` dan flag `-it` kita bisa dengan mudah stop kemudian hapus container.
+
+Selain itu ada juga flag yang mungkin penting yaitu `-d` atau dettach. Flag ini bisa digabung dengan `-it`. Dettach adalah mode di mana ketika command `docker run` dijalankan, command akan langsung selesai. Dari sini untuk stop container berarti harus menggunakan command `docker stop`. Contoh:
+
+```bash
+docker container run --name my-container-hello-world --rm -itd -e PORT=8080 -e INSTANCE_ID="my first instance" -p 8080:8080 my-image-hello-world
+docker container stop my-container-hello-world
+```
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 1d08f53a73faa7005ab2b988ca8d3ec95c20269a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:27 +0700
Subject: [PATCH 097/522] New translations c-echo-routing.md (English)
---
en/content-en/C-echo-routing.md | 288 ++++++++++++++++++++++++++++++++
1 file changed, 288 insertions(+)
create mode 100644 en/content-en/C-echo-routing.md
diff --git a/en/content-en/C-echo-routing.md b/en/content-en/C-echo-routing.md
new file mode 100644
index 000000000..df5e72736
--- /dev/null
+++ b/en/content-en/C-echo-routing.md
@@ -0,0 +1,288 @@
+# C.3. Echo Framework & Routing
+
+Pada chapter ini kita akan belajar cara mudah routing menggunakan [Echo Framework](https://echo.labstack.com/).
+
+Mulai chapter **C1** hingga **C6** kita akan mempelajari banyak aspek dalam framework Echo dan mengkombinasikannya dengan beberapa library lain.
+
+# C.3.1 Echo Framework
+
+Echo adalah framework bahasa golang untuk pengembangan aplikasi web. Framework ini cukup terkenal di komunitas. Echo merupakan framework besar, di dalamnya terdapat banyak sekali dependensi.
+
+Salah satu dependensi yang ada di dalamnya adalah router, dan pada chapter ini kita akan mempelajarinya.
+
+Dari banyak routing library yang sudah penulis gunakan, hampir seluruhnya mempunyai kemiripan dalam hal penggunaannya, cukup panggil fungsi/method yang dipilih (biasanya namanya sama dengan HTTP Method), lalu sisipkan rute pada parameter pertama dan handler pada parameter kedua.
+
+Berikut contoh sederhana penggunaan echo framework.
+
+```go
+r := echo.New()
+r.GET("/", handler)
+r.Start(":9000")
+```
+
+Sebuah objek router `r` dicetak lewat `echo.New()`. Lalu lewat objek router tersebut, dilakukan registrasi rute untuk `/` dengan method GET dan handler adalah closure `handler`. Terakhir, dari objek router di-start-lah sebuah web server pada port 9000.
+
+> Echo router mengadopsi konsep [radix tree](https://en.wikipedia.org/wiki/Radix_tree), membuat performa lookup nya begitu cepat. Tak juga itu, pemanfaatan sync pool membuat penggunaan memory lebih hemat, dan aman dari GC overhead.
+
+## C.3.2. Praktek
+
+Mari kita pelajari lebih lanjut dengan praktek langsung. Buat folder proyek baru, buat `main.go`, isi dengan kode berikut, kemudian jalankan aplikasi.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/labstack/echo"
+ "net/http"
+ "strings"
+)
+
+type M map[string]interface{}
+
+func main() {
+ r := echo.New()
+
+ r.GET("/", func(ctx echo.Context) error {
+ data := "Hello from /index"
+ return ctx.String(http.StatusOK, data)
+ })
+
+ r.Start(":9000")
+}
+```
+
+Kode di atas adalah contoh sederhana penerapan echo router.
+
+
+
+Routing dengan memanfaatkan package `net/http` dalam penerapannya adalah menggunakan `http.HandleFunc()` atau `http.Handle()`. Berbeda dengan Echo, routingnya adalah method-based, tidak hanya endpoint dan handler yang di-registrasi, method juga.
+
+Statement `echo.New()` mengembalikan objek mux/router. Pada kode di atas rute `/` dengan method `GET` di-daftarkan. Selain `r.GET()` ada banyak lagi method lainnya, semua method dalam [spesifikasi REST](https://en.wikipedia.org/wiki/Representational_state_transfer) seperti PUT, POST, dan lainnya bisa digunakan.
+
+Handler dari method routing milik echo membutuhkan satu argument saja, dengan tipe adalah `echo.Context`. Dari argumen tersebut objek `http.ResponseWriter` dan `http.Request` bisa di-akses. Namun kedua objek tersebut akan jarang kita gunakan karena `echo.Context` memiliki banyak method yang beberapa tugasnya sudah meng-cover operasi umum yang biasanya kita lakukan lewat objek request dan response, di antara seperti:
+
+ - Render output (dalam bentuk html, plain text, json, atau lainnya).
+ - Parsing request data (json payload, form data, query string).
+ - URL Redirection.
+ - ... dan lainnya.
+
+> Untuk mengakses objek `http.Request` gunakan `ctx.Request()`. Sedang untuk objek `http.ResponseWriter` gunakan `ctx.Response()`.
+
+Salah satu alasan lain kenapa penulis memilih framework ini, adalah karena desain route-handler-nya menarik. Dalam handler cukup kembalikan objek error ketika memang ada kesalahan terjadi, sedangkan jika tidak ada error maka kembalikan nilai `nil`.
+
+Ketika terjadi error pada saat mengakses endpoint, idealnya [HTTP Status](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) error dikembalikan sesuai dengan jenis errornya. Tapi terkadang juga ada kebutuhan dalam kondisi tertentu `http.StatusOK` atau status 200 dikembalikan dengan disisipi informasi error dalam response body-nya. Kasus sejenis ini menjadikan standar error reporting menjadi kurang bagus. Pada konteks ini echo unggul menurut penulis, karena default-nya semua error dikembalikan sebagai response dalam bentuk yang sama.
+
+Method `ctx.String()` dari objek context milik handler digunakan untuk mempermudah rendering data string sebagai output. Method ini mengembalikan objek error, jadi bisa digunakan langsung sebagai nilai balik handler. Argumen pertama adalah http status dan argumen ke-2 adalah data yang dijadikan output.
+
+## C.3.3. Response Method milik `ctx`
+
+Selain `ctx.String()` ada banyak method sejenis lainnya, berikut selengkapnya.
+
+#### • Method `.String()`
+
+Digunakan untuk render plain text sebagai output (isi response header `Content-Type` adalah `text/plain`). Method ini tugasnya sama dengan method `.Write()` milik objek `http.ResponseWriter`.
+
+```go
+r.GET("/index", func(ctx echo.Context) error {
+ data := "Hello from /index"
+ return ctx.String(http.StatusOK, data)
+})
+```
+
+#### • Method `.HTML()`
+
+Digunakan untuk render html sebagai output. Isi response header `Content-Type` adalah `text/html`.
+
+```go
+r.GET("/html", func(ctx echo.Context) error {
+ data := "Hello from /html"
+ return ctx.HTML(http.StatusOK, data)
+})
+```
+
+#### • Method `.Redirect()`
+
+Digunakan untuk redirect, pengganti `http.Redirect()`.
+
+```go
+r.GET("/index", func(ctx echo.Context) error {
+ return ctx.Redirect(http.StatusTemporaryRedirect, "/")
+})
+```
+
+#### • Method `.JSON()`
+
+Digunakan untuk render data JSON sebagai output. Isi response header `Content-Type` adalah `application/json`.
+
+```go
+r.GET("/json", func(ctx echo.Context) error {
+ data := M{"Message": "Hello", "Counter": 2}
+ return ctx.JSON(http.StatusOK, data)
+})
+```
+
+## C.3.4. Parsing Request
+
+Echo juga menyediakan beberapa method untuk keperluan parsing request, di antaranya:
+
+#### • Parsing Query String
+
+Method `.QueryParam()` digunakan untuk mengambil data pada query string request, sesuai dengan key yang diinginkan.
+
+```go
+r.GET("/page1", func(ctx echo.Context) error {
+ name := ctx.QueryParam("name")
+ data := fmt.Sprintf("Hello %s", name)
+
+ return ctx.String(http.StatusOK, data)
+})
+```
+
+Test menggunakan curl:
+
+```bash
+curl -X GET http://localhost:9000/page1?name=grayson
+```
+
+#### • Parsing URL Path Param
+
+Method `.Param()` digunakan untuk mengambil data path parameter sesuai skema rute.
+
+```go
+r.GET("/page2/:name", func(ctx echo.Context) error {
+ name := ctx.Param("name")
+ data := fmt.Sprintf("Hello %s", name)
+
+ return ctx.String(http.StatusOK, data)
+})
+```
+
+Bisa dilihat, terdapat `:name` pada pendeklarasian rute. Nantinya url apapun yang ditulis sesuai skema di-atas akan bisa diambil path parameter-nya. Misalkan `/page2/halo` maka `ctx.Param("name")` mengembalikan string `halo`.
+
+Test menggunakan curl:
+
+```bash
+curl -X GET http://localhost:9000/page2/grayson
+```
+
+#### • Parsing URL Path Param dan Setelahnya
+
+Selain mengambil parameter sesuai spesifik path, kita juga bisa mengambil data **parameter path dan setelahnya**.
+
+```go
+r.GET("/page3/:name/*", func(ctx echo.Context) error {
+ name := ctx.Param("name")
+ message := ctx.Param("*")
+
+ data := fmt.Sprintf("Hello %s, I have message for you: %s", name, message)
+
+ return ctx.String(http.StatusOK, data)
+})
+```
+
+Statement `ctx.Param("*")` mengembalikan semua path sesuai dengan skema url-nya. Misal url adalah `/page3/tim/a/b/c/d/e/f/g/h` maka yang dikembalikan adalah `a/b/c/d/e/f/g/h`.
+
+Test menggunakan curl:
+
+```bash
+curl -X GET http://localhost:9000/page3/tim/need/some/sleep
+```
+
+#### • Parsing Form Data
+
+Data yang dikirim sebagai request body dengan jenis adalah Form Data bisa di-ambil dengan mudah menggunakan `ctx.FormValue()`.
+
+```go
+r.POST("/page4", func(ctx echo.Context) error {
+ name := ctx.FormValue("name")
+ message := ctx.FormValue("message")
+
+ data := fmt.Sprintf(
+ "Hello %s, I have message for you: %s",
+ name,
+ strings.Replace(message, "/", "", 1),
+ )
+
+ return ctx.String(http.StatusOK, data)
+})
+```
+
+Test menggunakan curl:
+
+```bash
+curl -X POST -F name=damian -F message=angry http://localhost:9000/page4
+```
+
+Pada chapter selanjutnya kita akan belajar teknik parsing request data yang lebih advance.
+
+## C.3.5. Penggunaan `echo.WrapHandler` Untuk Routing Handler Bertipe `func(http.ResponseWriter,*http.Request)` atau `http.HandlerFunc`
+
+Echo bisa dikombinasikan dengan handler ber-skema *NON-echo-handler* seperti `func(http.ResponseWriter,*http.Request)` atau `http.HandlerFunc`.
+
+Caranya dengan memanfaatkan fungsi `echo.WrapHandler` untuk mengkonversi handler tersebut menjadi echo-compatible. Lebih jelasnya silakan lihat kode berikut.
+
+```go
+var ActionIndex = func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("from action index"))
+}
+
+var ActionHome = http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("from action home"))
+ },
+)
+
+var ActionAbout = echo.WrapHandler(
+ http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("from action about"))
+ },
+ ),
+)
+
+func main() {
+ r := echo.New()
+
+ r.GET("/index", echo.WrapHandler(http.HandlerFunc(ActionIndex)))
+ r.GET("/home", echo.WrapHandler(ActionHome))
+ r.GET("/about", ActionAbout)
+
+ r.Start(":9000")
+}
+```
+
+Untuk routing handler dengan skema `func(http.ResponseWriter,*http.Request)`, maka harus dibungkus dua kali, pertama menggunakan `http.HandlerFunc`, lalu dengan `echo.WrapHandler`.
+
+Sedangkan untuk handler yang sudah bertipe `http.HandlerFunc`, bungkus langsung menggunakan `echo.WrapHandler`.
+
+## C.3.6. Routing Static Assets
+
+Cara routing static assets di echo sangatlah mudah. Gunakan method `.Static()`, isi parameter pertama dengan prefix rute yang di-inginkan, dan parameter ke-2 dengan path folder tujuan.
+
+Buat sub folder dengan nama `assets` dalam folder project. Dalam folder tersebut buat sebuah file `layout.js`, isinya bebas.
+
+Pada `main.go` tambahkan routing static yang mengarah ke path folder `assets`.
+
+```go
+r.Static("/static", "assets")
+```
+
+Jalankan aplikasi, lalu coba akses `http://localhost:9000/static/layout.js`.
+
+
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 0e5f6e440ddeaaeb94176bb48556984077dac6b0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:28 +0700
Subject: [PATCH 098/522] New translations c-echo-template-rendering.md
(English)
---
en/content-en/C-echo-template-rendering.md | 146 +++++++++++++++++++++
1 file changed, 146 insertions(+)
create mode 100644 en/content-en/C-echo-template-rendering.md
diff --git a/en/content-en/C-echo-template-rendering.md b/en/content-en/C-echo-template-rendering.md
new file mode 100644
index 000000000..0f24f9f49
--- /dev/null
+++ b/en/content-en/C-echo-template-rendering.md
@@ -0,0 +1,146 @@
+# C.7. Template Rendering in Echo
+
+Pada chapter ini kita akan belajar cara render template html pada aplikasi yang routingnya menggunakan echo.
+
+Pada dasarnya proses parsing dan rendering template tidak di-handle oleh echo sendiri, melainkan oleh API dari package `html/template`. Jadi bisa dibilang cara render template di echo adalah sama seperti pada aplikasi yang murni menggunakan golang biasa, seperti yang sudah dibahas pada chapter [Template: Render HTML Template](/B-template-render-html.html), [Template: Render Partial HTML Template](/B-template-render-partial-html.html), [Template: Render Specific HTML Template](/B-render-specific-html.html), dan [Template: Render HTML String](/B-render-html-string.html).
+
+Echo menyediakan satu fasilitas yang bisa kita manfaatkan untuk standarisasi rendering template. Cara penggunaannya, dengan meng-override default `.Renderer` property milik echo menggunakan objek cetakan struct, yang di mana pada struct tersebut harus ada method bernama `.Render()` dengan skema sesuai dengan kebutuhan echo. Nah, di dalam method `.Render()` inilah kode untuk parsing dan rendering template ditulis.
+
+## C.7.1. Praktek
+
+Agar lebih mudah dipahami, mari langsung kita praktekan. Siapkan sebuah project, import package yang dibutuhkan.
+
+```go
+package main
+
+import (
+ "github.com/labstack/echo"
+ "html/template"
+ "io"
+ "net/http"
+)
+
+type M map[string]interface{}
+```
+
+Buat sebuah struct bernama `Renderer`, struct ini mempunyai 3 buah property dan 2 buah method.
+
+```go
+type Renderer struct {
+ template *template.Template
+ debug bool
+ location string
+}
+```
+
+Berikut adalah tugas dan penjelasan mengenai ketiga property di atas.
+
+ - Property `.template` bertanggung jawab untuk parsing dan rendering template.
+ - Property `.location` mengarah ke path folder di mana file template berada.
+ - Property `.debug` menampung nilai bertipe `bool`.
+ - Jika `false`, maka parsing template hanya dilakukan sekali saja pada saat aplikasi di start. Mode ini sangat cocok untuk diaktifkan pada stage production.
+ - Sedangkan jika nilai adalah `true`, maka parsing template dilakukan tiap pengaksesan rute. Mode ini cocok diaktifkan untuk stage development, karena perubahan kode pada file html sering pada stage ini.
+
+Selanjutnya buat fungsi `NewRenderer()` untuk mempermudah inisialisasi objek renderer.
+
+```go
+func NewRenderer(location string, debug bool) *Renderer {
+ tpl := new(Renderer)
+ tpl.location = location
+ tpl.debug = debug
+
+ tpl.ReloadTemplates()
+
+ return tpl
+}
+```
+
+Siapkan dua buah method untuk struct renderer, yaitu `.ReloadTemplates()` dan `.Render()`.
+
+```go
+func (t *Renderer) ReloadTemplates() {
+ t.template = template.Must(template.ParseGlob(t.location))
+}
+
+func (t *Renderer) Render(
+ w io.Writer,
+ name string,
+ data interface{},
+ c echo.Context,
+) error {
+ if t.debug {
+ t.ReloadTemplates()
+ }
+
+ return t.template.ExecuteTemplate(w, name, data)
+}
+```
+
+Method `.ReloadTemplates()` bertugas untuk parsing template. Method ini wajib dipanggil pada saat inisialisasi objek renderer. Jika `.debug == true`, maka method ini harus dipanggil setiap kali rute diakses (jika tidak, maka perubahan pada view tidak akan muncul).
+
+Method `.Render()` berguna untuk render template yang sudah diparsing sebagai output. Method ini harus dibuat dalam skema berikut.
+
+```go
+// skema method Render()
+func (io.Writer, string, interface{}, echo.Context) error
+```
+
+Selanjutnya, buat echo router, override property renderer nya, dan siapkan sebuah rute.
+
+```go
+func main() {
+ e := echo.New()
+
+ e.Renderer = NewRenderer("./*.html", true)
+
+ e.GET("/index", func(c echo.Context) error {
+ data := M{"message": "Hello World!"}
+ return c.Render(http.StatusOK, "index.html", data)
+ })
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Saat pemanggilan `NewRenderer()` sisipkan path folder tempat file template html berada. Gunakan `./*.html` agar mengarah ke **semua file html pada current folder**.
+
+Buat file `index.html` dengan isi kode di bawah ini.
+
+```html
+
+
+
+
+
+
+ Message from index: {{.message}}!
+
+
+```
+
+Pada rute `/index`, sebuah variabel bernama `data` disiapkan, bertipe `map` dengan isi satu buah item. Data tersebut disisipkan pada saat view di-render, membuatnya bisa diakses dari dalam template html.
+
+Syntax `{{.message}}` artinya menampilkan isi property yang namanya adalah `message` dari current context (yaitu objek data yang disisipkan). Lebih jelasnya silakan baca kembali chapter [B. Template Actions & Variables](/B-template-actions-variables.html).
+
+Jalankan aplikasi untuk melihat hasilnya.
+
+
+
+## C.7.2. Render Parsial dan Spesifik Template
+
+Proses parsing dan rendering tidak di-handle oleh echo, melainkan menggunakan API dari `html/template`. Echo hanya menyediakan tempat untuk mempermudah pemanggilan fungsi rendernya. Nah dari sini berarti untuk render parsial, render spesifik template, maupun operasi template lainnya dilakukan seperti biasa, menggunakan `html/template`.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From a09b1109a3d123f02a8a175a8230f03ddb6e4205 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:29 +0700
Subject: [PATCH 099/522] New translations c-flag-parser.md (English)
---
en/content-en/C-flag-parser.md | 345 +++++++++++++++++++++++++++++++++
1 file changed, 345 insertions(+)
create mode 100644 en/content-en/C-flag-parser.md
diff --git a/en/content-en/C-flag-parser.md b/en/content-en/C-flag-parser.md
new file mode 100644
index 000000000..4d20e5e69
--- /dev/null
+++ b/en/content-en/C-flag-parser.md
@@ -0,0 +1,345 @@
+# C.9. CLI Flag Parser (Kingpin v2)
+
+Tidak jarang, sebuah aplikasi dalam eksekusinya membutuhkan argumen untuk disisipkan, entah itu mandatory atau tidak. Contohnya seperti berikut.
+
+```bash
+$ ./main --port=3000
+```
+
+Pada chapter ini kita akan belajar cara parsing argumen eksekusi aplikasi. Parsing sebenarnya bisa dilakukan dengan cukup memainkan property `os.Args`, akan tetapi pada pembelajaran kali ini kita akan menggunakan 3rd party library [github.com/alecthomas/kingpin](https://github.com/alecthomas/kingpin) untuk mempermudah pelaksanaannya.
+
+## C.9.1. Parsing Argument
+
+Kita akan buat aplikasi yang bisa menerima bentuk argument seperti berikut.
+
+```bash
+# $ ./main ArgAppName
+$ ./main "My Application" 4000
+```
+
+Argument `ArgAppName` mandatory, harus diisi, sedangkan argument `ArgPort` adalah opsional (ada nilai default-nya).
+
+OK, mari kita praktekan. Buat folder project baru dengan isi satu buah main file. Siapkan dua buah property untuk menampung `appName` dan `port`, dan satu buah fungsi `main()`.
+
+```go
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/labstack/echo"
+ "gopkg.in/alecthomas/kingpin.v2"
+)
+
+var (
+ argAppName = kingpin.Arg("name", "Application name").Required().String()
+ argPort = kingpin.Arg("port", "Web server port").Default("9000").Int()
+)
+
+func main() {
+ kingpin.Parse()
+
+ // more code here ...
+}
+```
+
+Statement `kingpin.Arg()` digunakan untuk menyiapkan objek penampung argument. Tulis nama argument sebagai parameter pertama, dan deskripsi argument sebagai parameter kedua. 2 Informasi tersebut nantinya akan muncul ketika flag `--help` digunakan.
+
+Untuk aplikasi yang memerlukan banyak argument, deklarasi variabel penampungnya harus dituliskan berurutan. Seperti contoh di atas `argAppName` merupakan argument pertama, dan `argPort` adalah argument kedua.
+
+Chain statement `kingpin.Arg()` dengan beberapa method yang tersedia sesuai dengan kebutuhan. Berikut adalah penjelasan dari 4 method yang digunakan di atas.
+
+ - Method `.Required()` membuat argument yang ditulis menjadi mandatory. Jika tidak disisipkan maka muncul error.
+ - Method `.String()` menandakan bahwa argument ditampung dalam tipe `string`.
+ - Method `.Default()` digunakan untuk menge-set default value dari argument. Method ini adalah kebalikan dari `.Required()`. Jika default value di-set maka argument boleh untuk tidak diisi. Objek penampung akan berisi default value.
+ - Method `.Int()` menandakan bahwa argument ditampung dalam tipe `int`.
+
+Perlu diketahui, dalam pendefinisian argument, penulisan statement-nya harus diakhiri dengan pemanggilan method `.String()`, `.Int()`, `.Bool()`, atau method tipe lainnya yang di-support oleh kingpin. Lebih jelasnya silakan cek [laman dokumentasi](https://godoc.org/github.com/alecthomas/kingpin#ArgClause).
+
+Mari kita selesaikan aplikasi, silakan tulis kode berikut dalam fungsi `main()`.
+
+```go
+appName := *argAppName
+port := fmt.Sprintf(":%d", *argPort)
+
+fmt.Printf("Starting %s at %s", appName, port)
+
+e := echo.New()
+e.GET("/index", func(c echo.Context) (err error) {
+ return c.JSON(http.StatusOK, true)
+})
+e.Logger.Fatal(e.Start(port))
+```
+
+Objek argument kingpin pasti bertipe pointer, maka *dereference* objek tersebut untuk mengambil nilai aslinya.
+
+Jalankan aplikasi, cek hasilnya.
+
+
+
+Bisa dilihat dari gambar di atas ketika flag `--help` dipanggil list semua argument muncul.
+
+## C.9.2. Penggunaan Kingpin Application Instance
+
+Dari yang sudah kita praktekan di atas, fungsi-fungsi diakses langsung dari package `kingpin`.
+
+```go
+kingpin.Arg("name", "Application name").Required().String()
+kingpin.Arg("port", "Web server port").Default("9000").Int()
+
+kingpin.Parse()
+```
+
+Kingpin menyediakan fasilitas untuk membuat *-what-so-called-* objek kingpin application. Lewat objek ini, semua fungsi yang biasa digunakan (seperti `.Arg()` atau `.Parse()`) bisa diakses sebagai method.
+
+Kelebihan menggunakan kingpin application, kita bisa buat custom handler untuk antisipasi error. Pada aplikasi yg sudah dibuat di atas, jika argument yang *required* tidak disisipkan dalam eksekusi binary, maka aplikasi langsung exit dan error muncul. Error sejenis ini bisa kita override jika menggunakan kingpin application.
+
+Berikut adalah contoh implementasinya.
+
+```go
+app = kingpin.New("App", "Simple app")
+argAppName = app.Arg("name", "Application name").Required().String()
+argPort = app.Arg("port", "Web server port").Default("9000").Int()
+
+command, err := app.Parse(os.Args[1:])
+if err != nil {
+ // handler error here ...
+}
+```
+
+Statement `kingpin.Arg()` diganti dengan `app.Arg()`. Juga, `kingpin.Parse()` diganti dengan `app.Parse()`, dengan pemanggilannya harus disisipkan `os.Args[1:]`.
+
+Manfaatkan objek `err` kembalian `app.Parse()` untuk membuat custom error handling. Atau bisa tetap gunakan default custom error handling milik kingpin, caranya dengan membungkus statement `app.Parse()` ke dalam `kingpin.MustParse()`.
+
+```go
+kingpin.MustParse(app.Parse(os.Args[1:]))
+```
+
+## C.9.3. Parsing Flag
+
+Flag adalah argument yang lebih terstruktur. Golang sebenarnya sudah menyediakan package `flag`, isinya API untuk parsing flag.
+
+ - Contoh argument:
+
+ ```bash
+ $ ./executable "My application" 4000
+ ```
+
+ - Contoh flag:
+
+ ```bash
+ $ ./executable --name="My application" --port=4000
+ $ ./executable --name "My application" --port 4000
+ $ ./executable --name "My application" -p 4000
+ ```
+
+Kita tetap menggunakan kingpin pada bagian ini. Pembuatan flag di kingpin tidak sulit, cukup gunakan `.Flag()` (tidak menggunakan `.Arg()`). Contohnya seperti berikut.
+
+```go
+app = kingpin.New("App", "Simple app")
+flagAppName = app.Flag("name", "Application name").Required().String()
+flagPort = app.Flag("port", "Web server port").Short('p').Default("9000").Int()
+
+kingpin.MustParse(app.Parse(os.Args[1:]))
+```
+
+Method `.Short()` digunakan untuk mendefinisikan short flag. Pada kode di atas, flag port bisa ditulis dalam bentuk `--port=value` ataupun `-p=value`.
+
+Penggunaan flag `--help` akan memunculkan keterangan mendetail tiap-tiap flag.
+
+> Flag bisa dikombinasikan dengan argument.
+
+
+
+## C.9.4. Parsing Command
+
+Command adalah bentuk yang lebih advance dari argument. Banyak command bisa dibuat, pendefinisian flag ataupun argument bisa dilakukan lebih spesifik, untuk masing-masing command.
+
+Di bagian ini kita akan buat sebuah aplikasi untuk simulasi manajemen user. Tiga buah command dipersiapkan dengan skema sebagai berikut.
+
+ - Command `add`
+ - Flag `--override`
+ - Argument `user`
+ - Command `update`
+ - Argument `old user`
+ - Argument `new user`
+ - Command `delete`
+ - Flag `--force`
+ - Argument `user`
+
+Mari kita praktekan, buat satu folder project baru. Buat satu file main, isi dengan kode berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "gopkg.in/alecthomas/kingpin.v2"
+)
+
+var app = kingpin.New("App", "Simple app")
+```
+
+Method `.Command()` digunakan untuk membuat command. Pembuatan argument dan flag masing-masing command bisa dilakukan seperti biasanya, dengan mengakses method `.Arg()` atau `.Flag()`, hanya saja pengaksesannya lewat objek command masing-masing. Contoh:
+
+```go
+var commandSomething = app.Command("something", "do something")
+var commandSomethingArgX = commandSomething.Flag("x", "arg x").String()
+var commandSomethingFlagY = commandSomething.Flag("y", "flag y").String()
+```
+
+OK, sekarang buat 3 command sesuai skema yang sudah disepakati di atas.
+
+ - Command add, beserta flag dan argument-nya.
+
+ ```go
+ var commandAdd = app.Command("add", "add new user")
+ var commandAddFlagOverride = commandAdd.Flag("override", "override existing user").
+ Short('o').Bool()
+ var commandAddArgUser = commandAdd.Arg("user", "username").Required().String()
+ ```
+
+- Command update, beserta argument-nya.
+
+ ```go
+ var commandUpdate = app.Command("update", "update user")
+ var commandUpdateArgOldUser = commandUpdate.Arg("old", "old username").Required().
+ String()
+ var commandUpdateArgNewUser = commandUpdate.Arg("new", "new username").Required().
+ String()
+ ```
+
+- Command delete, beserta flag dan argument-nya.
+
+ ```go
+ var commandDelete = app.Command("delete", "delete user")
+ var commandDeleteFlagForce = commandDelete.Flag("force", "force deletion").
+ Short('f').Bool()
+ var commandDeleteArgUser = commandDelete.Arg("user", "username").Required().
+ String()
+ ```
+
+Buat fungsi main, lalu di dalamnya siapkan action untuk masing-masing command. Gunakan method `.Action()` dengan parameter adalah fungsi ber-skema `func(*kingpin.ParseContext)error` untuk menambahkan action.
+
+Di akhir, tulis statement untuk parsing.
+
+```go
+func main() {
+ commandAdd.Action(func(ctx *kingpin.ParseContext) error {
+ // more code here ...
+ })
+
+ commandUpdate.Action(func(ctx *kingpin.ParseContext) error {
+ // more code here ...
+ })
+
+ commandDelete.Action(func(ctx *kingpin.ParseContext) error {
+ // more code here ...
+ })
+
+ kingpin.MustParse(app.Parse(os.Args[1:]))
+}
+```
+
+Berikut adalah isi masing-masing action dari ketiga command di atas.
+
+ - Action command `add`:
+
+ ```go
+ commandAdd.Action(func(ctx *kingpin.ParseContext) error {
+ user := *commandAddArgUser
+ override := *commandAddFlagOverride
+ fmt.Printf("adding user %s, override %t \n", user, override)
+
+ return nil
+ })
+ ```
+
+ - Action command `update`:
+
+ ```go
+ commandUpdate.Action(func(ctx *kingpin.ParseContext) error {
+ oldUser := *commandUpdateArgOldUser
+ newUser := *commandUpdateArgNewUser
+ fmt.Printf("updating user from %s %s \n", oldUser, newUser)
+
+ return nil
+ })
+ ```
+
+ - Action command `delete`:
+
+ ```go
+ commandDelete.Action(func(ctx *kingpin.ParseContext) error {
+ user := *commandDeleteArgUser
+ force := *commandDeleteFlagForce
+ fmt.Printf("deleting user %s, force %t \n", user, force)
+
+ return nil
+ })
+ ```
+
+Jalankan aplikasi untuk mengetes hasilnya.
+
+Tambahkan flag `--help` pada pemanggilan command untuk menampilkan help command terpilih.
+
+
+
+Atau gunakan `--help-long` dalam eksekusi binary, untuk menampilkan help yang mendetail (argument dan flag tiap command juga dimunculkan).
+
+
+
+## C.9.5. Command Action Tanpa Menggunakan `.Action()`
+
+Nilai balik statement `kingpin.MustParse()`, `kingpin.Parse()`, dan nilai balik pertama `app.Parse()` adalah sama, yaitu informasi command yang ditulis pada saat pemanggilan binary.
+
+Dari informasi command tersebut, bisa kita kembangkan untuk membuat handler masing-masing command. Dengan ini tak perlu menggunakan method `.Action()` untuk menulis handler command. Contoh prakteknya seperti berikut.
+
+```go
+commandInString := kingpin.MustParse(app.Parse(os.Args[1:]))
+switch commandInString {
+
+case commandAdd.FullCommand(): // add user
+ user := *commandAddArgUser
+ override := *commandAddFlagOverride
+ fmt.Printf("adding user %s, override %t \n", user, override)
+
+case commandUpdate.FullCommand(): // update user
+ oldUser := *commandUpdateArgOldUser
+ newUser := *commandUpdateArgNewUser
+ fmt.Printf("updating user from %s %s \n", oldUser, newUser)
+
+case commandDelete.FullCommand(): // delete user
+ user := *commandDeleteArgUser
+ force := *commandDeleteFlagForce
+ fmt.Printf("deleting user %s, force %t \n", user, force)
+
+}
+```
+
+## C.9.6. Advanced Command Line Application
+
+Pembahasan di atas fokus tentang bagaiamana cara parsing argument, flag, dan command yang disisipkan sewaktu eksekusi aplikasi. Aplikasi yang dieksekusi sendiri bisa berupa command-line-based ataupun web-based.
+
+Jika pembaca ingin membuat aplikasi command line, penggunaan kingpin cukup membantu dalam proses pengembangan, tapi akan lebih mudah lagi jika menggunakan 3rd party library [Cobra](https://github.com/spf13/cobra).
+
+Cobra merupakan library yang dirancang khusus untuk development aplikasi berbasis command line. Library ini dibuat oleh author yang juga membuat kingpin.
+
+---
+
+ - [Kingpin](https://github.com/alecthomas/kingpin), by Alec Thomas, MIT license
+ - [Cobra](https://github.com/alecthomas/cobra), by Alec Thomas, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 2f26454b9e0c38e65b04adfda153f263dc017893 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:29 +0700
Subject: [PATCH 100/522] New translations c-go-project-layout.md (English)
---
en/content-en/C-go-project-layout.md | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 en/content-en/C-go-project-layout.md
diff --git a/en/content-en/C-go-project-layout.md b/en/content-en/C-go-project-layout.md
new file mode 100644
index 000000000..73bd4fa3a
--- /dev/null
+++ b/en/content-en/C-go-project-layout.md
@@ -0,0 +1,22 @@
+# C.1. Project Layout
+
+Pada chapter ini kita akan belajar cara melakukan pertukaran data lewat FTP (File Transfer Protocol) menggunakan Golang.
+
+> Definisi mengenai FTP sendiri adalah sebuah protokol network standar yang digunakan untuk pertukaran atau transfer data antar client dan server.
+
+Sebelum memulai, ada satu hal penting yang perlu dipersiapkan, yaitu sebuah server dengan FTP server ter-install. Jika tidak ada, bisa menggunakan library [ftpd](https://github.com/goftp/ftpd) untuk set up ftp server di local (untuk keperluan belajar).
+
+Dalam server tersebut, siapkan beberapa file dan folder dengan struktur sebagai berikut.
+
+
+
+ - File `test1.txt`, isi dengan apapun.
+ - File `test2.txt`, isi dengan apapun.
+ - File `somefolder/test3.txt`, isi dengan apapun.
+ - File `movie.mp4`, gunakan file seadanya.
+
+Library FTP client yang kita gunakan adalah [github.com/jlaffaye/ftp](https://github.com/jlaffaye/ftp).
+
+---
+
+
From 3d776228d8c4a30c17434a37caefb38656a867c0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:30 +0700
Subject: [PATCH 101/522] New translations c-golang-aws-s3.md (English)
---
en/content-en/C-golang-aws-s3.md | 446 +++++++++++++++++++++++++++++++
1 file changed, 446 insertions(+)
create mode 100644 en/content-en/C-golang-aws-s3.md
diff --git a/en/content-en/C-golang-aws-s3.md b/en/content-en/C-golang-aws-s3.md
new file mode 100644
index 000000000..dc2c37608
--- /dev/null
+++ b/en/content-en/C-golang-aws-s3.md
@@ -0,0 +1,446 @@
+# C.38. Amazon S3 (Simple Storage Service)
+
+Pada bab ini kita akan belajar untuk membuat koneksi ke Amazon S3 menggunakan Golang. Mulai dari cara membuat bucket di S3, melihat semua daftar bucket di S3, melihat semua object/file yang ada di dalam sebuah bucket S3, serta mengupload dan mendownload file dari S3 bucket.
+
+Kita mulai bahasan ini dengan mengenal apa itu Amazon S3 dan beberapa istilah yang berkaitan.
+
+## C.38.1 Apa itu Amazon Simple Storage Service (S3)?
+
+Pada dasarnya Simple Storage Service (S3) adalah layanan penyimpanan file/object yang dimiliki oleh Amazon Web Service (AWS). Dengan menggunakan Amazon S3, kita bisa menyimpan dan melindungi object untuk berbagai kebutuhan sistem kita. Ringkasnya, kita bisa menganalogikan Amazon S3 sebagai harddisk/storage online yang bisa kita akses selama kita terhubung dengan internet.
+
+> AWS menyediakan layanan [Free Tier](https://aws.amazon.com/free/), dengan kita bisa memanfaatkan service S3 secara gratis selama 12 bulan.
+
+## C.39.2 Beberapa istilah terkait Amazon S3
+
+Beberapa istilah yang biasa kita temukan saat kita bekerja dengan Amazon S3 antara lain:
+
+#### • Bucket
+
+Bucket adalah wadah untuk object bisa disimpan ke dalam Amazon S3. Kita bisa menganalogikan bucket seperti directory yang ada di harddisk kita, dimana kita bisa membuat folder/path dan menyimpan file di dalamnya. Seperti contoh, misal kita membuat bucket `adamstudio-bucket` di region `ap-southeast-1` dan mengupload file `adamstudio.jpg`, maka kita bisa mengakses file tersebut dengan URL `https://adamstudio-bucket.s3.ap-southeast-1.amazonaws.com/adamstudio.jpg` (dengan authorisasi tertentu pastinya).
+
+#### • Object
+
+Object secara singkat bisa kita artikan sebagai file, meskipun pada dasarnya berbeda, karena object juga menyimpan metadata file dan data-data lainnya.
+
+> Untuk mempelajari lebih lanjut mengenai definisi dan beberapa istilah lain terkait Amazon S3, silakan cek https://docs.aws.amazon.com/id_id/AmazonS3/latest/userguide/Welcome.html
+
+## C.39.3 *Authentication* dan *authorization* bucket S3
+
+AWS S3 menyediakan beberapa jenis metode otentikasi untuk pengaksesan konten S3 via aplikasi, salah satunya menggunakan access keys (`aws_access_key_id` dan `aws_secret_access_key`).
+
+Pada chapter ini kita akan menerapkan metode otentikasi tersebut, dengan asumsi *bucket policy* yang digunakan adalah *defalt*, dimana semua konten dalam bucket bisa diakses dan dimodifikasi.
+
+> Lebih lanjut mengenai *bucket policy* bisa mengunjungi link berikut: https://docs.aws.amazon.com/id_id/AmazonS3/latest/userguide/about-object-ownership.html
+
+> Disini penulis asumsikan kita sudah memiliki akses berupa `aws_access_key_id` dan `aws_secret_access_key` untuk digunakan di aplikasi kita dalam membuat koneksi ke bucket S3.
+
+## C.39.3 Koneksi ke S3
+
+*And here we go...*
+
+Ok, seperti biasa buat proyek baru, kemudian buat 1 file bernama `main.go`. Definisikan package dan import beberapa dependensi yang dibutuhkan. Disini kita akan menggunaakan [official SDK dari AWS untuk Golang](https://github.com/aws/aws-sdk-go).
+
+> Untuk dokumentasi detailnya bisa dibaca disini: https://aws.amazon.com/id/sdk-for-go/
+
+Definisikan konstanta untuk menampung nilai access key, dan juga region bucket.
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/credentials"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/s3"
+ "github.com/aws/aws-sdk-go/service/s3/s3manager"
+)
+
+const (
+ AWS_ACCESS_KEY_ID string = "AKIA**********"
+ AWS_SECRET_ACCESS_KEY string = "LReO***********"
+ AWS_REGION string = "ap-southeast-1"
+)
+```
+
+> Jangan lupa untuk `go get -u github.com/aws/aws-sdk-go` dependensi aws-sdk-go
+
+Selanjutnya, kita siapkan fungsi untuk mendapatkaan objek `*session.Session` dari AWS. Object ini berisi informasi session pengaksesan *resource* AWS, yang pada konteks ini adalah AWS S3.
+
+```go
+func newSession() (*session.Session, error) {
+ sess, err := session.NewSession(&aws.Config{
+ Region: aws.String(AWS_REGION),
+ Credentials: credentials.NewStaticCredentials(
+ AWS_ACCESS_KEY_ID,
+ AWS_SECRET_ACCESS_KEY,
+ "",
+ ),
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ return sess, nil
+}
+```
+
+Panggil fungsi `newSession()` di atas, lalu bungkus menggunakan fungsi `s3.New()` untuk mendapatkan object session `*s3.S3`, yang pada kode berikut direpresentasikan oleh variabel `s3Client`. Lewat object tersebut nantinya operasi pengaksesan dan manipulasi konten S3 dilakukan.
+
+```go
+func main() {
+ sess, err := newSession()
+ if err != nil {
+ fmt.Println("Failed to create AWS session:", err)
+ return
+ }
+
+ s3Client := s3.New(sess)
+ fmt.Println("S3 session & client initialized")
+
+ // ...
+}
+```
+
+## C.39.4 Membuat bucket baru ke S3
+
+Gunakan method `CreateBucket()` milik object `client` untuk membuat bucket baru, siapkan statement dalam fungsi bernama `createBucket()`. Tulis nama bucket pada parameter pemanggilan fungsi dengan skema penulisan bisa dilihat di bawah ini:
+
+```go
+func createBucket(client *s3.S3, bucketName string) error {
+ _, err := client.CreateBucket(&s3.CreateBucketInput{
+ Bucket: aws.String(bucketName),
+ })
+
+ return err
+}
+```
+
+Navigasi ke fungsi `main()`, panggil fungsi `createBucket()` di atas. Pada contoh berikut, bucket name yang dipilih adalah `bucketName`.
+
+```go
+func main() {
+ // ...
+
+ bucketName := "adamstudio-new-bucket"
+
+ // =============== create bucket ===============
+ err = createBucket(s3Client, bucketName)
+ if err != nil {
+ fmt.Printf("Couldn't create new bucket: %v", err)
+ return
+ }
+
+ fmt.Println("New bucket successfully created")
+
+ // ...
+}
+```
+
+Jalankan program dan lihat hasilnya.
+
+
+
+## C.39.5 Melihat semua daftar bucket di S3
+
+Sekarang siapkan fungsi baru untuk menampilkan daftar bucket. Gunakan method `ListBuckets()`. Pada contoh berikut method dipanggil dengan parameter `nil` karena kita tidak menggunakan filter.
+
+```go
+func listBuckets(client *s3.S3) (*s3.ListBucketsOutput, error) {
+ res, err := client.ListBuckets(nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
+```
+
+Modifikasi fungsi `main()`, kemudian tambahkan baris kode berikut untuk memanggil fungsi `listBuckets()` di atas. Lewat nilai balik fungsi tersebut, akses property `.Buckets` untuk mendapatkan list buckets.
+
+```go
+func main() {
+ // ...
+
+ // =============== list all buckets ===============
+ buckets, err := listBuckets(s3Client)
+ if err != nil {
+ fmt.Printf("Couldn't list buckets: %v", err)
+ return
+ }
+
+ for _, bucket := range buckets.Buckets {
+ fmt.Printf("Found bucket: %s, created at: %s\n", *bucket.Name, *bucket.CreationDate)
+ }
+
+ // ...
+}
+```
+
+Jalankan program dan lihat hasilnya.
+
+
+
+## C.39.6 Mengupload object ke dalam S3 bucket
+
+Operasi upload file bisa dilakukan via beberapa cara, salah satunya menggunakan method `Upload()` milik object uploader (yarn bertipe `*s3manager.Uploader`) yang dalam pemanggilannya, file yang ingin di-upload disisipkan sebagai argument pemanggilan method.
+
+Pada contoh berikut, variabel `file` merepresentasikan file yang akan di upload. Variabel ini disisipkan pada pemanggilan method `Upload()`.
+
+```go
+func uploadFile(uploader *s3manager.Uploader, filePath string, bucketName string, fileName string) error {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return err
+ }
+
+ defer file.Close()
+
+ _, err = uploader.Upload(&s3manager.UploadInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(fileName),
+ Body: file,
+ })
+
+ return err
+}
+```
+
+Selanjutnya di fungsi `main()`, siapkan object uploader, caranya mudah yaitu dengan membungkus object session menggunakan fungsi `s3manager.NewUploader()`.
+
+> Pastikan untuk tidak meng-import dependensi `github.com/aws/aws-sdk-go/service/s3/s3manager`
+
+Tak lupa siapkan juga sample file yang akan digunakan untuk testing (untuk di upload ke bucket). Pada contoh ini penulis menggunakan file `./upload/adamstudio.jpg`.
+
+Modifikasi lagi fungsi `main`, tambahkan statement-statement di atas dan panggil fungsi `uploadFile()`.
+
+```go
+func main() {
+ // ...
+
+ // =============== upload file ===============
+ uploader := s3manager.NewUploader(sess)
+
+ fileName := "adamstudio.jpg"
+ filePath := filepath.Join("upload", fileName)
+
+ err = uploadFile(uploader, filePath, bucketName, fileName)
+ if err != nil {
+ fmt.Printf("Failed to upload file: %v", err)
+ }
+
+ fmt.Println("Successfully uploaded file!")
+
+ // ...
+}
+```
+
+Jalankan program dan lihat hasilnya.
+
+
+
+## C.39.7 Menampilkan daftar objects dalam bucket
+
+Gunakan method `ListObjectsV2()` untuk menampilkan isi bucket.
+
+```go
+func listObjects(client *s3.S3, bucketName string) (*s3.ListObjectsV2Output, error) {
+ res, err := client.ListObjectsV2(&s3.ListObjectsV2Input{
+ Bucket: aws.String(bucketName),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
+```
+
+Panggil fungsi `listObjects()` yang telah dibuat di atas. Lewat nilai balik fungsi tersebut, akses property `.Contents` untuk mendapatkan list objects.
+
+```go
+func main() {
+ // ...
+
+ // =============== list objects ===============
+ // bucketName := "adamstudio-new-bucket"
+ objects, err := listObjects(s3Client, bucketName)
+ if err != nil {
+ fmt.Printf("Couldn't list objects: %v", err)
+ return
+ }
+
+ for _, object := range objects.Contents {
+ fmt.Printf("Found object: %s, size: %d\n", *object.Key, *object.Size)
+ }
+
+ // ...
+}
+```
+
+Jalankan program dan lihat hasilnya.
+
+
+
+## C.39.8 Men-download object dari S3 bucket
+
+Untuk proses download, kita harus mempersiapkan satu file object terlebih dahulu untuk menampung konten hasil operasi download.
+
+Pada contoh berikut, object `file` adalah file yang akan menampung operasi download. Object tersebut disisipkan sebagai argument pemanggilan fungsi `Download()` milik object downloader bertipe `*s3manager.Downloader`.
+
+```go
+func downloadFile(downloader *s3manager.Downloader, bucketName string, key string, downloadPath string) error {
+ file, err := os.Create(downloadPath)
+ if err != nil {
+ return err
+ }
+
+ defer file.Close()
+
+ _, err = downloader.Download(
+ file,
+ &s3.GetObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(key),
+ },
+ )
+
+ return err
+}
+```
+
+Buat object downloader menggunakan fungsi `s3manager.NewDownloader()`, siapkan juga variabel untuk path file, lalu panggil method `downloadFile()`.
+
+> Pastikan untuk meng-import dependensi `github.com/aws/aws-sdk-go/service/s3/s3manager`
+
+```go
+func main() {
+ // ...
+
+ // =============== download file ===============
+ downloader := s3manager.NewDownloader(sess)
+ fileName := "adamstudio.jpg"
+ bucketName := "adamstudio-new-bucket"
+ downloadPath := filepath.Join("download", fileName)
+ err = downloadFile(downloader, bucketName, fileName, downloadPath)
+ if err != nil {
+ fmt.Printf("Couldn't download file: %v", err)
+ return
+ }
+
+ fmt.Println("Successfully downloaded file")
+
+ // ...
+}
+```
+
+Jalankan program dan lihat hasilnya.
+
+
+
+## C.39.9 Menghapus object dari S3 bucket
+
+Operasi delete object bisa dilakukan menggunakan method `DeleteObject()` milik object s3 client:
+
+```go
+func deleteFile(client *s3.S3, bucketName string, fileName string) error {
+ _, err := client.DeleteObject(&s3.DeleteObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(fileName),
+ })
+
+ return err
+}
+```
+
+Panggil fungsi di atas pada `main()`, lalu sisipkan variabel file yang ingin di-delete.
+
+```go
+func main() {
+ // ...
+
+ // =============== delete file ===============
+ fileName := "adamstudio.jpg"
+ bucketName := "adamstudio-new-bucket"
+ err = deleteFile(s3Client, bucketName, fileName)
+ if err != nil {
+ fmt.Printf("Couldn't delete file: %v", err)
+ return
+ }
+
+ fmt.Println("Successfully delete file")
+}
+```
+
+Jalankan program dan lihat hasilnya.
+
+
+
+## C.39.10 Presign URL
+
+Presign URL adalah salah satu metode untuk sharing object bisa diakses oleh publik (lewat internet). Dengan presign url, kita bisa menentukan durasi berapa lama object bisa diakses oleh public.
+
+Cara penerapannya cukup mudah, pertama akses *instance* object menggunakan method `GetObjectRequest()`. Pada argument pemanggilan method, isi `Key` dengan nama object. Lalu gunakan nilai balik pertama statement untuk mengakses method `Presign()` sekaligus tentukan durasinya.
+
+Pada kode berikut, method `Presign()` mengembalikan URL object yang valid selama `15 menit`.
+
+```go
+func presignUrl(client *s3.S3, bucketName string, fileName string) (string, error) {
+ req, _ := client.GetObjectRequest(&s3.GetObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(fileName),
+ })
+
+ urlStr, err := req.Presign(15 * time.Minute)
+ if err != nil {
+ return "", err
+ }
+
+ return urlStr, nil
+}
+```
+
+Panggil method tersebut di fungsi `main()` lalu coba akses URL nya.
+
+```go
+func main() {
+ // ...
+
+ // =============== presign url ===============
+ fileName := "adamstudio.jpg"
+ bucketName := "adamstudio-new-bucket"
+ urlStr, err := presignUrl(s3Client, bucketName, fileName)
+ if err != nil {
+ fmt.Printf("Couldn't presign url: %v", err)
+ return
+ }
+
+ fmt.Println("Presign url:", urlStr)
+}
+```
+
+Hasilnya:
+
+
+
+
+
+---
+
+ - [aws-sdk-go](https://github.com/aws/aws-sdk-go), by AWS, Apache 2.0 License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 127ae254f1d420ff00bcd8e2399780ba32807bb3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:31 +0700
Subject: [PATCH 102/522] New translations c-golang-ftp.md (English)
---
en/content-en/C-golang-ftp.md | 302 ++++++++++++++++++++++++++++++++++
1 file changed, 302 insertions(+)
create mode 100644 en/content-en/C-golang-ftp.md
diff --git a/en/content-en/C-golang-ftp.md b/en/content-en/C-golang-ftp.md
new file mode 100644
index 000000000..7dee6f497
--- /dev/null
+++ b/en/content-en/C-golang-ftp.md
@@ -0,0 +1,302 @@
+# C.28. FTP
+
+Pada chapter ini kita akan belajar cara melakukan pertukaran data lewat FTP (File Transfer Protocol) menggunakan Golang.
+
+> Definisi mengenai FTP sendiri adalah sebuah protokol network standar yang digunakan untuk pertukaran atau transfer data antar client dan server.
+
+Sebelum memulai, ada satu hal penting yang perlu dipersiapkan, yaitu sebuah server dengan FTP server ter-install. Jika tidak ada, bisa menggunakan library [ftpd](https://github.com/goftp/ftpd) untuk set up ftp server di local (untuk keperluan belajar).
+
+Dalam server tersebut, siapkan beberapa file dan folder dengan struktur sebagai berikut.
+
+
+
+ - File `test1.txt`, isi dengan apapun.
+ - File `test2.txt`, isi dengan apapun.
+ - File `somefolder/test3.txt`, isi dengan apapun.
+ - File `movie.mp4`, gunakan file seadanya.
+
+Library FTP client yang kita gunakan adalah [github.com/jlaffaye/ftp](https://github.com/jlaffaye/ftp).
+
+## C.28.1. Koneksi ke Server
+
+Buat satu buah folder project baru dengan isi `main.go`. Di dalam file main akan kita isi dengan beberapa operasi FTP seperti upload, download, akses ke direktori dan lainnya.
+
+OK, langsung saja, silakan tulis kode berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/jlaffaye/ftp"
+ "log"
+)
+
+func main() {
+ const FTP_ADDR = "0.0.0.0:2121"
+ const FTP_USERNAME = "admin"
+ const FTP_PASSWORD = "123456"
+
+ // ...
+}
+```
+
+Tiga buah konstanta dengan prefix `FTP_` disiapkan, isinya adalah credentials FTP untuk bisa melakukan koneksi FTP ke server.
+
+Di dalam `main()`, tambahkan kode untuk terhubung dengan server.
+
+```go
+connt, err := ftp.Dial(FTP_ADDR)
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+err = conn.Login(FTP_USERNAME, FTP_PASSWORD)
+if err != nil {
+ log.Fatal(err.Error())
+}
+```
+
+Cara konek ke server melalui FTP dipecah menjadi dua tahap. Pertama adalah menggunakan `ftp.Dial()` dengan argumen adalah alamat server (beserta port). Statement tersebut mengembalikan objek bertipe `*ftp.ServerConn` yang ditampung oleh variabel `conn`.
+
+Tahap kedua, lewat objek `conn`, panggil method `.Login()` dengan disisipi argumen username dan password FTP.
+
+## C.28.2. Menampilkan Semua File Menggunakan `.List()`
+
+Lewat tipe `*ftp.ServerConn`, semua method untuk operasi FTP bisa diakses. Salah satu dari method tersebut adalah `.List()`, gunanya untuk listing semua file yang ada di server. Operasi ini sama dengan `ls`.
+
+```go
+fmt.Println("======= PATH ./")
+
+entries, err := conn.List(".")
+if err != nil {
+ log.Fatal(err.Error())
+}
+for _, entry := range entries {
+ fmt.Println(" ->", entry.Name, getStringEntryType(entry.Type))
+}
+```
+
+Method `.List()` di atas dipanggil dengan argumen adalah string `"."`, berarti semua aset dalam `"."` atau **current path** dimunculkan.
+
+Method tersebut mengembalikan slice yang tiap elemen-nya adalah representasi tiap file. Pada kode di atas, semua file dimunculkan ke console, nama dan tipe-nya.
+
+Property `.Type` milik `entry` tipenya adalah `ftp.EntryType`, yang sebenarnya `int`. Fungsi `getStringEntryType()` kita buat untuk menampilkan keterangan dalam string sesuai dengan tipe.
+
+```go
+func getStringEntryType(t ftp.EntryType) string {
+ switch t {
+ case ftp.EntryTypeFile:
+ return "(file)"
+ case ftp.EntryTypeFolder:
+ return "(folder)"
+ case ftp.EntryTypeLink:
+ return "(link)"
+ default:
+ return ""
+ }
+}
+```
+
+Jalankan aplikasi lihat hasilnya.
+
+
+
+Jika dibandingkan dengan file yang ada di server, ada satu yang tidak muncul, yaitu `somefolder/test3.txt`. Hal ini dikarenakan file yang di-list adalah yang ada pada `"."` atau **current path**. File `test3.txt` berada di dalam sub folder `somefolder`.
+
+## C.28.3. Pindah Ke Folder Tertentu Menggunakan `.ChangeDir()`
+
+Selanjutnya, kita akan coba masuk ke folder `somefolder`, lalu menampilkan isinya. Gunakan method `.ChangeDir()`, sisipkan path folder tujuan sebagai argument.
+
+```go
+fmt.Println("======= PATH ./somefolder")
+
+err = conn.ChangeDir("./somefolder")
+if err != nil {
+ log.Fatal(err.Error())
+}
+```
+
+Setelah itu, list semua file. Tetap gunakan `"."` sebagai argument pemanggilan method `.List()` untuk listing current path. Karena kita sudah masuk ke folder `./somefolder`, maka path tersebut-lah yang menjadi current path.
+
+```go
+entries, err = conn.List(".")
+if err != nil {
+ log.Fatal(err.Error())
+}
+for _, entry := range entries {
+ fmt.Println(" ->", entry.Name, getStringEntryType(entry.Type))
+}
+```
+
+Jalankan aplikasi, lihat lagi hasilnya.
+
+
+
+## C.28.4. Pindah Ke Parent Folder Menggunakan `.ChangeDirToParent()`
+
+Gunakan method `.ChangeDirToParent()` untuk mengubah aktif path ke parent path. Tambahkan kode berikut, agar current path `./somefolder` kembali menjadi `.`.
+
+```go
+err = conn.ChangeDirToParent()
+if err != nil {
+ log.Fatal(err.Error())
+}
+```
+
+## C.28.5. Mengambil File Menggunakan `.Retr()` Lalu Membaca Isinya
+
+Cara mengambil file adalah dengan method `.Retr()`. Tulis saja path file yang ingin diambil sebagai argumen. Nilai baliknya adalah objek bertipe `*ftp.Response` dan error (jika ada).
+
+```go
+fmt.Println("======= SHOW CONTENT OF FILES")
+
+fileTest1Path := "test1.txt"
+fileTest1, err := conn.Retr(fileTest1Path)
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+test1ContentInBytes, err := ioutil.ReadAll(fileTest1)
+fileTest1.Close()
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+fmt.Println(" ->", fileTest1Path, "->", string(test1ContentInBytes))
+```
+
+Baca isi objek response tersebut menggunakan method `.Read()` miliknya, atau bisa juga menggunakan `ioutil.ReadAll()` lebih praktisnya (nilai baliknya bertipe `[]byte` maka cast ke tipe `string` terlebih dahulu untuk menampilkan isinya).
+
+> Jangan lupa untuk import package `io/ioutil`.
+
+Di kode di atas file `test1.txt` dibaca. Lakukan operasi yang sama pada file `somefolder/test3.txt`.
+
+```go
+fileTest2Path := "somefolder/test3.txt"
+fileTest2, err := conn.Retr(fileTest2Path)
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+test2ContentInBytes, err := ioutil.ReadAll(fileTest2)
+fileTest2.Close()
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+fmt.Println(" ->", fileTest2Path, "->", string(test2ContentInBytes))
+```
+
+> Jangan lupa juga untuk meng-close objek bertipe `*ftp.Response`, setelah dibaca isinya.
+
+Jalankan aplikasi, cek hasilnya.
+
+
+
+Bisa dilihat, isi file yang dibaca adalah aslinya.
+
+## C.28.6. Download File
+
+Proses download secara teknis adalah memindahkan **isi file** dari remote server ke local server. Di local, dibuatkan sebuah file yang nantinya akan menampung isi file dari remote server yang di-transfer. Sebarapa cepat proses download berlangsung sangat tergantung kepada besar file yang isinya sedang di transfer (dan beberapa faktor lainnya).
+
+Di golang, penerapan download lewat FTP kurang lebih adalah sama. perlu dibuat terlebih dahulu file di local untuk menampung isi file yang diambil dari remote server.
+
+OK, mari kita praktekan. File `movie.mp4` yang berada di server akan kita unduh ke local.
+
+```go
+fmt.Println("======= DOWNLOAD FILE TO LOCAL")
+
+fileMoviePath := "movie.mp4"
+fileMovie, err := conn.Retr(fileMoviePath)
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+destinationMoviePath := "/Users/novalagung/Desktop/downloaded-movie.mp4"
+f, err := os.Create(destinationMoviePath)
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+_, err = io.Copy(f, fileMovie)
+if err != nil {
+ log.Fatal(err.Error())
+}
+fileMovie.Close()
+f.Close()
+
+fmt.Println("File downloaded to", destinationMoviePath)
+```
+
+Pertama ambil file tujuan menggunakan `.Retr()`. Lalu buat file di local. Pada contoh di atas nama file di local adalah berbeda dengan nama file asli, `downloaded-movie.mp4`. Buat file tersebut menggunakan `os.Create()`.
+
+Setelah itu, copy isi file yang sudah diambil dari server, ke `downloaded-movie.mp4` menggunakan `io.Copy()`. Setelah proses transfer selesai, jangan lupa untuk close file dari remote dan juga file di local.
+
+> Operasi di atas membutuhkan dua package lain, yaitu `io` dan `os`, maka import kedua package tersebut.
+
+Jalankan aplikasi, coba cek md5 sum dari kedua file, harusnya sama.
+
+
+
+Coba buka `downloaded-movie.mp4`, jika proses transfer sukses maka pasti bisa dibuka.
+
+
+
+## C.28.7. Upload File
+
+Upload file adalah kebalikan dari download file. File dari lokal di transfer ke server. Mari langsung kita praktekan.
+
+Pertama buka file tujuan menggunakan `os.Open()`. Lalu panggil method `.Store()` milik `conn`, sisipkan path file tujuan remote server sebagai parameter pertama, lalu objek file di local sebagai parameter kedua.
+
+```go
+fmt.Println("======= UPLOAD FILE TO FTP SERVER")
+
+sourceFile := "/Users/novalagung/Desktop/Go-Logo_Aqua.png"
+f, err = os.Open(sourceFile)
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+destinationFile := "./somefolder/logo.png"
+err = conn.Stor(destinationFile, f)
+if err != nil {
+ log.Fatal(err.Error())
+}
+f.Close()
+
+fmt.Println("File", sourceFile, "uploaded to", destinationFile)
+```
+
+File `Go-Logo_Aqua.png` di upload ke server pada path `./somefolder/logo.png`. Coba list untuk mengecek apakah file sudah terupload.
+
+```go
+entries, err = conn.List("./somefolder")
+if err != nil {
+ log.Fatal(err.Error())
+}
+
+for _, entry := range entries {
+ fmt.Println(" ->", entry.Name, getStringEntryType(entry.Type))
+}
+```
+
+Jalankan aplikasi, cek hasilnya. Untuk memvalidasi bahwa file di client dan di server sama, bandingkan md5 sum kedua file.
+
+
+
+---
+
+ - [ftpd](https://github.com/goftp/ftpd), by Lunny Xiao
+ - [ftp](https://github.com/jlaffaye/ftp), by Julien Laffaye, ISC license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From c5a54591aafda62a7569f1e65ac65f55503577cf Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:32 +0700
Subject: [PATCH 103/522] New translations c-golang-grpc-protobuf.md (English)
---
en/content-en/C-golang-grpc-protobuf.md | 541 ++++++++++++++++++++++++
1 file changed, 541 insertions(+)
create mode 100644 en/content-en/C-golang-grpc-protobuf.md
diff --git a/en/content-en/C-golang-grpc-protobuf.md b/en/content-en/C-golang-grpc-protobuf.md
new file mode 100644
index 000000000..ef2c9f60c
--- /dev/null
+++ b/en/content-en/C-golang-grpc-protobuf.md
@@ -0,0 +1,541 @@
+# C.31. gRPC + Protobuf
+
+Pada chapter ini kita akan belajar tentang penerapan **gRPC** dan **protobuf** pada bahasa Go.
+
+Kita akan buat satu buah folder project besar, di dalamnya terdapat 3 buah aplikasi. Dua di antaranya merupakan aplikasi server, lebih tepatnya rpc server, dan yang satu lagi aplikasi client. Aplikasi client akan berkomunikasi dengan kedua aplikasi server.
+
+Bisa dibilang ini adalah contoh super sederhana (dan asal-asalan) tentang penerapan [microservices architecture](https://en.wikipedia.org/wiki/Microservices).
+
+> BEWARE: Tutorial ini sangat panjang! Dan jangan ber-ekspektasi terlalu tinggi, karena target pembaca adalah orang yang baru belajar Go, gRPC, dan protobuf.
+
+## C.31.1. Definisi
+
+gRPC adalah salah satu RPC framework, dibuat oleh Google. gRPC menggunakan protokol RPC untuk transport dan protobuf di bagian antarmuka-nya.
+
+> Remote Procedure Call (RPC) adalah sebuah metode yang memungkinkan kita untuk mengakses sebuah prosedur yang berada di komputer lain. Untuk dapat melakukan ini sebuah server harus menyediakan layanan remote procedure.
+
+## C.31.2 Prerequisites
+
+Sekedar informasi bahwa sebelum mulai mengikuti pembelajaran pada chapter ini, **WAJIB HUKUMNYA** untuk mengikuti pembahasan pada chapter sebelumnya [C.30. Protobuf](/C-golang-protobuf-implementation.html) terlebih dahulu.
+
+## C.31.3. Struktur Aplikasi
+
+Siapkan satu project baru dengan struktur sebagai berikut.
+
+```bash
+# perform go get outside of go mod project, so the binary will be stored in $GOPATH/bin.
+# more details, take a look at https://stackoverflow.com/a/30097929/1467988
+go get -u github.com/golang/protobuf/protoc-gen-go
+
+mkdir chapter-c30
+cd chapter-c30
+go mod init chapter-c30
+
+go get -u github.com/golang/protobuf@v1.3.2
+go get -u google.golang.org/grpc@v1.26.0
+
+# then prepare underneath structures
+tree .
+.
+├── go.mod
+├── common
+│ ├── config
+│ │ └── config.go
+│ └── model
+│ ├── garage.proto
+│ ├── user.proto
+│ └── util.proto
+│
+├── client
+│ └── main.go
+│
+└── services
+ ├── service-garage
+ │ └── main.go
+ └── service-user
+ └── main.go
+
+7 directories, 7 files
+```
+
+Salah satu pembeda yang paling terlihat dibanding chapter sebelumnya adalah di sini kita go get package `google.golang.org/grpc`. Package ini diperlukan oleh generator untuk bisa memahami dan men-*generate* spesifikasi `service`. Lebih jelasnya akan kita bahas sambil praktek.
+
+Lanjut. Di bawah ini merupakan penjelasan per bagian dari struktur project di atas.
+
+#### • Folder `common`
+
+Folder `common`, berisikan 2 buah sub folder, `config` dan `model`.
+
+ - Folder `config` berisikan informasi shared atau global, yang digunakan aplikasi client maupun server.
+ - Folder `model` berisikan file `.proto`. Silakan salin file `garage.proto` dan `user.proto` pada chapter sebelumnya ke folder tersebut.
+
+#### • Folder `client`
+
+Isinya adalah satu buah file main, yang nantinya di jalankan sebagai aplikasi client. Aplikasi client ini akan berkomunikasi dengan 2 buah aplikasi server.
+
+#### • Folder `services`
+
+Satu buah file proto untuk satu aplikasi rpc server (service). Karena ada dua file proto, berarti jelasnya ada dua aplikasi rpc server, `service-user` dan `service-garage`. Folder `services` ini menampung kedua aplikasi service tersebut.
+
+File `garage.proto` dan `user.proto` tidak dijadikan satu dalam respektif folder `service-garage` dan `service-user`, karena kedua file ini juga digunakan pada aplikasi client, itulah kenapa file ini dipisah ke dalam folder `common/model`.
+
+## C.31.4. File Konfigurasi pada folder `common/config`
+
+Siapkan file bernama `config.go` dalam `common/config`. Di dalam file config ini didefinisikan dua buah konstanta yaitu port untuk service user dan garage. Nantinya aplikasi server di start menggunakan port ini.
+
+```go
+package config
+
+const (
+ SERVICE_GARAGE_PORT = ":7000"
+ SERVICE_USER_PORT = ":9000"
+)
+```
+
+## C.31.5. Proto Model pada folder `common/model`
+
+Setelah kedua file `user.proto` dan `garage.proto` pada chapter sebelumnya disalin, kita perlu menambahkan pendefinisian service pada masing-masing file proto.
+
+Keyword `service` digunakan untuk membuat service. Service ini nantinya juga ikut di konversi ke bentuk Go (menjadi interface), lewat command `protoc`. Di aplikasi rpc server, nantinya harus dibuat implementasi dari interface tersebut.
+
+OK, sekarang tambahkan kode berikut ke file proto.
+
+#### • Service `Users`
+
+Buka file `user.proto`, tambahkan kode berikut di akhir baris.
+
+```protobuf
+// ...
+
+import "google/protobuf/Empty.proto";
+
+service Users {
+ rpc Register(User) returns (google.protobuf.Empty) {}
+ rpc List(google.protobuf.Empty) returns (UserList) {}
+}
+```
+
+Sebuah service bernama `Users` didefinisikan, dengan isi dua buah method.
+
+ - `Register()`, menerima parameter bertipe model `User`, dan mengembalikan `google.protobuf.Empty`.
+ - `List()`, menerima parameter bertipe `google.protobuf.Empty`, dan mengembalikan tipe `UserList`.
+
+Silakan dilihat bagaimana cara untuk membuat service pada kode atas.
+
+Pada method `Register()` sebenarnya kita tidak butuh nilai balik. Tapi karena requirements dari protobuf mewajibkan semua rpc method harus memiliki nilai balik, maka kita gunakan model `Empty` milik google protobuf.
+
+Cara penggunaan model `Empty` adalah dengan meng-import file proto-nya terlebih dahulu, `google/protobuf/Empty.proto`, lalu menggunakan `google.protobuf.Empty` sebagai model.
+
+Juga, pada method `List()`, sebenarnya argument tidak dibutuhkan, tapi karena protobuf mewajibkan pendefinisian rpc method untuk memiliki satu buah argument dan satu buah return type, maka mau tidak mau harus ada argument.
+
+Setelah di-compile, dua buah interface terbuat dengan skema nama `Server` dan `Client`. Karena nama service adalah `Users`, maka terbuatlah `UsersServer` dan `UsersClient`.
+
+ - Interface `UsersServer`.
+
+ ```go
+ type UsersServer interface {
+ Register(context.Context, *User) (*empty.Empty, error)
+ List(context.Context, *empty.Empty) (*UserList, error)
+ }
+ ```
+ Interface ini nantinya harus diimplementasikan di aplikasi rpc server.
+
+ - Interface `UsersClient`.
+
+ ```go
+ type UsersClient interface {
+ Register(ctx context.Context, in *User, opts ...grpc.CallOption) (*empty.Empty, error)
+ List(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*UserList, error)
+ }
+ ```
+ Interface ini nantinya harus diimplementasikan di aplikasi rpc client.
+
+#### • Service `Garages`
+
+Pada file `garage.proto`, definisikan service `Garages` dengan isi dua buah method, `Add()` dan `List()`.
+
+```protobuf
+import "google/protobuf/Empty.proto";
+
+service Garages {
+ rpc List(string) returns (GarageList) {}
+ rpc Add(string, Garage) returns (google.protobuf.Empty) {}
+}
+```
+
+Perlu diketahui bahwa protobuf mewajibkan pada rpc method untuk tidak menggunakan tipe primitif sebagai tipe argument maupun tipe nilai balik. Tipe `string` pada `rpc List(string)` akan menghasilkan error saat di-compile. Dan juga protobuf mewajibkan method untuk hanya menerima satu buah parameter, maka jelasnya `rpc Add(string, Garage)` juga invalid.
+
+Lalu bagaimana solusinya? Buat model baru lagi, property nya sesuaikan dengan parameter yang dibutuhkan di masing-masing rpc method.
+
+```protobuf
+message GarageUserId {
+ string user_id = 1;
+}
+
+message GarageAndUserId {
+ string user_id = 1;
+ Garage garage = 2;
+}
+
+service Garages {
+ rpc List(GarageUserId) returns (GarageList) {}
+ rpc Add(GarageAndUserId) returns (google.protobuf.Empty) {}
+}
+```
+
+Sama seperti service `Users`, service `Garages` juga akan di-compile menjadi interface.
+
+ - Interface `GaragesServer`.
+
+ ```go
+ type GaragesServer interface {
+ Add(context.Context, *GarageAndUserId) (*empty.Empty, error)
+ List(context.Context, *GarageUserId) (*GarageList, error)
+ }
+ ```
+
+ - Interface `GaragesClient`.
+
+ ```go
+ type GaragesClient interface {
+ Add(ctx context.Context, in *GarageAndUserId, opts ...grpc.CallOption) (*empty.Empty, error)
+ List(ctx context.Context, in *GarageUserId, opts ...grpc.CallOption) (*GarageList, error)
+ }
+ ```
+
+## C.31.6. Kompilasi File `.proto` Dengan Enable Plugin `grpc`
+
+Gunakan command berikut untuk generate file .go dari file .proto yang sudah kita buat:
+
+```bash
+PATH=$PATH:$GOPATH/bin/ protoc --go_out=plugins=grpc:. *.proto
+```
+
+Perhatikan baik-baik command di atas, Pada flag `--go_out` isinya adalah `plugins=grpc:.`, ada `plugins=grpc` di situ (berbeda dibanding pada chapter sebelumnya yang isinya langsung `.`).
+
+Plugin `grpc` ini dipergunakan untuk men-*generate* **service bindings behaviour** yang ada pada gRPC. Seperti yang kita telah praktekan bahwa di atas kita menuliskan definisi `service`. Dengan menambahkan `plugins=grpc` maka definisi `service` tersebut akan bisa dipahami oleh generator untuk kemudian di-*transform* menjadi definisi interface beserta isi method-nya.
+
+## C.31.7. Aplikasi Server `service-user`
+
+Buka file `services/service-user/main.go`, import package yang dibutuhkan.
+
+```go
+package main
+
+import (
+ "context"
+ "log"
+ "net"
+
+ "chapter-c30/common/config"
+ "chapter-c30/common/model"
+
+ "github.com/golang/protobuf/ptypes/empty"
+ "google.golang.org/grpc"
+)
+```
+
+Lalu buat satu buah objek bernama `localStorage` bertipe `*model.UserList`. Objek ini nantinya menampung data user yang ditambahkan dari aplikasi client (via rpc) lewat method `Register()`. Dan data objek ini juga dikembalikan ke client ketika `List()` diakses.
+
+```go
+var localStorage *model.UserList
+
+func init() {
+ localStorage = new(model.UserList)
+ localStorage.List = make([]*model.User, 0)
+}
+```
+
+Buat struct baru `UsersServer`. Struct ini akan menjadi implementasi dari generated interface `model.UsersServer`. Siapkan method-method-nya sesuai spesifikasi interface.
+
+```go
+type UsersServer struct{}
+
+func (UsersServer) Register(ctx context.Context, param *model.User) (*empty.Empty, error) {
+ localStorage.List = append(localStorage.List, param)
+
+ log.Println("Registering user", param.String())
+
+ return new(empty.Empty), nil
+}
+
+func (UsersServer) List(ctx context.Context, void *empty.Empty) (*model.UserList, error) {
+ return localStorage, nil
+}
+```
+
+Buat fungsi `main()`, buat objek grpc server dan objek implementasi `UsersServer`, lalu registrasikan kedua objek tersebut ke model menggunakan statement `model.RegisterUsersServer()`.
+
+```go
+func main() {
+ srv := grpc.NewServer()
+ var userSrv UsersServer
+ model.RegisterUsersServer(srv, userSrv)
+
+ log.Println("Starting RPC server at", config.SERVICE_USER_PORT)
+
+ // more code here ...
+}
+```
+
+Pembuatan objek grpc server dilakukan lewat `grpc.NewServer()`. Package [google.golang.org/grpc](https://google.golang.org/grpc) perlu di `go get` dan di-import.
+
+> Fungsi `RegisterUsersServer()` otomatis digenerate oleh protoc, karena service `Users` didefinisikan. Contoh lainnya misal service `SomeServiceTest` disiapkan, maka fungsi `RegisterSomeServiceTestServer()` dibuat.
+
+Selanjutnya, siapkan objek listener yang listen ke port `config.SERVICE_USER_PORT`, lalu gunakan listener tersebut sebagai argument method `.Serve()` milik objek rpc server.
+
+```go
+// ...
+
+l, err := net.Listen("tcp", config.SERVICE_USER_PORT)
+if err != nil {
+ log.Fatalf("could not listen to %s: %v", config.SERVICE_USER_PORT, err)
+}
+
+log.Fatal(srv.Serve(l))
+```
+
+## C.31.8. Aplikasi Server `service-garage`
+
+Buat file `services/service-garage/main.go`, import package yang sama seperti pada `service-user`. Lalu buat objek `localStorage` dari struct `*model.GarageListByUser`.
+
+```go
+var localStorage *model.GarageListByUser
+
+func init() {
+ localStorage = new(model.GarageListByUser)
+ localStorage.List = make(map[string]*model.GarageList)
+}
+
+type GaragesServer struct{}
+```
+
+Tugas `localStorage` kurang lebih sama seperti pada `service-user`, hanya saja pada aplikasi ini data garage disimpan per user dalam map.
+
+Selanjutnya buat implementasi interface `model.GaragesServer`, lalu siapkan method-method-nya.
+
+ - Method `Add()`
+
+ ```go
+ func (GaragesServer) Add(ctx context.Context, param *model.GarageAndUserId) (*empty.Empty, error) {
+ userId := param.UserId
+ garage := param.Garage
+
+ if _, ok := localStorage.List[userId]; !ok {
+ localStorage.List[userId] = new(model.GarageList)
+ localStorage.List[userId].List = make([]*model.Garage, 0)
+ }
+ localStorage.List[userId].List = append(localStorage.List[userId].List, garage)
+
+ log.Println("Adding garage", garage.String(), "for user", userId)
+
+ return new(empty.Empty), nil
+ }
+ ```
+
+ - Method `List()`
+
+ ```go
+ func (GaragesServer) List(ctx context.Context, param *model.GarageUserId) (*model.GarageList, error) {
+ userId := param.UserId
+
+ return localStorage.List[userId], nil
+ }
+ ```
+
+Start rpc server, gunakan `config.SERVICE_GARAGE_PORT` sebagai port aplikasi.
+
+```go
+func main() {
+ srv := grpc.NewServer()
+ var garageSrv GaragesServer
+ model.RegisterGaragesServer(srv, garageSrv)
+
+ log.Println("Starting RPC server at", config.SERVICE_GARAGE_PORT)
+
+ l, err := net.Listen("tcp", config.SERVICE_GARAGE_PORT)
+ if err != nil {
+ log.Fatalf("could not listen to %s: %v", config.SERVICE_GARAGE_PORT, err)
+ }
+
+ log.Fatal(srv.Serve(l))
+}
+```
+
+## C.31.9. Aplikasi Client & Testing
+
+Buat file `client/main.go`, import package yang sama seperti pada `service-user` maupun `service-garage`. Lalu siapkan dua buah method yang mengembalikan rpc client yang terhubung ke dua service yang sudah kita buat.
+
+ - RPC client garage:
+
+ ```go
+ func serviceGarage() model.GaragesClient {
+ port := config.SERVICE_GARAGE_PORT
+ conn, err := grpc.Dial(port, grpc.WithInsecure())
+ if err != nil {
+ log.Fatal("could not connect to", port, err)
+ }
+
+ return model.NewGaragesClient(conn)
+ }
+ ```
+
+- RPC client user:
+
+ ```go
+ func serviceUser() model.UsersClient {
+ port := config.SERVICE_USER_PORT
+ conn, err := grpc.Dial(port, grpc.WithInsecure())
+ if err != nil {
+ log.Fatal("could not connect to", port, err)
+ }
+
+ return model.NewUsersClient(conn)
+ }
+ ```
+
+Buat fungsi `main()`, isi dengan pembuatan object dari generated-struct yang ada di package `model`.
+
+```go
+func main() {
+ user1 := model.User{
+ Id: "n001",
+ Name: "Noval Agung",
+ Password: "kw8d hl12/3m,a",
+ Gender: model.UserGender(model.UserGender_value["MALE"]),
+ }
+
+ garage1 := model.Garage{
+ Id: "q001",
+ Name: "Quel'thalas",
+ Coordinate: &model.GarageCoordinate{
+ Latitude: 45.123123123,
+ Longitude: 54.1231313123,
+ },
+ }
+
+ // ...
+}
+```
+
+#### • Test rpc client user
+
+Selanjutnya akses fungsi `serviceUser()` untuk memperoleh objek rpc client user. Dari situ eksekusi method `.Register()`.
+
+```go
+user := serviceUser()
+
+fmt.Println("\n", "===========> user test")
+
+// register user1
+user.Register(context.Background(), &user1)
+
+// register user2
+user.Register(context.Background(), &user2)
+```
+
+Jalankan aplikasi `service-user`, `service-garage`, dan client, lalu lihat hasilnya.
+
+
+
+Bisa dilihat, pemanggilan method `Add()` pada aplikasi rpc server `service-user` sukses.
+
+Sekarang coba panggil method `.List()`. Jalankan ulang aplikasi client untuk melihat hasilnya. O iya, aplikasi `service-user` juga di-restart, agar datanya tidak menumpuk.
+
+```go
+// show all registered users
+res1, err := user.List(context.Background(), new(empty.Empty))
+if err != nil {
+ log.Fatal(err.Error())
+}
+res1String, _ := json.Marshal(res1.List)
+log.Println(string(res1String))
+```
+
+Bisa dilihat pada gambar berikut, pemanggilan method `.List()` juga sukses. Dua buah data yang sebelumnya didaftarkan muncul.
+
+
+
+#### • Test rpc client garage
+
+Tambahkan beberapa statement untuk memanggil method yang ada di `service-garage`.
+
+ - Menambahkan garage untuk user `user1`:
+
+ ```go
+ garage := serviceGarage()
+
+ fmt.Println("\n", "===========> garage test A")
+
+ // add garage1 to user1
+ garage.Add(context.Background(), &model.GarageAndUserId{
+ UserId: user1.Id,
+ Garage: &garage1,
+ })
+
+ // add garage2 to user1
+ garage.Add(context.Background(), &model.GarageAndUserId{
+ UserId: user1.Id,
+ Garage: &garage2,
+ })
+ ```
+
+ - Menampilkan list semua garage milik `user1`:
+
+ ```go
+ // show all garages of user1
+ res2, err := garage.List(context.Background(), &model.GarageUserId{UserId: user1.Id})
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+ res2String, _ := json.Marshal(res2.List)
+ log.Println(string(res2String))
+ ```
+
+ - Menambahkan garage untuk user `user2`:
+
+ ```go
+ fmt.Println("\n", "===========> garage test B")
+
+ // add garage3 to user2
+ garage.Add(context.Background(), &model.GarageAndUserId{
+ UserId: user2.Id,
+ Garage: &garage3,
+ })
+ ```
+
+ - Menampilkan list semua garage milik `user2`:
+
+ ```go
+ // show all garages of user2
+ res3, err := garage.List(context.Background(), &model.GarageUserId{UserId: user2.Id})
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+ res3String, _ := json.Marshal(res3.List)
+ log.Println(string(res3String))
+ ```
+
+Jalankan ulang services dan aplikasi client, lihat hasilnya.
+
+
+
+---
+
+OK, jika anda membaca sampai baris ini, berarti anda telah berhasil sabar dalam mempelajari gRPC dalam pembahasan yang sangat panjang ini 🎉
+
+---
+
+ - [Protobuf](https://github.com/golang/protobuf), by Google, BSD-3-Clause License
+ - [gRPC](https://github.com/grpc/grpc), by Google, Apache-2.0 License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From bb4d3f25957ad9b6980fbc0b9762cb41cd779a8c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:33 +0700
Subject: [PATCH 104/522] New translations c-golang-jwt.md (English)
---
en/content-en/C-golang-jwt.md | 417 ++++++++++++++++++++++++++++++++++
1 file changed, 417 insertions(+)
create mode 100644 en/content-en/C-golang-jwt.md
diff --git a/en/content-en/C-golang-jwt.md b/en/content-en/C-golang-jwt.md
new file mode 100644
index 000000000..f60c6719e
--- /dev/null
+++ b/en/content-en/C-golang-jwt.md
@@ -0,0 +1,417 @@
+# C.32. JSON Web Token (JWT)
+
+Pada chapter ini kita akan belajar tentang JSON Web Token (JWT) dan cara penerapannya di bahasa Go.
+
+## C.32.1. Definisi
+
+JWT merupakan salah satu standar JSON ([RFC 7519](https://tools.ietf.org/html/rfc7519)) untuk keperluan akses token. Token dibentuk dari kombinasi beberapa informasi yang di-encode dan di-enkripsi. Informasi yang dimaksud adalah header, payload, dan signature.
+
+Contoh JWT:
+
+
+
+Skema JWT:
+
+
+
+ - **Header**, isinya adalah jenis algoritma yang digunakan untuk generate signature.
+ - **Payload**, isinya adalah data penting untuk keperluan otentikasi, seperti *grant*, *group*, kapan login terjadi, dan atau lainnya. Data ini dalam konteks JWT biasa disebut dengan **CLAIMS**.
+ - **Signature**, isinya adalah hasil dari enkripsi data menggunakan algoritma kriptografi. Data yang dimaksud adalah gabungan dari (encoded) header, (encoded) payload, dan secret key.
+
+Umumnya pada aplikasi yang menerapkan teknik otorisasi menggunakan token, token di-generate di back end secara acak (menggunakan algoritma tertentu) lalu disimpan di database bersamaan dengan data user. Token tersebut bisa jadi tidak ada isinya, hanya random string, atau mungkin saja ada isinya.
+
+Nantinya di setiap http call, token yang disertakan pada request header dicocokan dengan token yang ada di database, dilanjutkan dengan pengecekan grant/group/sejenisnya, untuk menentukan apakah request tersebut adalah verified request yang memang berhak mengakses endpoint.
+
+Pada aplikasi yang menerapkan JWT, yang terjadi sedikit berbeda. Token adalah hasil dari proses kombinasi, encoding, dan enkripsi terhadap beberapa informasi. Nantinya pada sebuah http call, pengecekan token tidak dilakukan dengan membandingkan token yang ada di request vs token yang tersimpan di database, karena memang token pada JWT tidak disimpan di database. Pengecekan token dilakukan dengan cara mengecek hasil decode dan decrypt token yang ditautkan dalam request.
+
+> Ada kalanya token JWT perlu juga untuk disimpan di back-end, misalnya untuk keperluan auto-invalidate session pada multiple login, atau kasus lainnya.
+
+Mungkin sekilas terlihat mengerikan, terlihat sangat gampang sekali untuk di-retas, buktinya adalah data otorisasi bisa dengan mudah diambil dari token JWT. Dan penulis sampaikan, bahwa ini adalah presepsi yang salah.
+
+Informasi yang ada dalam token, selain di-encode, juga di-enkripsi. Dalam enkripsi diperlukan private key atau secret key, dan hanya pengembang yang tau. Jadi pastikan simpan baik-baik key tersebut.
+
+Selama peretas tidak tau secret key yang digunakan, hasil decoding dan dekripsi data **PASTI TIDAK VALID**.
+
+## C.32.2. Persiapan Praktek
+
+Kita akan buat sebuah aplikasi RESTful web service sederhana, isinya dua buah endpoint `/index` dan `/login`. Berikut merupakan spesifikasi aplikasinya:
+
+ - Pengaksesan `/index` memerlukan token JWT.
+ - Token didapat dari proses otentikasi ke endpoint `/login` dengan menyisipkan username dan password.
+ - Pada aplikasi yang sudah kita buat, sudah ada data list user yang tersimpan di database (sebenarnya bukan di-database, tapi di file json).
+
+Ok, sekarang siapkan folder project baru.
+
+```bash
+mkdir chapter-c32
+cd chapter-c32
+go mod init chapter-c32
+
+# then prepare underneath structures
+
+tree .
+.
+├── go.mod
+├── main.go
+├── middleware.go
+└── users.json
+```
+
+#### • File `middleware.go`
+
+Lanjut isi file `middleware.go` dengan kode middleware yang sudah biasa kita gunakan.
+
+```go
+package main
+
+import "net/http"
+
+type CustomMux struct {
+ http.ServeMux
+ middlewares []func(next http.Handler) http.Handler
+}
+
+func (c *CustomMux) RegisterMiddleware(next func(next http.Handler) http.Handler) {
+ c.middlewares = append(c.middlewares, next)
+}
+
+func (c *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ var current http.Handler = &c.ServeMux
+
+ for _, next := range c.middlewares {
+ current = next(current)
+ }
+
+ current.ServeHTTP(w, r)
+}
+```
+
+#### • File `users.json`
+
+Juga isi file `users.json` yang merupakan database aplikasi. Silakan tambahkan data JSON berikut.
+
+```json
+[{
+ "username": "noval",
+ "password": "kaliparejaya123",
+ "email": "terpalmurah@gmail.com",
+ "group": "admin"
+}, {
+ "username": "farhan",
+ "password": "masokpakeko",
+ "email": "cikrakbaja@gmail.com",
+ "group": "publisher"
+}]
+```
+
+#### • File `main.go`
+
+Sekarang kita fokus ke file `main.go`. Import packages yang diperlukan. Salah satu dari packages tersebut adalah [golang-jwt/jwt](https://github.com/golang-jwt/jwt), yang digunakan untuk keperluan JWT.
+
+```bash
+go get -u github.com/golang-jwt/jwt/v4@v4.2.0
+go get -u github.com/novalagung/gubrak/v2
+```
+
+```go
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+
+ jwt "github.com/golang-jwt/jwt/v4"
+ gubrak "github.com/novalagung/gubrak/v2"
+)
+```
+
+Masih di file yang sama, siapkan 4 buah konstanta yaitu: nama aplikasi, durasi login, metode enkripsi token, dan secret key.
+
+```go
+type M map[string]interface{}
+
+var APPLICATION_NAME = "My Simple JWT App"
+var LOGIN_EXPIRATION_DURATION = time.Duration(1) * time.Hour
+var JWT_SIGNING_METHOD = jwt.SigningMethodHS256
+var JWT_SIGNATURE_KEY = []byte("the secret of kalimdor")
+```
+
+Kemudian buat fungsi `main()`, siapkan didalmnya sebuah `mux` baru, dan daftarkan middleware `MiddlewareJWTAuthorization` dan dua buah rute `/index` dan `/login`.
+
+```go
+func main() {
+ mux := new(CustomMux)
+ mux.RegisterMiddleware(MiddlewareJWTAuthorization)
+
+ mux.HandleFunc("/login", HandlerLogin)
+ mux.HandleFunc("/index", HandlerIndex)
+
+ server := new(http.Server)
+ server.Handler = mux
+ server.Addr = ":8080"
+
+ fmt.Println("Starting server at", server.Addr)
+ server.ListenAndServe()
+}
+```
+
+Middleware `MiddlewareJWTAuthorization` nantinya akan kita buat, tugasnya memvalidasi setiap request yang masuk, dengan cara mengecek token JWT yang disertakan. Middleware ini hanya berguna pada request ke selain endpoint `/login`, karena pada endpoint tersebut proses otentikasi terjadi.
+
+## C.32.3. Otentikasi & Generate Token
+
+Siapkan handler `HandlerLogin`. Tulis kode berikut.
+
+```go
+func HandlerLogin(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ http.Error(w, "Unsupported http method", http.StatusBadRequest)
+ return
+ }
+
+ username, password, ok := r.BasicAuth()
+ if !ok {
+ http.Error(w, "Invalid username or password", http.StatusBadRequest)
+ return
+ }
+
+ // ...
+}
+```
+
+Handler ini bertugas untuk meng-otentikasi client/consumer. Data username dan password dikirimkan ke endpoint dalam bentuk [B.18. HTTP Basic Auth](/B-http-basic-auth.html). Data tersebut kemudian disisipkan dalam pemanggilan fungsi otentikasi `authenticateUser()`, yang nantinya juga akan kita buat.
+
+```go
+ok, userInfo := authenticateUser(username, password)
+if !ok {
+ http.Error(w, "Invalid username or password", http.StatusBadRequest)
+ return
+}
+```
+
+Fungsi `authenticateUser()` memiliki dua nilai balik yang ditampung oleh variabel berikut:
+
+ - Variabel `ok`, penanda sukses tidaknya otentikasi.
+ - Variabel `userInfo`, isinya informasi user yang sedang login, datanya didapat dari `data.json` (tetapi tanpa password).
+
+Selanjutnya kita akan buat objek claims. Objek ini harus memenuhi persyaratan interface `jwt.Claims`. Objek claims bisa dibuat dari tipe `map` dengan cara membungkusnya dalam fungsi `jwt.MapClaims()`; atau dengan meng-embed interface `jwt.StandardClaims` pada struct baru, dan cara inilah yang akan kita pakai.
+
+Seperti yang sudah kita bahas di awal, bahwa claims isinya adalah data-data untuk keperluan otentikasi. Dalam prakteknya, claims merupakan sebuah objek yang memilik banyak property atau fields. Nah, objek claims **harus** memiliki fields yang termasuk di dalam list [JWT Standard Fields](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields). Dengan meng-embed interface `jwt.StandardClaims`, maka fields pada struct dianggap sudah terwakili.
+
+Pada aplikasi yang sedang kita kembangkan, claims selain menampung standard fields, juga menampung beberapa informasi lain, oleh karena itu kita perlu buat `struct` baru yang meng-embed `jwt.StandardClaims`.
+
+```go
+type MyClaims struct {
+ jwt.StandardClaims
+ Username string `json:"Username"`
+ Email string `json:"Email"`
+ Group string `json:"Group"`
+}
+```
+
+Ok, struct `MyClaims` sudah siap, sekarang buat objek baru dari struct tersebut.
+
+```go
+claims := MyClaims{
+ StandardClaims: jwt.StandardClaims{
+ Issuer: APPLICATION_NAME,
+ ExpiresAt: time.Now().Add(LOGIN_EXPIRATION_DURATION).Unix(),
+ },
+ Username: userInfo["username"].(string),
+ Email: userInfo["email"].(string),
+ Group: userInfo["group"].(string),
+}
+```
+
+Ada beberapa standard claims, pada contoh di atas hanya dua yang di-isi nilainya, `Issuer` dan `ExpiresAt`, selebihnya kita kosongi. Lalu 3 fields tambahan yang kita buat (username, email, dan group) di-isi menggunakan data yang didapat dari `userInfo`.
+
+ - `Issuer` (code `iss`), adalah penerbit JWT, dalam konteks ini adalah `APPLICATION_NAME`.
+ - `ExpiresAt` (code `exp`), adalah kapan token JWT dianggap expired.
+
+Ok, objek claims sudah siap, sekarang buat token baru. Pembuatannya dilakukan menggunakan fungsi `jwt.NewWithClaims()` yang menghasilkan nilai balik bertipe `jwt.Token`. Parameter pertama adalah metode enkripsi yang digunakan, yaitu `JWT_SIGNING_METHOD`, dan parameter kedua adalah `claims`.
+
+```go
+token := jwt.NewWithClaims(
+ JWT_SIGNING_METHOD,
+ claims,
+)
+```
+
+Kemudian tanda-tangani token tersebut menggunakan secret key yang sudah didefinisikan di `JWT_SIGNATURE_KEY`, caranya dengan memanggil method `SignedString()` milik objek `jwt.Token`. Pemanggilan method ini mengembalikan data token string yang kemudian dijadikan nilai balik handler. Token string inilah yang dibutuhkan client/consumer untuk bisa mengakses endpoints yang ada.
+
+```go
+signedToken, err := token.SignedString(JWT_SIGNATURE_KEY)
+if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+}
+
+tokenString, _ := json.Marshal(M{ "token": signedToken })
+w.Write([]byte(tokenString))
+```
+
+Bagian otentikasi dan generate token sebenarnya cukup sampai di sini. Tapi sebenarnya ada yang kurang, yaitu fungsi `authenticateUser()`. Silakan buat fungsi tersebut.
+
+```go
+func authenticateUser(username, password string) (bool, M) {
+ basePath, _ := os.Getwd()
+ dbPath := filepath.Join(basePath, "users.json")
+ buf, _ := ioutil.ReadFile(dbPath)
+
+ data := make([]M, 0)
+ err := json.Unmarshal(buf, &data)
+ if err != nil {
+ return false, nil
+ }
+
+ res := gubrak.From(data).Find(func(each M) bool {
+ return each["username"] == username && each["password"] == password
+ }).Result()
+
+ if res != nil {
+ resM := res.(M)
+ delete(resM, "password")
+ return true, resM
+ }
+
+ return false, nil
+}
+```
+
+Isi fungsi `authenticateUser()` cukup jelas, sesuai namanya, yaitu melakukan pencocokan username dan password dengan data yang ada di dalam file `users.json`.
+
+## C.32.4. JWT Authorization Middleware
+
+Sekarang kita perlu menyiapkan `MiddlewareJWTAuthorization`, yang tugasnya adalah mengecek setiap request yang masuk ke endpoint selain `/login`, apakah ada akses token yang dibawa atau tidak. Dan jika ada, akan diverifikasi valid tidaknya token tersebut.
+
+```go
+func MiddlewareJWTAuthorization(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ if r.URL.Path == "/login" {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ authorizationHeader := r.Header.Get("Authorization")
+ if !strings.Contains(authorizationHeader, "Bearer") {
+ http.Error(w, "Invalid token", http.StatusBadRequest)
+ return
+ }
+
+ tokenString := strings.Replace(authorizationHeader, "Bearer ", "", -1)
+
+ // ...
+ })
+}
+```
+
+Kita gunakan skema header `Authorization: Bearer `, sesuai spesifikasi [RFC 6750](https://tools.ietf.org/html/rfc6750) untuk keperluan penempatan dan pengambilan token.
+
+Token di-extract dari header, kemudian diparsing dan di-validasi menggunakan fungsi `jwt.Parse()`.
+
+```go
+token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
+ if method, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("Signing method invalid")
+ } else if method != JWT_SIGNING_METHOD {
+ return nil, fmt.Errorf("Signing method invalid")
+ }
+
+ return JWT_SIGNATURE_KEY, nil
+})
+if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+}
+```
+
+Parameter ke-2 fungsi `jwt.Parse()` berisikan callback untuk pengecekan valid tidak-nya signing method, jika valid maka secret key dikembalikan. Fungsi `jwt.Parse()` ini sendiri mengembalikan objek token.
+
+Dari objek token, informasi claims diambil, lalu dilakukan pengecekan valid-tidaknya claims tersebut.
+
+```go
+claims, ok := token.Claims.(jwt.MapClaims)
+if !ok || !token.Valid {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+}
+```
+
+O iya, mungkin ada pertanyaan kenapa objek claims yang dihasilkan `jwt.Parse()` tipenya bukan `MyClaims`. Hal ini karena setelah objek claims dimasukan dalam proses pembentukan token, lewat fungsi `jwt.NewWithClaims()`, objek akan di-encode ke tipe `jwt.MapClaims`.
+
+Data claims yang didapat disisipkan ke dalam context, agar nantinya di endpoint, informasi `userInfo` bisa diambil dengan mudah dari context request.
+
+```go
+ctx := context.WithValue(context.Background(), "userInfo", claims)
+r = r.WithContext(ctx)
+
+next.ServeHTTP(w, r)
+```
+
+#### • Handler Index
+
+Terakhir, kita perlu menyiapkan handler untuk rute `/index`.
+
+```go
+func HandlerIndex(w http.ResponseWriter, r *http.Request) {
+ userInfo := r.Context().Value("userInfo").(jwt.MapClaims)
+ message := fmt.Sprintf("hello %s (%s)", userInfo["Username"], userInfo["Group"])
+ w.Write([]byte(message))
+}
+```
+
+Informasi `userInfo` diambil dari context, lalu ditampilkan sebagai response endpoint.
+
+## C.32.5. Testing
+
+Jalankan aplikasi, lalu test menggunakan curl.
+
+#### • Otentikasi
+
+```bash
+curl -X POST --user noval:kaliparejaya123 http://localhost:8080/login
+```
+
+Output:
+
+
+
+#### • Mengakses Endpoint
+
+Test endpoint `/index`. Sisipkan token yang dikembalikan pada saat otentikasi, sebagai value header otorisasi dengan skema `Authorization: Bearer `.
+
+```bash
+curl -X GET \
+ --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg2NTI0NzksImlzcyI6Ik15IFNpbXBsZSBKV1QgQXBwIiwiVXNlcm5hbWUiOiJub3ZhbCIsIkVtYWlsIjoidGVycGFsbXVyYWhAZ21haWwuY29tIiwiR3JvdXAiOiJhZG1pbiJ9.JREJgUAYs2R5zuquqyX8tk23QlajVVe19susm6JsZq8" \
+ http://localhost:8080/index
+```
+
+Output:
+
+
+
+Semua berjalan sesuai harapan. Agar lebih meyakinkan, coba lakukan beberapa test dengan skenario yg salah, seperti:
+
+
+
+---
+
+ - [JWT Go](https://github.com/golang-jwt/jwt), by Dave Grijalva, MIT License
+ - [Gubrak v2](https://github.com/novalagung/gubrak), by Noval Agung, MIT License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 36c05df6b3b9b2282c42c073a2d61bc4a5bc2dac Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:34 +0700
Subject: [PATCH 105/522] New translations c-golang-ldap-authentication.md
(English)
---
en/content-en/C-golang-ldap-authentication.md | 326 ++++++++++++++++++
1 file changed, 326 insertions(+)
create mode 100644 en/content-en/C-golang-ldap-authentication.md
diff --git a/en/content-en/C-golang-ldap-authentication.md b/en/content-en/C-golang-ldap-authentication.md
new file mode 100644
index 000000000..dc3c5099a
--- /dev/null
+++ b/en/content-en/C-golang-ldap-authentication.md
@@ -0,0 +1,326 @@
+# C.33. LDAP Authentication
+
+Pada chapter ini kita belajar mengenai otentikasi user ke *Directory Service* lewat protokol LDAP. Pembelajaran akan kita awali dengan membahas sedikit mengenai definisi dari LDAP dan beberapa istilah relevan lainnya.
+
+## C.33.1. Definisi
+
+#### • LDAP
+
+LDAP (Lightweight Directory Access Protocol) adalah protokol yang digunakan untuk mengakses **Directory Services** dalam sebuah komunikasi client-server.
+
+#### • Directory Services
+
+Directory Services adalah sebuah sistem yang menyimpan, mengelola, dan menyediakan akses informasi untuk menghubungkan sumber daya network (atau network resources). Network resources yang dimaksud contohnya:
+
+- Email
+- Perangkat periferal
+- Komputer
+- Folder/Files
+- Printers
+- Nomor telephone
+- ...
+
+Cakupan network resources mulai dari hardware seperti komputer (atau lebih tinggi lagi) hingga aspek yang relatif kecil seperti file.
+
+Dengan terhubungnya resources tersebut, akan mudah bagi kita untuk mengelola banyak hal yang berhubungan dengan network. Contoh misalnya membuat aplikasi yang bisa login menggunakan credentials email kantor, atau banyak lainnya.
+
+Selain itu, juga LDAP sering dimanfaatkan dalam implementasi SSO (Single sign-on).
+
+#### • Bind Operation
+
+Operasi bind digunakan untuk otentikasi client ke directory server, dan juga untuk mengubah state otorisasi client tersebut. Operasi bind dilakukan dengan mengirim informasi bind dn dan password.
+
+#### • Directory Server untuk Testing
+
+Karena komunikasi adalah client-server maka kita perlu menggunakan salah satu directory server untuk keperluan testing. Beruntung-nya [Forum Systems](http://www.forumsys.com) berbaik hati menyediakan directory server yg bisa diakses secara gratis oleh public, dan pada chapter ini akan kita menggunakannya.
+
+Berikut adalah informasi credentials directory server tersebut:
+
+```
+Server: ldap.forumsys.com
+Port: 389
+Bind DN: cn=read-only-admin,dc=example,dc=com
+Bind Password: password
+```
+
+Terdapat beberapa user data di sana, seluruhnya memiliki password yang sama, yaitu: `password`.
+
+Lebih detailnya silakan cek di http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
+
+## C.33.2. LDAP Browser & Directory Client
+
+Silakan gunakan LDAP browser yang disukai, atau bisa gunakan [Apache Directory Studio](https://directory.apache.org/studio/).
+
+Buat koneksi baru menggunakan credentials di atas. Lakukan hingga berhasil memunculkan list user data seperti gambar berikut.
+
+
+
+Bisa dilihat bahwa terdapat beberapa user data. Nantinya testing akan dilkakukan dengan login menggunakan salah satu user tersebut.
+
+OK, sekarang mari kita masuk ke bagian tulis-menulis kode.
+
+## C.33.3. Login Web App
+
+Pertama kita buat terlebih dahulu aplikasi web sederhana, dengan dua buah rute dipersiapkan.
+
+- Landing page, memunculkan form login
+- Action login endpoint, untuk handle proses login
+
+Siapkan dulu project-nya:
+
+```bash
+mkdir chapter-c33
+cd chapter-c33
+go mod init chapter-c33
+```
+
+Buat file `main.go`, lalu siapkan html string untuk login form.
+
+```go
+package main
+
+import "net/http"
+import "fmt"
+import "html/template"
+
+// the port where web server will run
+const webServerPort = 9000
+
+// the login form html
+const view = `
+
+ Template
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+```
+
+Lalu buat fungsi `main()`, siapkan route handler landing page, parse html string di atas.
+
+```go
+http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ var tmpl = template.Must(template.New("main-template").Parse(view))
+ if err := tmpl.Execute(w, nil); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+})
+```
+
+Kemudian siapkan route handler untuk action login.
+
+```go
+http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+
+ username := r.PostFormValue("username")
+ password := r.PostFormValue("password")
+
+ // authenticate via ldap
+ ok, data, err := AuthUsingLDAP(username, password)
+ if !ok {
+ http.Error(w, "invalid username/password", http.StatusUnauthorized)
+ return
+ }
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusUnauthorized)
+ return
+ }
+
+ // greet user on success
+ message := fmt.Sprintf("welcome %s", data.FullName)
+ w.Write([]byte(message))
+})
+```
+
+Pada kode di atas proses otentikasi di handle secara implisit oleh fungsi `AuthUsingLDAP()`, yang akan kita buat pastinya. Ketika user sukses melakukan login maka pesan `selamat datang` akan ditampilkan disertai dengan nama lengkap user (data nama didapat nantinya dari user data di directory service).
+
+OK, dua handler sudah siap, tambahkan kode untuk start webserver.
+
+```go
+portString := fmt.Sprintf(":%d", webServerPort)
+fmt.Println("server started at", portString)
+http.ListenAndServe(portString, nil)
+```
+
+## C.33.4. Handling the LDAP Authentication
+
+Masuk ke bagian kode LDAP, buat file baru di folder yang sama, `ldap.go`. Import library ldap dan siapkan beberapa konstanta.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/go-ldap/ldap"
+)
+
+const (
+ ldapServer = "ldap.forumsys.com"
+ ldapPort = 389
+ ldapBindDN = "cn=read-only-admin,dc=example,dc=com"
+ ldapPassword = "password"
+ ldapSearchDN = "dc=example,dc=com"
+)
+```
+
+3rd party lib [github.com/go-ldap/ldap](https://github.com/go-ldap/ldap) v3 kita gunakan untuk melakukan operasi client-server dengan directory server. Silakan `go get` terlebih dahulu jika belum.
+
+```bash
+go get -u github.com/go-ldap/ldap/v3
+```
+
+Beberapa konstanta di atas isinya sesuai dengan credentials directory server test yang di atas sudah kita bahas. Diluar itu ada satu tambahan konstanta, yaitu `ldapSearchDN`, nantinya kita perlukan dalam melakukan operasi search.
+
+Selanjutnya, siapkan struct untuk menampung user data yang dikembalikan oleh directory server.
+
+```go
+type UserLDAPData struct {
+ ID string
+ Email string
+ Name string
+ FullName string
+}
+```
+
+Buat fungsi `AuthUsingLDAP()`, lalu inisialisasi koneksi ldap. Fungsi ini menerima dua parameter, username dan password, yang keduanya diinputkan oleh user nantinya lewat browser.
+
+```go
+func AuthUsingLDAP(username, password string) (bool, *UserLDAPData, error) {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ return false, nil, err
+ }
+ defer l.Close()
+
+ // ...
+}
+```
+
+Fungsi `ldap.Dial()` digunakan untuk melakukan handshake dengan directory server.
+
+Setelah itu lakukan bind operation menggunakan Bind DN dan password sesuai konstanta yang sudah dipersiapkan. Gunakan method `.Bind()` milik objek hasil dial ldap untuk bind.
+
+```go
+err = l.Bind(ldapBindDN, ldapPassword)
+if err != nil {
+ return false, nil, err
+}
+```
+
+Setelah bind sukses, lakukan operasi search. Siapkan terlebih dahulu objek search request. Klausa search yang digunakan adalah `uid` atau username dengan base dn adalah `ldapSearchDN`.
+
+```go
+searchRequest := ldap.NewSearchRequest(
+ ldapSearchDN,
+ ldap.ScopeWholeSubtree,
+ ldap.NeverDerefAliases,
+ 0,
+ 0,
+ false,
+ fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", username),
+ []string{"dn", "cn", "sn", "mail"},
+ nil,
+)
+```
+
+Klausa dituliskan dalam bentuk **LDAP Filter Syntax**. Bisa dilihat di atas merupakan contoh filter uid/username. Lebih jelasnya mengenai sintaks ini silakan merujuk ke http://www.ldapexplorer.com/en/manual/109010000-ldap-filter-syntax.htm
+
+Parameter setelah filter adalah informasi atributtes yang ingin kita dapat dari hasil search request.
+
+Berikutnya, trigger object search request tersebut lewat method `.Search()` dari objek ldap. Cek data yang dikembalikan (accessible via `.Entries` property), jika tidak ada data maka kita asumsikan search gagal.
+
+```go
+sr, err := l.Search(searchRequest)
+if err != nil {
+ return false, nil, err
+}
+
+if len(sr.Entries) == 0 {
+ return false, nil, fmt.Errorf("User not found")
+}
+entry := sr.Entries[0]
+```
+
+Jika semuanya berjalan lancar, maka kita akan dapat setidaknya 1 user data. Lakukan bind operation menggunakan DN milik user tersebut dengan password adalah yang diinputkan user. Ini diperlukan untuk mem-validasi apakah password yang di-inputkan user sudah benar atau tidak.
+
+```go
+err = l.Bind(entry.DN, password)
+if err != nil {
+ return false, nil, err
+}
+```
+
+Jika semua masih berjalan lancar, berarti proses otentikasi bisa dipastikan berhasil. Tapi ada satu hal lagi yang perlu dilakukan, yaitu serialisasi data attributes kembalian dari server untuk fit ke object `UserLDAPData` yang sudah disiapkan.
+
+```go
+data := new(UserLDAPData)
+data.ID = username
+
+for _, attr := range entry.Attributes {
+ switch attr.Name {
+ case "sn":
+ data.Name = attr.Values[0]
+ case "mail":
+ data.Email = attr.Values[0]
+ case "cn":
+ data.FullName = attr.Values[0]
+ }
+}
+
+return true, data, nil
+```
+
+## C.33.5. Testing
+
+Ok, mari kita test. Jalankan program, lalu akses http://localhost:9000/; Lakukan login menggunakan salah satu user yang ada (silakan cek di LDAP browser anda). Di sini saya pilih menggunakan user `boyle`, password-nya `password` (semua user ber-kata-sandi sama).
+
+
+
+Login berhasil, dibuktikan dengan munculnya fullname **Robert Boyle**. Coba juga gunakan password yang salah agar lebih meyakinkan.
+
+
+
+## C.33.6. LDAP TLS
+
+Untuk koneksi LDAP yang TLS-enabled, maka cukup panggil method `.StartTLS()` milik objek ldap dial, dan isi parameternya dengan tls config. Pemanggilan fungsi ini harus tepat setelah proses dial ke directory server.
+
+```go
+l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+if err != nil {
+ return false, nil, err
+}
+defer l.Close()
+
+// reconnect with TLS
+err = l.StartTLS(tlsConfig)
+if err != nil {
+ return false, nil, err
+}
+```
+
+---
+
+ - [go-ldap/ldap](https://github.com/go-ldap/ldap), by go-ldap team, MIT License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 5b077128a1c0b88c448bd92110826104dce15a1d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:35 +0700
Subject: [PATCH 106/522] New translations c-golang-protobuf-implementation.md
(English)
---
.../C-golang-protobuf-implementation.md | 402 ++++++++++++++++++
1 file changed, 402 insertions(+)
create mode 100644 en/content-en/C-golang-protobuf-implementation.md
diff --git a/en/content-en/C-golang-protobuf-implementation.md b/en/content-en/C-golang-protobuf-implementation.md
new file mode 100644
index 000000000..4641baf23
--- /dev/null
+++ b/en/content-en/C-golang-protobuf-implementation.md
@@ -0,0 +1,402 @@
+# C.30. Protobuf
+
+Pada chapter ini kita akan belajar tentang penggunaan protobuf (Protocol Buffers) di Go. Topik gRPC kita pelajari pada chapter selanjutnya (bukan pada chapter ini).
+
+## C.30.1. Definisi
+
+Protocol Buffers adalah metode untuk serialisasi data terstruktur, yang dibuat oleh Google. Protobuf cocok digunakan pada aplikasi yang berkomunikasi dengan aplikasi lain. Protobuf bisa dipakai di banyak platform, contoh: komunikasi antara aplikasi mobile iOS dan Go Web Service, bisa menggunakan protobuf.
+
+Protobuf hanya bertugas di bagian serialisasi data saja, untuk komunikasi antar service atau antar aplikasi sendiri menggunakan gRPC.
+
+> gRPC adalah sebuah remote procedure call atau RPC yang dibuat oleh google. gRPC menggunakan HTTP/2 untuk komunikasinya, dan Protocol Buffers di bagian antarmuka-nya.
+
+Mungkin sampai sini masih terasa abstrak, membingungkan, dan muncul banyak pertanyaan mengenai apa dan untuk apa protobuf ini. Agar lebih mudah untuk dipahami, bayangkan sebuah aplikasi client yang mengkonsumsi data dari (RESTful) web service, dengan data dikirimkan dalam bentuk JSON.
+
+
+
+Di analogi sederhana di gambar, dijelaskan bahwa HTTP digunakan sebagai transport antara client dan server, dan JSON digunakan sebagai tipe data payload request dan response body type.
+
+Arsitektur di atas (yang menggunakan http dan json) jika dikonversi ke bentuk gRPC dan protobuf, maka kurang lebih jadinya seperti gambar di bawah ini.
+
+
+
+Cukup bisa dipahami bukan?
+
+Sederhananya protobuf itu mirip seperti JSON atau XML, tapi lebih terstruktur. Perbedaannya adalah pada protobuf skema model harus didefinisikan di awal (*schema on write*).
+
+Skema tersebut didefinisikan dalam file berekstensi `.proto`. Dari file tersebut di generate-lah file model sesuai bahasa yang dipilih. Misalkan bahasa yang digunakan adalah java, maka nantinya terbentuk pojo; misal bahasa adalah php nantinya class di-generate; jika bahasa Go maka struct di-generate, dan seterusnya.
+
+> gRPC dan protobuf adalah salah satu pilihan terbaik untuk diterapkan pada aplikasi yang mengadopsi konsep microservices.
+
+## C.30.2. Instalasi
+
+Schema yang ditulis dalam `.proto` di-compile ke bentuk file sesuai bahasa yang dipilih. Dari sini jelasnya sebuah compiler dibutuhkan, maka dari itu protobuf harus install terlebih dahulu di lokal masing-masing. Compiler tersebut bernama `protoc` atau proto compiler.
+
+Silakan merujuk ke http://google.github.io/proto-lens/installing-protoc.html untuk mengetahui cara instalasi `protoc` sesuai sistem operasi yang dibutuhkan.
+
+Selain `protoc`, masih ada satu lagi yang perlu di install, yaitu protobuf runtime untuk Go (karena di sini kita menggunakan bahasa Go). Cara instalasinya cukup mudah:
+
+```bash
+go get -u github.com/golang/protobuf/protoc-gen-go
+```
+
+Wajib mengeksekusi command di atas di luar Go Modules, agar dependency tersebut binary nya tersimpan dalam $GOPATH/bin. Jika tidak, maka bisa muncul masalah (see https://stackoverflow.com/a/30097929/1467988).
+
+> Protobuf runtime tersedia untuk banyak bahasa selain Go, selengkapnya silakan cek https://github.com/protocolbuffers/protobuf.
+
+## C.30.3. Pembuatan File `.Proto`
+
+Siapkan satu buah folder dengan skema seperti berikut.
+
+```bash
+mkdir chapter-c29
+cd chapter-c29
+go mod init chapter-c29
+
+# then prepare underneath structures
+tree .
+.
+├── main.go
+└── model
+ ├── garage.proto
+ └── user.proto
+```
+
+Folder `yourproject/model` berisikan file-file `.proto` (dua buah file proto didefinisikan). Dari kedua file di atas akan di-generate file model `.go` menggunakan command `protoc`. Nantinya generated file tersebut dipakai dalam `main.go`.
+
+#### • File `user.proto`
+
+OK, mari kita masuk ke bagian tulis-menulis kode. Buka file `user.proto`, tulis kode berikut.
+
+```protobuf
+syntax = "proto3";
+
+package model;
+
+enum UserGender {
+ UNDEFINED = 0;
+ MALE = 1;
+ FEMALE = 2;
+}
+```
+
+Bahasa yang digunakan dalam file proto sangat minimalis, dan cukup mudah untuk dipahami.
+
+Statement `syntax = "proto3";`, artinya adalah versi proto yang digunakan adalah `proto3`. Ada juga versi `proto2`, namun kita tidak menggunakannya.
+
+Statement `package model;`, digunakan untuk menge-set nama package dari file yang nantinya di-generate. File `user.proto` akan di-compile, menghasilkan file `user.pb.go`. File Go tersebut package-nya adalah sesuai dengan yang sudah didefinisikan di statement, pada contoh ini `model`.
+
+Statement `enum UserGender` digunakan untuk pendefinisian enum. Tulis nama-nama enum beserta value di dalam kurung kurawal. Keyword `UserGender` sendiri nantinya menjadi tipe enum. Value enum di protobuf hanya bisa menggunakan tipe data numerik int.
+
+Setelah proses kompilasi ke bentuk Go, kode di atas kurang lebihnya akan menjadi seperti berikut.
+
+```go
+package model
+
+type UserGender int32
+
+const (
+ UserGender_UNDEFINED UserGender = 0
+ UserGender_MALE UserGender = 1
+ UserGender_FEMALE UserGender = 2
+)
+```
+
+Selanjutnya tambahkan statement pendefinisian message berikut dalam file `user.proto`.
+
+```protobuf
+message User {
+ string id = 1;
+ string name = 2;
+ string password = 3;
+ UserGender gender = 4;
+}
+
+message UserList {
+ repeated User list = 1;
+}
+```
+
+> Untuk mengurangi miskomunikasi, penulis gunakan istilah **model** untuk `message` pada kode di atas.
+
+Model didefinisikan menggunakan keyword `message` diikuti dengan nama model-nya. Di dalam kurung kurawal, ditulis property-property model dengan skema penulisan ` = `.
+
+> Bisa dilihat bahwa di tiap field terdapat *unique number*. Informasi ini berguna untuk versioning model protobuf. Tiap field harus memiliki angka yang unik satu sama lain dalam satu `message`.
+
+Di dalam `User`, dideklarasikan property `id`, `name`, dan `password` yang bertipe `string`; dan juga property `gender` yang bertipe enum `UserGender`.
+
+Selain `User`, model `UserList` juga didefinisikan. Isinya hanya satu property yaitu `list` yang tipe-nya adalah `User`. Keyword repeated pada property digunakan untuk pendefinisian tipe array atau slice. Statement `repeated User` adalah ekuivalen dengan `[]*User` (tipe element slice pasti pointer).
+
+Kode protobuf di atas menghasilkan kode Go berikut.
+
+```go
+type User struct {
+ Id string
+ Name string
+ Password string
+ Gender UserGender
+}
+
+type UserList struct {
+ List []*User
+}
+```
+
+#### • File `garage.proto`
+
+Sekarang beralih ke file ke-dua, `garage.proto`. Silakan tulis kode berikut.
+
+```protobuf
+syntax = "proto3";
+
+package model;
+
+message GarageCoordinate {
+ float latitude = 1;
+ float longitude = 2;
+}
+
+message Garage {
+ string id = 1;
+ string name = 2;
+ GarageCoordinate coordinate = 3;
+}
+```
+
+Bisa dilihat, property `coordinate` pada model `Garage` tipe-nya adalah model juga, yaitu `GarageCoordinate`.
+
+Di atas, tipe data `float` digunakan pada pendefinisian property `latitude` dan `longitude`. Silakan merujuk ke link berikut untuk mengetahui tipe-tipe primitif apa saja yang bisa digunakan sebagai tipe data property model https://developers.google.com/protocol-buffers/docs/proto3#scalar.
+
+Buat dua buah model lainnya berikut ini.
+
+```protobuf
+message GarageList {
+ repeated Garage list = 1;
+}
+
+message GarageListByUser {
+ map list = 1;
+}
+```
+
+Selain array/slice, tipe `map` juga bisa digunakan pada protobuf. Gunakan keyword `map` untuk mendefinisikan tipe map. Penulisannya disertai dengan penulisan tipe data key dan tipe data value map tersebut.
+
+> Penulisan tipe data map mirip seperti penulisan HashMap pada java yang disisipkan juga tipe generics-nya.
+
+Kembali ke topik, dua message di atas akan menghasilkan kode Go berikut.
+
+```go
+type GarageList struct {
+ List []*Garage
+}
+
+type GarageListByUser struct {
+ List map[string]*GarageList
+}
+```
+
+## C.30.4. Kompilasi File `.Proto`
+
+File `.proto` sudah siap, sekarang saatnya untuk meng-compile file-file tersebut agar menjadi `.go`. Kompilasi dilakukan lewat command `protoc`. Agar output berupa file Go, maka gunakan flag `--go_out`. Lebih jelasnya silakan ikut command berikut.
+
+```bash
+cd chapter-c29/model
+
+PATH=$PATH:$GOPATH/bin/ protoc --go_out=. *.proto
+
+tree .
+.
+├── garage.pb.go
+├── garage.proto
+├── user.pb.go
+└── user.proto
+
+0 directories, 4 files
+```
+
+Dua file baru dengan ekstensi `.pb.go` muncul.
+
+## C.30.5. Praktek
+
+Sekarang kita akan belajar tentang pengoperasian file proto yang sudah di-generate. Buka file `main.go`, tulis kode berikut.
+
+```go
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+
+ // sesuaikan dengan struktuk folder project masing2
+ "chapter-c29/model"
+)
+
+func main() {
+ // more code here ...
+}
+```
+
+Package model yang isinya generated proto file, di-import. Dari package tersebut, kita bisa mengakses generated struct seperti `model.User`, `model.GarageList`, dan lainnya. Maka coba buat beberapa objek untuk ke semua generated struct.
+
+ - Objek struct `model.User`:
+
+ ```go
+ var user1 = &model.User{
+ Id: "u001",
+ Name: "Sylvana Windrunner",
+ Password: "f0r Th3 H0rD3",
+ Gender: model.UserGender_FEMALE,
+ }
+ ```
+ Untuk tipe enum pengaksesannya seperti di atas, penulisannya `model.UserGender_*`. Cukup ubah `*` dengan value yang diinginkan, `UNDEFINED`, `MALE`, atau `FEMALE`.
+
+ - Objek struct `model.UserList`:
+
+ ```go
+ var userList = &model.UserList{
+ List: []*model.User{
+ user1,
+ },
+ }
+ ```
+ Disarankan untuk selalu menampung objek cetakan struct protobuf dalam bentuk pointer, karena dengan itu objek akan memenuhi kriteria interface `proto.Message`, yang nantinya akan sangat membantu proses koding.
+
+ - Objek struct `model.Garage`:
+
+ ```go
+ var garage1 = &model.Garage{
+ Id: "g001",
+ Name: "Kalimdor",
+ Coordinate: &model.GarageCoordinate{
+ Latitude: 23.2212847,
+ Longitude: 53.22033123,
+ },
+ }
+ ```
+
+ - Objek struct `model.GarageList`:
+
+ ```go
+ var garageList = &model.GarageList{
+ List: []*model.Garage{
+ garage1,
+ },
+ }
+ ```
+
+ - Objek struct `model.GarageListByUser`:
+
+ ```go
+ var garageListByUser = &model.GarageListByUser{
+ List: map[string]*model.GarageList{
+ user1.Id: garageList,
+ },
+ }
+ ```
+
+#### • Print proto object
+
+Print salah satu objek yang sudah dibuat di atas.
+
+```go
+// =========== original
+fmt.Printf("# ==== Original\n %#v \n", user1)
+
+// =========== as string
+fmt.Printf("# ==== As String\n %v \n", user1.String())
+```
+
+Jalankan aplikasi untuk melihat hasilnya.
+
+
+
+Pada statement print pertama, objek ditampilkan apa adanya. Generated struct memiliki beberapa property lain selain yang sudah didefinisikan pada proto message, seperti `XXX_unrecognized` dan beberapa lainnya. Property tersebut dibutuhkan oleh protobuf, tapi tidak kita butuhkan, jadi biarkan saja.
+
+Di statement print kedua, method `.String()` diakses, menampilkan semua property yang didefinisikan dalam proto message (property `XXX_` tidak dimunculkan).
+
+#### • Konversi objek proto ke json string
+
+Tambahkan kode berikut:
+
+```go
+// =========== as json string
+var buf bytes.Buffer
+err1 := (&jsonpb.Marshaler{}).Marshal(&buf, garageList)
+if err1 != nil {
+ fmt.Println(err1.Error())
+ os.Exit(0)
+}
+jsonString := buf.String()
+fmt.Printf("# ==== As JSON String\n %v \n", jsonString)
+```
+
+Di atas kita membuat banyak objek lewat generated struct. Objek tersebut bisa dikonversi ke bentuk JSON string, caranya dengan memanfaatkan package [github.com/golang/protobuf/jsonpb](https://github.com/golang/protobuf/). Lakukan `go get` pada package jika belum punya, dan jangan lupa untuk meng-importnya pada file yang sedang dibuat.
+
+```bash
+go get -u github.com/golang/protobuf@v1.3.2
+```
+
+Kembali ke pembahasan, buat objek pointer baru dari struct `jsonpb.Marshaler`, lalu akses method `.Marshal()`. Sisipkan objek bertipe `io.Writer` penampung hasil konversi sebagai parameter pertama (pada contoh di atas kita gunakan `bytes.Buffer`). Lalu sisipkan juga objek proto yang ingin dikonversi sebagai parameter kedua.
+
+Jalankan aplikasi, cek hasilnya.
+
+
+
+Selain method `.Marshal()`, konversi ke json string bisa dilakukan lewat method `.MarshalToString()`.
+
+#### • Konversi json string ke objek proto
+
+Proses unmarshal dari json string ke objek proto, bisa dilakukan lewat dua cara:
+
+ - Membuat objek pointer dari struct `jsonpb.Unmarshaler`, lalu mengakses method `.Unmarshal()`. Parameter pertama diisi dengan objek `io.Reader` yang isinya json string, dan parameter kedua adalah receiver.
+
+ ```go
+ buf2 := strings.NewReader(jsonString)
+ protoObject := new(model.GarageList)
+
+ err2 := (&jsonpb.Unmarshaler{}).Unmarshal(buf2, protoObject)
+ if err2 != nil {
+ fmt.Println(err2.Error())
+ os.Exit(0)
+ }
+
+ fmt.Printf("# ==== As String\n %v \n", protoObject.String())
+ ```
+
+ - Menggunakan `jsonpb.UnmarshalString`, dengan parameter pertama disisipi data json string.
+
+ ```go
+ protoObject := new(model.GarageList)
+
+ err2 := jsonpb.UnmarshalString(jsonString, protoObject)
+ if err2 != nil {
+ fmt.Println(err2.Error())
+ os.Exit(0)
+ }
+
+ fmt.Printf("# ==== As String\n %v \n", protoObject.String())
+ ```
+
+Silakan pilih cara yang cocok sesuai kebutuhan. Lalu jalankan aplikasi dan lihat hasilnya.
+
+
+
+## C.30.6. gRPC + Protobuf
+
+Pada chapter selanjutnya kita akan belajar tentang penerapan gRPC dan protobuf.
+
+---
+
+ - [Protobuf](https://github.com/golang/protobuf), by Google, BSD-3-Clause License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From f15fefd1bba8be7edf87caaf4d6be4b636f74b95 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:36 +0700
Subject: [PATCH 107/522] New translations c-golang-redis.md (English)
---
en/content-en/C-golang-redis.md | 172 ++++++++++++++++++++++++++++++++
1 file changed, 172 insertions(+)
create mode 100644 en/content-en/C-golang-redis.md
diff --git a/en/content-en/C-golang-redis.md b/en/content-en/C-golang-redis.md
new file mode 100644
index 000000000..952e66a4c
--- /dev/null
+++ b/en/content-en/C-golang-redis.md
@@ -0,0 +1,172 @@
+# C.36. Redis
+
+Pada bab ini kita akan belajar cara menggunakan Redis, dari cara koneksi Redis, cara menyimpan data dan cara mengambil data. Untuk command selengkapnya bisa dilihat di https://redis.io/commands.
+
+## C.36.1 Apa itu Redis?
+
+Redis, singkatan dari *Remote Dictionary Server*, adalah penyimpanan data nilai utama di dalam memori yang super cepat. Umumnya Redis dimanfaatkan sebagai database, cache, manajemen session, *message broker*, *queue*, dan jenis kebutuhan lainnya yg relevan dengan operasi real-time dan temporary data.
+
+Salah satu library Redis untuk Go yang populer ketika artikel ini dibuat adalah [go-redis/redis](https://github.com/go-redis/redis), dan pada chapter ini kita akan mempelajarinya.
+
+## C.36.2 Koneksi ke Redis
+
+Sebelum kita mulai, pastikan Redis server sudah ter-install dan sudah berjalan di sistem operasi dengan baik. Lebih jelasnya perihal instalasi redis bisa merujuk ke https://redis.io/topics/quickstart.
+
+Ok, buat proyek baru, lalu buat file baru dengan nama `main.go`. Definisikan package dan import dependensi.
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "time"
+
+ "github.com/go-redis/redis/v8"
+)
+```
+
+> Jangan lupa untuk `go get -u github.com/go-redis/redis/v8` dependensi go-redis
+
+Selanjutnya siapkan fungsi `newRedisClient()` untuk membuat objek Redis client yang nantinya digunakan dalam operasi.
+
+```go
+func newRedisClient(host string, password string) *redis.Client {
+ client := redis.NewClient(&redis.Options{
+ Addr: host,
+ Password: password,
+ DB: 0,
+ })
+ return client
+}
+```
+
+Sekarang siapkan fungsi `main()` dengan isi kode untuk inisialisasi objek redis client. Di sini saya menggunakan `localhost:6379` sebagai host tujuan koneksi redis, karena redis server saya install di lokal.
+
+```go
+func main() {
+ var redisHost = "localhost:6379"
+ var redisPassword = ""
+
+ rdb := newRedisClient(redisHost, redisPassword)
+ fmt.Println("redis client initialized")
+
+ // ...
+}
+```
+## C.36.3 Menyimpan data ke Redis dengan perintah `SET`
+
+Sekarang kita akan belajar cara menyimpan data menggunakan salah satu command Redis, yaitu `SET`.
+
+> Perlu diketahui bahwa command untuk menyimpan data di redis tidak hanya `SET`, ada juga lainnya seperti `SETTEX`, `HMSET`, `SETBIT`, `LSET`, RSET, `HSETNX`, masing-masing perintah memiliki kegunaan sendiri. Selengkapnya bisa merujuk ke https://redis.io/commands.
+
+Salah satu benefit yang didapat dengan menggunakan `go-redis` library, adalah sudah disediakan wrapper untuk command-command redis, termasuk command `SET`.
+
+Ok, sekarang tambahkan kode berikut:
+
+```go
+func () {
+ // ...
+
+ key := "key-1"
+ data := "Hello Redis"
+ ttl := time.Duration(3) * time.Second
+
+ // store data using SET command
+ op1 := rdb.Set(context.Background(), key, data, ttl)
+ if err := op1.Err(); err != nil {
+ fmt.Printf("unable to SET data. error: %v", err)
+ return
+ }
+ log.Println("set operation success")
+
+ // ...
+}
+```
+
+Pada kode di atas disiapkan variabel `key` yang difungsikan sebagai identifier untuk data yang kita simpan. Data yang disimpan adalah string `Hello Redis`. Data akan di-retain di redis server selama 3 detik (lihat variabel `ttl`).
+
+Variabel objek `rdb` adalah redis client yang kita buat sebelumnya via fungsi `newRedisClient()`. Lewat variabel ini kita panggil method `.Set()` untuk menyimpan data menggunakan command redis `SET`.
+
+## C.36.4 Mengambil data dar Redis dengan perintah `GET`
+
+Command redis untuk pengambilan data adalah `GET`. Menggunakan go-redis library, kita cukup memanfaatkan method `.Get` dari objek redis client untuk pengambilan data.
+
+Tambahkan kode berikut pada main.
+
+```go
+func main() {
+ // ...
+
+ // get data
+ op2 := rdb.Get(context.Background(), key)
+ if err := op2.Err(); err != nil {
+ fmt.Printf("unable to GET data. error: %v", err)
+ return
+ }
+ res, err := op2.Result()
+ if err != nil {
+ fmt.Printf("unable to GET data. error: %v", err)
+ return
+ }
+ log.Println("get operation success. result:", res)
+}
+```
+
+Method `.Get()` mengembalikan objek bertipe `*redis.StringCmd`. Cara penggunaan method ini cukup pass variabel konteks dan identifier key.
+
+Dari objek bertipe `*redis.StringCmd` tersebut kita bisa ambil minimal 2 hal:
+
+- informasi error, menggunakan method `.Err()`
+- data yang tersimpan sesuai key, menggunakan method `.Result()`
+
+## C.36.5 Test
+
+Jalankan program, lihat hasilnya.
+
+
+
+Sampai sini bisa disimpulkan operasi `SET` dan `GET` adalah sukses.
+
+## C.36.6 Redis expiration time (`TTL`)
+
+Mari kita sedikit modifikasi kode yang ada. Tambahkan `time.Sleep` untuk menghentikan eksekusi kode selama 4 detik.
+
+```go
+func main() {
+ // ...
+ log.Println("set operation success")
+
+ time.Sleep(time.Duration(4) * time.Second) // <----- add this code
+
+ // get data
+ op2 := rdb.Get(context.Background(), key)
+ if err := op2.Err(); err != nil {
+ fmt.Printf("unable to GET data. error: %v", err)
+ return
+ }
+ // ...
+}
+```
+
+Jalankan program dan lihat hasilnya.
+
+
+
+Error, ini karena data yang disimpan hanya di retain sesuai `ttl` yaitu 3 detik, dan pada kode di atas kita mencoba ambil datanya setelah detik ke 4. Jadi data tersebut expired.
+
+---
+
+ - [go-redis](https://github.com/redis/go-redis), by Redis, BSD-2-Clause License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From aa2944ea5a1ac6d1b68410cb588eed3cdced6389 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:37 +0700
Subject: [PATCH 108/522] New translations c-golang-ssh-sftp.md (English)
---
en/content-en/C-golang-ssh-sftp.md | 274 +++++++++++++++++++++++++++++
1 file changed, 274 insertions(+)
create mode 100644 en/content-en/C-golang-ssh-sftp.md
diff --git a/en/content-en/C-golang-ssh-sftp.md b/en/content-en/C-golang-ssh-sftp.md
new file mode 100644
index 000000000..1941183d2
--- /dev/null
+++ b/en/content-en/C-golang-ssh-sftp.md
@@ -0,0 +1,274 @@
+# C.29. SSH & SFTP
+
+Pada chapter ini kita akan belajar cara untuk me-remote server lewat protokol SSH (Secure Shell).
+
+> Protokol SSH digunakan untuk melakukan remote login secara aman/secure ke server tujuan. Komunikasi yang terjadi lewat SSH di-encrypt sehingga aman.
+
+Go menyediakan package [golang.org/x/crypto/ssh](https://godoc.org/golang.org/x/crypto/ssh), berisi cukup banyak API untuk keperluan operasi yang berhubungan dengan protokol SSH.
+
+## C.29.1. Otentikasi SSH
+
+Buat folder project baru, isinya file `main.go`, di dalamnya tulis kode berikut.
+
+```go
+package main
+
+import (
+ "golang.org/x/crypto/ssh"
+ "io/ioutil"
+ "log"
+ "os"
+)
+
+func main() {
+ const SSH_ADDRESS = "0.0.0.0:22"
+ const SSH_USERNAME = "user"
+ const SSH_PASSWORD = "password"
+
+ sshConfig := &ssh.ClientConfig{
+ User: SSH_USERNAME,
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ Auth: []ssh.AuthMethod{
+ ssh.Password(SSH_PASSWORD),
+ },
+ }
+
+ // ...
+}
+```
+
+Variabel `sshConfig` di atas adalah objek pointer cetakan struct `ssh.ClientConfig`. Objek bertipe ini nantinya digunakan untuk otentikasi SSH.
+
+Pada kode di atas, tiga buah konstanta dengan prefix `SSH_*` disiapkan. Credentials username dan password disisipkan sebagai property objek `sshConfig`. Bisa dilihat pada property `Auth`, isinya adalah slice `ssh.AuthMethod` dengan satu buah element yaitu `ssh.Password()`. Konfigurasi seperti ini dipakai jika **otentikasi menggunakan username dan password**.
+
+Jika otentikasi dilakukan menggunakan **identity file**, maka gunakan kode berikut.
+
+```go
+const SSH_ADDRESS = "192.168.0.24:22"
+const SSH_USERNAME = "user"
+const SSH_KEY = "path/to/file/identity.pem"
+
+sshConfig := &ssh.ClientConfig{
+ User: SSH_USERNAME,
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ Auth: []ssh.AuthMethod{
+ PublicKeyFile(SSH_KEY),
+ },
+}
+
+func PublicKeyFile(file string) ssh.AuthMethod {
+ buffer, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil
+ }
+
+ key, err := ssh.ParsePrivateKey(buffer)
+ if err != nil {
+ return nil
+ }
+
+ return ssh.PublicKeys(key)
+}
+```
+
+Silakan pilih jenis otentikasi sesuai dengan yang di dukung oleh remote server.
+
+Selanjutnya, dalam fungsi `main()`, buat koneksi baru ke server tujuan menggunakan `ssh.Dial()`. Statement ini mengembalikan objek `*ssh.Client`, pemanggilannya sendiri membutuhkan 3 parameter.
+
+ - Isi argument pertama isi dengan `"tcp"`, hal ini dikarenakan protokol tersebut digunakan dalam SSH.
+ - Argument ke-2 diisi dengan alamat tujuan server.
+ - Argument ke-3 diisi dengan objek `sshConfig`.
+
+```go
+// ...
+
+client, err := ssh.Dial("tcp", SSH_ADDRESS, sshConfig)
+if client != nil {
+ defer client.Close()
+}
+if err != nil {
+ log.Fatal("Failed to dial. " + err.Error())
+}
+```
+
+## C.29.2. Session & Run Command
+
+Dari objek `client`, buat session baru, caranya dengan mengakses method `.NewSession()`.
+
+```go
+session, err := client.NewSession()
+if session != nil {
+ defer session.Close()
+}
+if err != nil {
+ log.Fatal("Failed to create session. " + err.Error())
+}
+```
+
+Pada session, set tiga koneksi standar IO: stdin, stdout, dan stderr; ke standard IO sistem operasi.
+
+```go
+session.Stdin = os.Stdin
+session.Stdout = os.Stdout
+session.Stderr = os.Stderr
+```
+
+OK, semua persiapan sudah cukup. Sekarang coba jalankan salah satu command seperti `ls`.
+
+```go
+err = session.Run("ls -l ~/")
+if err != nil {
+ log.Fatal("Command execution error. " + err.Error())
+}
+```
+
+Jalankan aplikasi untuk mengetes hasilnya.
+
+
+
+## C.29.3. Penggunaan `session.StdinPipe()` untuk Run Multiple Command
+
+Ada beberapa cara yang bisa digunakan untuk menjalankan banyak command via SSH, cara paling mudah adalah dengan menggabung commands dengan operator `&&`.
+
+Alternatif metode lainnya, bisa dengan memanfaatkan `StdinPipe` milik session. Cukup tulis command yang diinginkan ke objek stdin pipe tersebut.
+
+> Method `.StdinPipe()` mengembalikan objek stdin pipe yang tipenya adalah `io.WriteCloser`. Tipe ini merupakan gabungan dari `io.Writer`, `io.Reader`, dan `io.Closer`.
+
+Mari kita praktekan, salin kode sebelumnya ke file baru, hapus semua baris kode setelah statement pembuatan session.
+
+Siapkan variabel untuk menampung objek default stdin pipe milik session.
+
+```go
+var stdout, stderr bytes.Buffer
+session.Stdout = &stdout
+session.Stderr = &stderr
+
+stdin, err := session.StdinPipe()
+if err != nil {
+ log.Fatal("Error getting stdin pipe. " + err.Error())
+}
+```
+
+Jalankan command prompt di remote host, menggunakan perintah `.Start()`. Pilih unix shell yang diinginkan dengan menuliskannya sebagai argument pemanggilan method `.Start()`. Pada tutorial ini kita menggunakan unix shell `bash`.
+
+```go
+err = session.Start("/bin/bash")
+if err != nil {
+ log.Fatal("Error starting bash. " + err.Error())
+}
+```
+
+Method `.Start()` dan `.Run()` memiliki kesamaan dan perbedaan. Salah satu kesamaannya adalah kedua method tersebut digunakan untuk menjalankan perintah shell, yang eksekusinya akan selesai ketika perintah yang dijalankan selesai, contoh: `.Start("ls -l ~/")` dan `.Run("ls -l ~/")`, kedua statement tersebut menghasilkan proses dan output yang sama.
+
+Sedangkan perbedaannya, method `.Start()` bisa digunakan untuk memilih command line interpreter atau shell (pada contoh ini `bash`), lalu memanfaatkannya untuk eksekusi banyak shell command dengan cara dilewatkan ke stdin pipe.
+
+> Pemanggilan method `.Start()` dan `.Run()` hanya bisa dilakukan sekali untuk tiap session.
+
+Selanjutnya, siapkan commands dalam slice, untuk mempermudah eksekusinya. Lakukan perulangan pada slice tersebut, lalu tulis command ke dalam objek stdin pipe. Pastikan command terakhir yang dieksekusi adalah `exit` untuk mengakhiri shell.
+
+```go
+commands := []string{
+ "cd /where/is/the/path",
+ "cd src/myproject",
+ "ls",
+ "exit",
+}
+for _, cmd := range commands {
+ if _, err = fmt.Fprintln(stdin, cmd); err != nil {
+ log.Fatal(err)
+ }
+}
+```
+
+Statement `fmt.Fprintln()` digunakan untuk menuliskan sesuatu ke objek `io.Writer`. Objek stdin pipe kita sisipkan sebagai parameter pertama, lalu shell command sebagai parameter setelahnya.
+
+Selain `fmt.Fprintln()`, ada juga `fmt.Fprint()` dan `fmt.Fprintf()`.
+
+> Statement yang paling sering kita gunakan, yaitu `fmt.Print()`, isinya sebenarnya memanggil `fmt.Fprint()`, dengan parameter pertama `io.Writer` diisi dengan `os.Stdout`.
+
+Gunakan method `.Wait()` milik session untuk menunggu banyak command yang dieksekusi selesai terlebih dahulu. Kemudian tangkap output stdout dan stderr nya lalu tampilkan.
+
+```go
+err = session.Wait()
+if err != nil {
+ log.Fatal(err)
+}
+
+outputErr := stderr.String()
+fmt.Println("============== ERROR")
+fmt.Println(strings.TrimSpace(outputErr))
+
+outputString := stdout.String()
+fmt.Println("============== OUTPUT")
+fmt.Println(strings.TrimSpace(outputString))
+```
+
+Jalankan aplikasi untuk melihat hasilnya.
+
+
+
+Output dalam banyak command muncul setelah semua command berhasil dieksekusi. Statement `session.Wait()` adalah blocking.
+
+Jika ingin eksekusi command dan pengambilan outpunya tidak blocking, manfaatkan `.StdoutPipe()`, `.StderrPipe()`, dan goroutine untuk pengambilan output hasil eksekusi command.
+
+## C.29.4. Transfer File via SFTP
+
+Transfer file antara client dan server bisa dilakukan lewat protokol SSH, dengan memanfaatkan SFTP (SSH File Transfer Protocol). Penerapannya sebenarnya bisa dilakukan cukup menggunakan API yang disediakan oleh package golang.org/x/crypto/ssh, namun pada bagian ini kita akan menggunakan 3rd party library lain untuk mempermudah penerapannya. Library tersebut adalah [github.com/pkg/sftp](https://github.com/pkg/sftp).
+
+Mari kita praktekan, salin kode sebelumnya ke file baru, hapus semua baris kode setelah statement pembuatan client. Kemudian, go get package tersebut, lalu import.
+
+Buat objek sftp client, objek ini merupakan superset dari objek ssh client.
+
+
+```go
+sftpClient, err := sftp.NewClient(client)
+if err != nil {
+ log.Fatal("Failed create client sftp client. " + err.Error())
+}
+```
+
+Kita akan menggunakan sample file di lokal untuk di transfer ke server. Mekanisme-nya sama seperti pada transfer file via ftp, yaitu dengan menyiapkan file kosong di sisi server, lalu meng-copy isi file di lokal ke file di server tersebut.
+
+OK, siapkan file tujuan transfer terlebih dahulu.
+
+```go
+fDestination, err := sftpClient.Create("/data/nginx/files/test-file.txt")
+if err != nil {
+ log.Fatal("Failed to create destination file. " + err.Error())
+}
+```
+
+Lalu baca file di lokal, gunakan `io.Copy` untuk mengcopy isi file.
+
+```go
+fSource, err := os.Open("/Users/novalagung/Desktop/test-file.txt")
+if err != nil {
+ log.Fatal("Failed to read source file. " + err.Error())
+}
+
+_, err = io.Copy(fDestination, fSource)
+if err != nil {
+ log.Fatal("Failed copy source file into destination file. " + err.Error())
+}
+
+log.Println("File copied.")
+```
+
+Jalankan aplikasi untuk melihat hasilnya.
+
+
+
+---
+
+ - [sftp](github.com/pkg/sftp), by pkg team, BSD-2-Clause License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 0911a1f3b01be102b69d40fab04ec946abd1e4f9 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:38 +0700
Subject: [PATCH 109/522] New translations c-golang-sso-saml-sp.md (English)
---
en/content-en/C-golang-sso-saml-sp.md | 320 ++++++++++++++++++++++++++
1 file changed, 320 insertions(+)
create mode 100644 en/content-en/C-golang-sso-saml-sp.md
diff --git a/en/content-en/C-golang-sso-saml-sp.md b/en/content-en/C-golang-sso-saml-sp.md
new file mode 100644
index 000000000..886916789
--- /dev/null
+++ b/en/content-en/C-golang-sso-saml-sp.md
@@ -0,0 +1,320 @@
+# C.34. SSO SAML 2.0 (Service Provider)
+
+Kali ini topik yang dipilih adalah SAML SSO versi 2.0. Kita akan pelajari cara penerapan SSO di sisi penyedia servis (Service Provider), dengan memanfaatkan salah satu penyedia Identity Provider (IDP) gratisan yaitu [samltest.id](https://samltest.id).
+
+> Pada chapter ini kita fokus pada bagian Service Provider.
+
+## C.34.1. Definisi
+
+Sebelum kita masuk ke bagian tulis menulis kode, alangkah baiknya sedikit membahas tentang definisi dari SSO dan SAML itu sendiri.
+
+#### • SSO
+
+SSO atau Single Sign-On merupakan servis untuk otentikasi dan manajemen session. Dengan SSO, maka akses ke banyak aplikasi cukup bisa sekali otentikasi saja. Contoh SSO:
+
+- Seorang pengguna ingin mengupload video di Youtube. Karena belum login, maka akan di redirect ke halaman SSO login milik Google.
+- Setelah ia selesai dengan keperluannya, user ingin menge-cek email, maka dibukalah Gmail. Nah di bagian ini user tidak perlu untuk login lagi, karena Youtube dan Gmail menggunakan SSO untuk otentikasinya.
+- Tak hanya Youtube dan Gmail, hampir semua produk Google terintegrasi dengan satu SSO.
+
+> Umumnya, otentikasi pada SSO dilakukan dengan database user di (directory) server via protokol LDAP.
+
+Ada beberapa jenis penerapan SSO yang bisa dipilih, salah satunya adalah **Security Assertion Markup Language** atau **SAML** yang akan kita bahas pada chapter ini.
+
+#### • SAML
+
+SAML merupakan protokol open standard untuk otentikasi dan otorisasi antara penyedia layanan (**Service Provider**) dan penyedia identitas (**Identity Provider**). SAML berbasis *assertion* berupa XML.
+
+> Service Provider biasa disingkat dengan **SP**, sedangkan Identity Provider disingkat **IDP**.
+
+SAML adalah standar yang paling banyak digunakan dalam platform berbentuk layanan enterprise (Sass/PaaS), seperti Github, Atlassian JIRA, Sales Force menerapkan SAML pada platform mereka, lebih detailnya silakan cek https://en.wikipedia.org/wiki/SAML-based_products_and_services.
+
+Dalam SAML, ada 3 buah role penting:
+
+1. Manusia atau pengguna aplikasi (di sini kita sebut sebagai *principal*)
+2. Penyedia Layanan atau SP, contohnya seperti: Gmail, Youtube, GCP
+3. Penyedia Identitas atau IDP, contoh: Google sendiri
+
+> Penulis kurang tau apakah otentikasi pada Google menggunakan SAML atau tidak, tapi yang jelas mereka menerapkan SAML dan juga OpenID di beberapa service mereka. Di atas dicontohkan menggunakan produk Google, hanya sebagai analogi untuk mempermudah memahami SAML.
+
+## C.34.2. Cara Kerja SAML
+
+Agar lebih mudah dipahami, kita gunakan contoh. Ada dua buah website/aplikasi yang terintegrasi dengan satu buah SSO, yaitu: http://ngemail.com dan http://ndeloktipi.com. Kedua aplikasi tersebut merupakan SP atau Service Provider.
+
+Aplikasi **ngemail** dan **ndeloktipi** menggunakan satu penyedia identitas yaitu http://loginomrene.com. Jadi **loginomrene** ini merupakan IDP atau Identity Provider.
+
+#### 1. User request *target resource* ke SP
+
+Suatu ketika ada sebuah user yang ingin mengakses **ngemail** untuk mengirim sebuah email ke temannya. User tersebut sudah terdaftar sebelumnya. User langsung mengakses url berikut di browser.
+
+```
+http://ngemail.com/ngirimemailsaiki
+```
+
+Yang terjadi ketika user browser website tersebut, si SP (dalam konteks ini **ngemail**) melakukan pengecekan ijin akses (disebut security context), apakah user ini sudah login atau belum. Karena belum login maka user diarahkan ke halaman otentikasi SSO.
+
+> Target resource di sini yang dimaksud adalah http://ngemail.com/ngirimemailsaiki
+
+#### 2. SP merespon dengan URL untuk SSO login di IDP
+
+Karena user belum memiliki ijin akses, maka SP membalas request dari browser tersebut dengan balasan berupa url halaman login SSO di IDP.
+
+```
+http://loginomrene.com/SAML2/SSO/Redirect?SAMLRequest=request
+```
+
+> Isi dari query string `SAMLRequest` adalah sebuah XML `...` yang di-encode dalam base64 encoding.
+
+#### 3. Browser request laman SSO login ke IDP
+
+Setelah aplikasi menerima balasan atas request pada point 1, dilakukan redirect ke URL halaman SSO login pada point 2.
+
+IDP kemudian menerima request tersebut, lalu memproses `AuthnRequest` yang dikirim via query string untuk kemudian dilakukan security check.
+
+#### 4. IDP merespon browser dengan menampilkan halaman login
+
+Jika hasil pengecekan yang dilakukan oleh IDP adalah: user belum memiliki akses login, maka IDP merespon browser dengan menampilkan halaman login HTML.
+
+```html
+
+
+ ...
+
+
+```
+
+> Isi dari input name `SAMLResponse` adalah sebuah XML `` yang di-encode dalam base64 encoding.
+
+#### 5. Submit POST ke SP untuk keperluan asertasi (istilahnya *Assertion Consumer Service*)
+
+User kemudian melakukan login di halaman otentikasi SSO pada point 4, username password atau credentials di-isi, tombol submit di klik. Request baru di-dispatch dengan tujuan url adalah action url form tersebut. Pada point 4 bisa dilihat bahwa action url adalah berikut.
+
+```
+https://loginomrene.com/SAML2/SSO/POST
+```
+
+#### 6. SP merespon dengan redirect ke *target resource*
+
+SP menerima request tersebut, kemudian mempersiapkan ijin akses/token (yang disebut *security context*). Setelahnya SP merespon request tersebut dengan redirect ke *target resource*, pada contoh ini adalah url http://ngemail.com/ngirimemailsaiki (url point 1).
+
+#### 7. User request *target resource* ke SP
+
+Pada bagian ini user melakukan request target resource ke SP untuk kedua kalinya setelah point pertama. Bedanya pada point pertama, requet dilakukan secara eksplisit olah user/browser, sedang kali ini request merupakan hasil redirect point 6.
+
+```
+http://ngemail.com/ngirimemailsaiki
+```
+
+Perbedaan kedua adalah, kali ini user memiliki ijin akses.
+
+#### 8. SP merespon dengan balasan *target resource* yang diminta
+
+Karena user memiliki security context, maka SP merespon dengan balasan berupa target resource yang diminta, walhasil halaman http://ngemail.com/ngirimemailsaiki muncul.
+
+> Selanjutnya, setiap kali ada request target resource, maka point 7 dan 8 akan diulang.
+
+
+
+## C.34.4. SAML Metadata
+
+Dalam per-SAML-an duniawi, ada istilah *metadata*, yang memiliki peran sangat penting dalam komunikasi antar SP dan IDP dalam SAML.
+
+Metadata merupakan sebuah XML berisi informasi penting mengenai SP dan IDP, yang juga sekaligus menjadi *unique identifier* ubaik ntuk SP dan juga IDP.
+
+SP memiliki metadata, IDP juga punya. Nantinya kedua entitas ini akan bertukar metadata. Jadi SP akan memiliki metadata IDP, dan berlaku sebalikanya (IDP memiliki metadata SP).
+
+Metadata diperlukan secara *mandatory* dalam operasi dan komunikasi antar SP dan IDP. Salah satu contoh kegunaannya bisa dilihat pada proses otentikasi. Informasi yang dikirim dari SP yang jelasnya ter-enkripsi, maka untuk bisa dibaca di sisi IDP, perlu untuk di-decrypt terlebih dahulu, hal ini membuat IDP wajib untuk tau public key yang digunakan oleh SP. Nah, dengan adanya pertukaran metadata, IDP akan tau key milik SP
+
+> Metadata berisi informasi penting. Di antaranya adalah entity ID, key pair, protocol endpoints, dan lainnya.
+
+
+
+Ada dua jenis metode pertukaran metadata, **static metadata exchange** dan **dynamic metadata exchange**. Yang kita terapkan pada pembahasan ini adalah yg static.
+
+Ok, lanjut ke bagian praktek.
+
+## C.34.3. Praktek & Persiapan
+
+Sebelum masuk praktek, ada beberapa hal yang perlu penulis infokan. Yang akan kita praktekan adalah membuat entitas SP. Untuk IDP nya kita gunakan layanan IDP gratis dari samltest.id, untuk keperluan testing.
+
+Di sini kita gunakan library https://github.com/crewjam/saml untuk mempermudah proses kodingnya. Pastikan menggunakan rilis v0.4.0 atau minimal revisi #861266e.
+
+Silakan buat 1 project baru, lalu di dalamnya generate *self-signed X.509 key pair* dengan menggunakan command berikut. Key pair ini diperlukan oleh SP dalam melakukan komunikasi dengan IDP.
+
+```bash
+mkdir chapter-c34
+cd chapter-c34
+go mod init chapter-c34
+go get -u github.com/crewjam/saml@861266e
+```
+
+```bash
+openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=localhost"
+```
+
+Setelah itu siapkan `config.go` yang isinya adalah beberapa konfigurasi.
+
+```go
+var (
+ samlCertificatePath = "./myservice.cert"
+ samlPrivateKeyPath = "./myservice.key"
+ samlIDPMetadata = "https://samltest.id/saml/idp"
+
+ webserverPort = 9000
+ webserverRootURL = fmt.Sprintf("http://localhost:%d", webserverPort)
+)
+```
+
+Bisa dilihat, selain konfigurasi untuk web server, konfigurasi key pair juga ada. Dan satu lagi variabel konfigurasi `samlIDPMetadata` isinya yang mengarah ke sebuah url.
+
+Seperti yang sudah di bahas di atas, bahwa untuk meng-enable SAML, perlu ada pertukaran metadata. Aplikasi yang kita buat ini (SP) wajib tau metadata IDP. Konfigurasi `samlIDPMetadata` berisi url download metadata IDP milik samltest.id.
+
+## C.34.4. SAML Middleware
+
+Buat `saml_middleware.go`, isinya sesuai petunjuk di github.com/crewjam/saml, berisi kode untuk setup objek saml. Pada pembuatan objek saml, beberapa konfigurasi (yang sudah kita siapkan) disisipkan.
+
+```go
+package main
+
+import (
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "net/url"
+
+ "github.com/crewjam/saml/samlsp"
+)
+
+func newSamlMiddleware() (*samlsp.Middleware, error) {
+ keyPair, err := tls.LoadX509KeyPair(samlCertificatePath, samlPrivateKeyPath)
+ if err != nil { return nil, err }
+
+ keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
+ if err != nil { return nil, err }
+
+ idpMetadataURL, err := url.Parse(samlIDPMetadata)
+ if err != nil { return nil, err }
+
+ idpMetadata, err := samlsp.FetchMetadata(context.Background(), http.DefaultClient, *idpMetadataURL)
+ if err != nil { return nil, err }
+
+ rootURL, err := url.Parse(webserverRootURL)
+ if err != nil { return nil, err }
+
+ sp, err := samlsp.New(samlsp.Options{
+ URL: *rootURL,
+ Key: keyPair.PrivateKey.(*rsa.PrivateKey),
+ Certificate: keyPair.Leaf,
+ IDPMetadata: idpMetadata,
+ })
+ if err != nil { return nil, err }
+
+ return sp, nil
+}
+```
+
+## C.34.5. SAML SP App
+
+Buat objek saml middleware, lalu jadikan sebagai handler dari endpoint `/saml/`. Endpoint ini merupakan reserved endpoint untuk SAML pada aplikasi SP kita.
+
+```go
+pckage main
+
+import (
+ "github.com/crewjam/saml/samlsp"
+ // ...
+)
+
+func main() {
+ sp, err := newSamlMiddleware()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ http.Handle("/saml/", sp)
+ // ...
+}
+```
+
+Buat dua buah route handler, `/` dan `/hello`, lalu bungkus fungsi handler kedua routes dengan `sp.RequireAccount()`. Dengan ini akan membuat kedua endpoint ini hanya bisa diakses ketika user statusnya *authorized* atau sudah melakukan login.
+
+```go
+// ...
+http.Handle("/index", sp.RequireAccount(
+ http.HandlerFunc(landingHandler),
+))
+http.Handle("/hello", sp.RequireAccount(
+ http.HandlerFunc(helloHandler),
+))
+// ...
+```
+
+Kemudian buat kedua fungsi handler routes di atas.
+
+```go
+func landingHandler(w http.ResponseWriter, r *http.Request) {
+ name := samlsp.AttributeFromContext(r.Context(), "displayName")
+ w.Write([]byte(fmt.Sprintf("Welcome, %s!", name)))
+}
+func helloHandler(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Hello!"))
+}
+```
+
+Terakhir start web server, lalu jalankan aplikasi.
+
+```go
+portString := fmt.Sprintf(":%d", webserverPort)
+fmt.Println("server started at", portString)
+http.ListenAndServe(portString, nil)
+```
+
+```bash
+go run *.go
+```
+
+Oops, muncul error pada saat mengakses `http://localhost:9000/index`. Meski url ini merupakan protected url, yang di mana hanya bisa diakses ketika sudah login, harusnya user akan di-redirect ke halaman login, bukan malah memunculkan error.
+
+
+
+Jika dilihat baik-baik, errornya cukup jelas, bahwa ini terjadi karena kita belum memberikan metadata SP aplikasi kita ke IDP. Ingat bahwa pertukaran metadata adalah wajib, SP sudah tau metadata IDP, tapi IDP belum tau metadata SP.
+
+Ok, sekarang kita perlu upload metadata SP ke samltest.id. Metadata SP bisa di-unduh lewat endpoint `/saml/metadata` (pada konteks ini url menjadi http://localhost:9000/saml/metadata. URL upload metadata samltest adalah https://samltest.id/upload.php.
+
+
+
+## C.34.6. Test SAML SP
+
+Test dengan membuka endpoint `/index` pada browser. Idealnya kita akan diarahkan ke URL SAML login. Gunakan sandbox account yang ada di halaman itu untuk login.
+
+
+
+Setelah login sukses, halaman yang diinginkan muncul dengan menampilkan pesan `Welcome, Nama User!`.
+
+Informasi nama user tersebut diambil dari objek context route handler, yg informasinya disisipkan oleh saml middleware. Contoh pengambilan informasi user bisa dilihat dalam handler `indexHandler`:
+
+```go
+name := samlsp.AttributeFromContext(r.Context(), "displayName")
+```
+
+## C.34.7. SSO Multiple SP Apps
+
+Ada dua pilihan metode inisialisasi SSO pada SAML: *SP-initiated SSO* dan *IDP-initiated SSO*.
+
+Pada contoh yg kita terapkan, *SP-initiated SSO* dipergunakan. Setiap user request akan melewati proses otentikasi dahulu ke IDP sebelum akhirnya diperbolehkan mengakses SP.
+
+Salah satu benefit metode inisialisasi ini: ketika ada banyak aplikasi SP (misal ada 3 aplikasi) yang seluruhnya berkomunikasi dengan satu IDP yang sama (misal samltest.id), maka Single Sign-on akan ter-enable *seamlessly*. Ketika kita login di salah satu aplikasi SP, maka pada aplikasi SP lainnya tidak perlu login lagi.
+
+---
+
+ - [crewjam/saml](https://github.com/crewjam/saml), by Ross Kinder, BSD-2-Clause License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 471f462a5e3f4f9f554b5bf9fc154653dff838d5 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:38 +0700
Subject: [PATCH 110/522] New translations c-golang-web-framework.md (English)
---
en/content-en/C-golang-web-framework.md | 66 +++++++++++++++++++++++++
1 file changed, 66 insertions(+)
create mode 100644 en/content-en/C-golang-web-framework.md
diff --git a/en/content-en/C-golang-web-framework.md b/en/content-en/C-golang-web-framework.md
new file mode 100644
index 000000000..48206b450
--- /dev/null
+++ b/en/content-en/C-golang-web-framework.md
@@ -0,0 +1,66 @@
+# C.2. Go Web Framework
+
+Salah satu kelebihan bahasa Go adalah dukungan dari komunitas. Banyak sekali library dan framework untuk bahasa ini yang kesiapannya production-ready dan gratis untuk dipergunakan.
+
+Di Go, sama seperti bahasa pemrograman lainnya, ada banyak library dan framework yang siap pakai. Ada framework yang sifatnya sudah komplit, lengkap isinya dari ujung ke ujung, mulai dari setup project hingga testing dan build/deployment sudah ada semua tooling-nya. Ada juga framework yg scope-nya lebih spesifik (biasa disebut library), seperti lib untuk mempermudah operasi di data layer, lib untuk routing, dan lainnya.
+
+Pada chapter ini kita tidak akan membahas satu persatu mengenai library/framework yang ada. Penulis hanya akan menuliskan yang pernah penulis pakai saja.
+
+## C.2.1. Web Framework
+
+Untuk opsi web framewok, ada cukup banyak pilihan. Author sendiri pernah menggunakan 3 pilihan berikut:
+
+- [Beego](https://github.com/astaxie/beego)
+- [Echo](https://github.com/labstack/echo)
+- [Gin](https://github.com/gin-gonic/gin)
+- custom, menggunakan kombinasi dari banyak library sesuai kebutuhan dan selera.
+- atau bisa menggunakan pilihan alternatif web framework lainnya.
+- atau bisa juga menggunakan salah satu web framework dan di-combine dengan library lain.
+
+Untuk opsi custom framework sendiri, pembaca bisa menggunakan kombinasi dari beberapa library berikut:
+
+## C.2.2. Routing Library
+
+Untuk opsi router, ada cukup banyak pilihan yg tersedia, sebagian di antaranya:
+
+- [Chi](https://github.com/go-chi/chi)
+- [FastHttp](https://github.com/valyala/fasthttp) atau [FastHttpRouter](https://github.com/buaazp/fasthttprouter)
+- [Gorilla Mux](https://github.com/gorilla/mux)
+- dan lainnya
+
+## C.2.3. HTTP Middlewares
+
+Untuk middlewares biasanya include sebagai dependensi router library. Tapi ada juga middleware independen. Contohnya:
+
+- [CORS](https://github.com/rs/cors)
+- [JWT](https://github.com/golang-jwt/jwt)
+- [Rate Limiter](https://github.com/ulule/limiter)
+- [Secure](https://github.com/unrolled/secure)
+- dan lainnya
+
+## C.2.4. Form & Validator
+
+Validator library berfungsi untuk mempermudah parsing payload dan parameter dari objek http request. Rekomendasi validator library salah satunya:
+
+- [Validator by go-playground](https://github.com/go-playground/validator/tree/v9)
+- dan lainnya
+
+## C.2.5. Database / ORM
+
+ORM adalah salah satu pattern yg cukup sering dipakai di data layer. Beberapa library yang tersedia di antaranya:
+
+- [Gorm](https://github.com/jinzhu/gorm)
+- [Gorp](https://github.com/go-gorp/gorp)
+- dan lainnya
+
+---
+
+Silakan mencoba-coba dan memilih kombinasi library yang cocok sesuai kebutuhan dan keinginan kawan-kawan. Semua opsi ada kelebihan dan kekurangannya. Begitu juga pada implementasi library, akan sangat mempengaruhi hasil.
+
+Saya sangat menganjurkan pembaca untuk mencoba banyak library dan framework.
+
+Ok, saya rasa cukup untuk pembahasan kali ini. Semoga bermanfaat
+
+---
+
+
From c7fb4fe1d0174a0bc9cd228f30d2b0fc5598c6c8 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:39 +0700
Subject: [PATCH 111/522] New translations c-http-error-handling.md (English)
---
en/content-en/C-http-error-handling.md | 114 +++++++++++++++++++++++++
1 file changed, 114 insertions(+)
create mode 100644 en/content-en/C-http-error-handling.md
diff --git a/en/content-en/C-http-error-handling.md b/en/content-en/C-http-error-handling.md
new file mode 100644
index 000000000..c548b3298
--- /dev/null
+++ b/en/content-en/C-http-error-handling.md
@@ -0,0 +1,114 @@
+# C.6. HTTP Error Handling (Validator v9, Echo)
+
+Pada chapter ini kita akan belajar cara membuat custom error handler yang lebih readable, sangat cocok untuk web service. Bahan dasar yang kita manfaatkan adalah source code pada chapter sebelum ini, lalu dimodifikasi. Jadi silakan salin project pada chapter sebelumnya sebagai project folder baru.
+
+## C.6.1. Error Handler
+
+Cara meng-custom default error handler milik echo, adalah dengan meng-override property `e.HTTPErrorHandler`. Langsung saja override property tersebut dengan callback berisi parameter objek error dan context. Gunakan callback tersebut untuk bisa menampilkan error yg lebih detail.
+
+```go
+e.HTTPErrorHandler = func(err error, c echo.Context) {
+ report, ok := err.(*echo.HTTPError)
+ if !ok {
+ report = echo.NewHTTPError(http.StatusInternalServerError, err.Error())
+ }
+
+ c.Logger().Error(report)
+ c.JSON(report.Code, report)
+}
+```
+
+Pada kode di atas, objek `report` menampung objek error setelah di casting ke tipe `echo.HTTPError`. Error tipe ini adalah error-error yang berhubungan dengan http, yang di-handle oleh echo. Untuk error yang bukan dari echo, tipe nya adalah `error` biasa. Pada kode di atas kita standarkan, semua jenis error harus berbentuk `echo.HTTPError`.
+
+Selanjutnya objek error tersebut kita tampilkan ke console dan juga ke browser dalam bentuk JSON.
+
+OK, jalankan aplikasi, lalu test hasilnya.
+
+
+
+
+
+## C.6.2. Human-Readable Error
+
+Error yang dikembalikan sudah bisa lebih detail dibanding sebelumnya, tapi masih kurang, karena masih susah untuk dipahami. Lakukan modifikasi pada callback custom error handler menjadi seperti pada kode berikut.
+
+```go
+e.HTTPErrorHandler = func(err error, c echo.Context) {
+ report, ok := err.(*echo.HTTPError)
+ if !ok {
+ report = echo.NewHTTPError(http.StatusInternalServerError, err.Error())
+ }
+
+ if castedObject, ok := err.(validator.ValidationErrors); ok {
+ for _, err := range castedObject {
+ switch err.Tag() {
+ case "required":
+ report.Message = fmt.Sprintf("%s is required",
+ err.Field())
+ case "email":
+ report.Message = fmt.Sprintf("%s is not valid email",
+ err.Field())
+ case "gte":
+ report.Message = fmt.Sprintf("%s value must be greater than %s",
+ err.Field(), err.Param())
+ case "lte":
+ report.Message = fmt.Sprintf("%s value must be lower than %s",
+ err.Field(), err.Param())
+ }
+
+ break
+ }
+ }
+
+ c.Logger().Error(report)
+ c.JSON(report.Code, report)
+}
+```
+
+Error yang dikembalikan oleh *validator.v9* bertipe `validator.ValidationErrors`. Pada kode di atas bisa kita lihat ada pengecekan apakah error tersebut adalah dari library *validator.v9* atau bukan; jika memang iya, maka `report.Message` diubah isinya dengan kata-kata yang lebih mudah dipahami.
+
+Tipe `validator.ValidationErrors` sendiri sebenarnya merupakan slice `[]validator.FieldError`. Objek tersebut di-loop, lalu diambil-lah elemen pertama sebagai nilai bailk error.
+
+OK, jalankan ulang aplikasi, lalu test.
+
+
+
+# C.6.3. Custom Error Page
+
+Untuk aplikasi non-web-service, akan lebih baik jika setiap terjadi error dimunculkan error page, atau halaman khusus yang menampilkan informasi error.
+
+Di echo sangatlah mudah untuk membuat halaman custom error. Contoh implementasinya seperti kode di bawah ini.
+
+```go
+e.HTTPErrorHandler = func(err error, c echo.Context) {
+ report, ok := err.(*echo.HTTPError)
+ if !ok {
+ report = echo.NewHTTPError(http.StatusInternalServerError, err.Error())
+ }
+
+ errPage := fmt.Sprintf("%d.html", report.Code)
+ if err := c.File(errPage); err != nil {
+ c.HTML(report.Code, "Errrrooooorrrrr")
+ }
+}
+```
+
+Setiap kali error terjadi, maka halaman dengan nama adalah `.html` dicari untuk kemudian ditampilkan. Misalkan errornya adalah 500 Internal Server Error, maka halaman `500.html` muncul; error 404 Page Not Found, maka halaman `404.html` muncul.
+
+Silakan ubah kode `fmt.Sprintf("%d.html", report.Code)` sesuai format nama halaman error yang digunakan.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [Validator v9](https://github.com/go-playground/validator/tree/v9), by Dean Karn (Go Playground), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 744a7ef74174655214e944add2c7886f487e8974 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:40 +0700
Subject: [PATCH 112/522] New translations c-http-gzip-compression.md (English)
---
en/content-en/C-http-gzip-compression.md | 150 +++++++++++++++++++++++
1 file changed, 150 insertions(+)
create mode 100644 en/content-en/C-http-gzip-compression.md
diff --git a/en/content-en/C-http-gzip-compression.md b/en/content-en/C-http-gzip-compression.md
new file mode 100644
index 000000000..a34804b99
--- /dev/null
+++ b/en/content-en/C-http-gzip-compression.md
@@ -0,0 +1,150 @@
+# C.17. HTTP Gzip Compression (gziphandler)
+
+Pada chapter ini kita akan mempelajari penerapan HTTP Compression, dengan encoding adalah Gzip, dalam aplikasi web golang.
+
+## C.17.1. Teori
+
+HTTP Compression adalah teknik kompresi data pada HTTP response, agar ukuran/size output menjadi lebih kecil dan response time lebih cepat.
+
+Pada saat sebuah endpoint diakses, di header request akan ada header `Accept-Encoding` yang disisipkan oleh browser secara otomatis.
+
+```http
+GET /hello HTTP/1.1
+Host: localhost:9000
+Accept-Encoding: gzip, deflate
+```
+
+Jika isinya adalah `gzip` atau `deflate`, berarti browser siap dan support untuk menerima response yang di-compress dari back end.
+
+> Deflate adalah algoritma kompresi untuk data lossless. Gzip adalah salah satu teknik kompresi data yang menerapkan algoritma deflate.
+
+Di sisi back end sendiri, jika memang output di-compress, maka response header `Content-Encoding: gzip` perlu disisipkan.
+
+```http
+Content-Encoding: gzip
+```
+
+Jika di sebuah request tidak ada header `Accept-Encoding: gzip`, tetapi response back end tetap di-compress, maka akan muncul error di browser `ERR_CONTENT_DECODING_FAILED`.
+
+## C.17.2. Praktek
+
+Golang menyediakan package `compress/gzip`. Dengan memanfaatkan API yang tersedia dalam package tersebut, kompresi data pada HTTP response bisa dilakukan.
+
+Namun pada chapter ini kita tidak memakainya, melainkan menggunakan salah satu library middleware gzip compression yang cukup terkenal, [gziphandler](https://github.com/NYTimes/gziphandler).
+
+Mari kita praktekan. Siapkan folder project baru, siapkan satu buah rute `/image`. Dalam handler rute tersebut terdapat proses pembacaan isi file gambar `sample.png`, untuk kemudian dijadikan sebagai output data response. Gunakan file gambar apa saja untuk keperluan testing.
+
+Tujuan dari aplikasi ini untuk melihat seberapa besar response size dan lama response time-nya. Nantinya akan kita bandingkan dengan hasil test di aplikasi yang menerapkan http gzip comporession.
+
+```go
+package main
+
+import (
+ "io"
+ "net/http"
+ "os"
+)
+
+func main() {
+ mux := new(http.ServeMux)
+
+ mux.HandleFunc("/image", func(w http.ResponseWriter, r *http.Request) {
+ f, err := os.Open("sample.png")
+ if f != nil {
+ defer f.Close()
+ }
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ _, err = io.Copy(w, f)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ })
+
+ server := new(http.Server)
+ server.Addr = ":9000"
+ server.Handler = mux
+
+ server.ListenAndServe()
+}
+```
+
+Jalankan aplikasi lalu test hasilnya.
+
+
+
+Image size adalah 137 KB, dan response time adalah 18 ms.
+
+Selanjutnya kita akan coba menerapkan middleware gzip pada program kecil di atas. Pertama unduh dependency gziphandler terlebih dahulu menggunakan `go get`.
+
+```bash
+go get -u github.com/NYTimes/gziphandler
+```
+
+Import library yang sudah ter-unduh pada file main, lalu bungkus multiplexer `mux` menggunakan `gziphandler.GzipHandler()`, mux tersebut dimasukan ke property `server.Handler`.
+
+```go
+import (
+ // ...
+ "github.com/NYTimes/gziphandler"
+)
+
+func main() {
+ // ...
+
+ server.Handler = gziphandler.GzipHandler(mux)
+
+ // ...
+}
+```
+
+Jalankan ulang aplikasi, lihat perbandingannya.
+
+
+
+Perbedannya size dan time nya mungkin tidak terlihat signifikan, karena memang gambarnya berukuran kecil, jumlahnya cuma satu asset, dan pengaksesannya di localhost. Untuk aplikasi yang sudah published di internet, dan diakses dari komputer lokal, pasti akan terasa jauh lebih cepat dan ringan.
+
+## C.17.3. Gzip Compression di Echo
+
+Penerapan http gzip compression di echo framework bisa dengan menggunakan middleware gziphandler di atas. Atau bisa juga menggunakan middleware gzip milik echo. Berikut merupakan contoh pemanfaatan echo middleware gzip.
+
+```go
+e := echo.New()
+
+e.Use(middleware.Gzip())
+
+e.GET("/image", func(c echo.Context) error {
+ f, err := os.Open("sample.png")
+ if err != nil {
+ return err
+ }
+
+ _, err = io.Copy(c.Response(), f)
+ if err != nil {
+ return err
+ }
+
+ return nil
+})
+
+e.Logger.Fatal(e.Start(":9000"))
+```
+
+---
+
+ - [Gzip Handler](https://github.com/NYTimes/gziphandler), by The New York Times team, Apache-2.0 license
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From a6369362ed02baec301db2a89dd34451457035fc Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:41 +0700
Subject: [PATCH 113/522] New translations c-http-request-payload-validation.md
(English)
---
.../C-http-request-payload-validation.md | 118 ++++++++++++++++++
1 file changed, 118 insertions(+)
create mode 100644 en/content-en/C-http-request-payload-validation.md
diff --git a/en/content-en/C-http-request-payload-validation.md b/en/content-en/C-http-request-payload-validation.md
new file mode 100644
index 000000000..c933d1800
--- /dev/null
+++ b/en/content-en/C-http-request-payload-validation.md
@@ -0,0 +1,118 @@
+# C.5. HTTP Request Payload Validation (Validator v9, Echo)
+
+Pada chapter ini kita akan belajar cara validasi payload request di sisi back end. Library yang kita gunakan adalah [github.com/go-playground/validator/v10](https://github.com/go-playground/validator), library ini sangat berguna untuk keperluan validasi data.
+
+## C.5.1. Payload Validation
+
+Penggunaan validator cukup mudah, di struct penampung payload, tambahkan tag baru pada masing-masing property dengan skema `validate:""`.
+
+Langsung saja kita praktekan, buat folder project baru dengan isi file `main.go`, lalu tulis kode berikut ke dalamnya.
+
+```go
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/go-playground/validator/v10"
+ "net/http"
+)
+
+type User struct {
+ Name string `json:"name" validate:"required"`
+ Email string `json:"email" validate:"required,email"`
+ Age int `json:"age" validate:"gte=0,lte=80"`
+}
+```
+
+Struct `User` memiliki 3 field, berisikan aturan/rule validasi, yang berbeda satu sama lain (bisa dilihat pada tag `validate`). Kita bahas validasi per-field agar lebih mudah untuk dipahami.
+
+ - Field `Name`, tidak boleh kosong.
+ - Field `Email`, tidak boleh kosong, dan isinya harus dalam format email.
+ - Field `Age`, tidak harus di-isi; namun jika ada isinya, maka harus berupa numerik dalam kisaran angka 0 hingga 80.
+
+Kurang lebih berikut adalah penjelasan singkat mengenai beberapa rule yang kita gunakan di atas.
+
+ - Rule `required`, menandakan bahwa field harus di isi.
+ - Rule `email`, menandakan bahwa value pada field harus dalam bentuk email.
+ - Rule `gte=n`, artinya isi harus numerik dan harus di atas `n` atau sama dengan `n`.
+ - Rule `lte=n`, berarti isi juga harus numerik, dengan nilai di bawah `n` atau sama dengan `n`.
+
+Jika sebuah field membutuhkan dua atau lebih rule, maka tulis seluruhnya dengan delimiter tanda koma (`,`).
+
+OK, selanjutnya buat struct baru `CustomValidator` dengan isi sebuah property bertipe `*validator.Validate` dan satu buah method ber-skema `Validate(interface{})error`. Objek cetakan struct ini akan kita gunakan sebagai pengganti default validator milik echo.
+
+```go
+type CustomValidator struct {
+ validator *validator.Validate
+}
+
+func (cv *CustomValidator) Validate(i interface{}) error {
+ return cv.validator.Struct(i)
+}
+
+func main() {
+ e := echo.New()
+ e.Validator = &CustomValidator{validator: validator.New()}
+
+ // routes here
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Method `.Struct()` milik `*validator.Validate`, digunakan untuk mem-validasi data objek dari struct.
+
+> Library validator menyediakan banyak sekali cakupan data yang bisa divalidasi, tidak hanya struct, lebih jelasnya silakan lihat di laman github https://github.com/go-playground/validator.
+
+Siapkan sebuah endpoint untuk keperluan testing. Dalam endpoint ini method `Validate` milik `CustomValidator` dipanggil.
+
+```go
+e.POST("/users", func(c echo.Context) error {
+ u := new(User)
+ if err := c.Bind(u); err != nil {
+ return err
+ }
+ if err := c.Validate(u); err != nil {
+ return err
+ }
+
+ return c.JSON(http.StatusOK, true)
+})
+```
+
+OK, jalankan aplikasi, lakukan testing.
+
+
+
+Bisa dilihat pada gambar di atas, ada beberapa request yang mengembalikan error.
+
+ - Request pertama adalah valid.
+ - Request ke-2 error karena value dari field `email` tidak valid. Harusnya berisi value dalam format email.
+ - Request ke-3 error karena value field `age` lebih dari 80. Value seharusnya numerik kisaran 0 hingga 80.
+ - Sedangkan request ke-4 sukses meskipun `age` adalah `null`, hal ini karena rule untuk field tersebut tidak ada `required`.
+
+> Field `Age` tidak harus di-isi; namun jika ada isinya, maka harus berupa numerik dalam kisaran angka 0 hingga 80.
+
+Dari testing di atas bisa kita simpulkan bahwa fungsi validasi berjalan sesuai harapan. Namun masih ada yang kurang, ketika ada yang tidak valid, error yang dikembalikan selalu sama, yaitu message `Internal server error`.
+
+Sebenarnya error 500 ini sudah sesuai jika muncul pada page yang sifatnya menampilkan konten. Pengguna tidak perlu tau secara mendetail mengenai detail error yang sedang terjadi. Mungkin dibuat saja halaman custom error agar lebih menarik.
+
+Tapi untuk web service (RESTful API?), akan lebih baik jika errornya detail (terutama pada fase development), agar aplikasi consumer bisa lebih bagus dalam meng-handle error tersebut.
+
+Nah, pada chapter selanjutnya kita akan belajar cara membuat custom error handler untuk meningkatkan kualitas error reporting.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [Validator v9](https://github.com/go-playground/validator/tree/v9), by Dean Karn (Go Playground), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From b3b95d6f7b1bd5046c98eff5f26a88ee4898954f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:42 +0700
Subject: [PATCH 114/522] New translations c-http2-server-push.md (English)
---
en/content-en/C-http2-server-push.md | 171 +++++++++++++++++++++++++++
1 file changed, 171 insertions(+)
create mode 100644 en/content-en/C-http2-server-push.md
diff --git a/en/content-en/C-http2-server-push.md b/en/content-en/C-http2-server-push.md
new file mode 100644
index 000000000..a2f980d4e
--- /dev/null
+++ b/en/content-en/C-http2-server-push.md
@@ -0,0 +1,171 @@
+# C.25. HTTP/2 dan HTTP/2 Server Push
+
+HTTP/2 adalah versi terbaru protokol HTTP, dikembangkan dari protokol [SPDY](https://tools.ietf.org/html/draft-mbelshe-httpbis-spdy-00) yang diinisiasi oleh Google.
+
+Protokol ini sekarang sudah kompatibel dengan banyak browser di antaranya: Chrome, Opera, Firefox 9, IE 11, Safari, Silk, dan Edge.
+
+Kelebihan HTTP/2 dibanding HTTP 1.1 (protokol yang umumnya digunakan) sebagian besar adalah pada performa dan sekuriti. Berikut merupakan beberapa point yang menjadi kelebihan dari protokol baru ini.
+
+ - Backward compatible dengan HTTP 1.1
+ - Kompresi data pada HTTP Headers
+ - Multiplexing banyak request (dalam satu koneksi TCP)
+ - HTTP/2 Server Push
+
+Pada chapter ini kita akan belajar cara menerapkan HTTP/2 dan salah satu fitur milik protokol ini yaitu HTTP/2 Server Push.
+
+> Mengenai multiplexing banyak request tidak akan kita bahas pada buku ini, silakan coba pelajari sendiri jika tertarik, menggunakan library cmux.
+
+## C.25.1. HTTP/2 di Golang
+
+Golang memiliki dukungan sangat baik terhadap HTTP/2. Dengan cukup meng-enable fasilitas TLS/HTTPS maka aplikasi golang secara otomatis menggunakan HTTP/2.
+
+Untuk memastikan mari kita langsung praktekkan, coba duplikat project pada chapter sebelumnya (**A.23. HTTPS/TLS Web Server**) sebagai project baru, jalankan aplikasinya lalu cek di browser chrome. Gunakan chrome extension [HTTP/2 and SPDY indicator](https://chrome.google.com/webstore/detail/http2-and-spdy-indicator/mpbpobfflnpcgagjijhmgnchggcjblin?hl=en) untuk menge-test apakah HTTP/2 sudah enabled.
+
+
+
+Perlu diketahui untuk golang versi sebelum **1.6** ke bawah, secara default HTTP/2 tidak akan di-enable. Perlu memanggil fungsi `http2.ConfigureServer()` secara eksplist untuk meng-enable HTTP/2. Fungsi tersebut tersedia dalam package `golang.org/x/net/http2`. Lebih jelasnya silakan baca [laman dokumentasi](https://godoc.org/golang.org/x/net/http2).
+
+## C.25.2. HTTP/2 Server Push
+
+HTTP/2 Server Push adalah salah satu fitur pada HTTP/2, berguna untuk mempercepat response dari request, dengan cara data yang akan di-response dikirim terlebih dahulu oleh server.
+
+Fitur server push ini cocok digunakan untuk push data assets, seperti: css, gambar, js, dan file assets lainnya.
+
+Lalu apakah server push ini bisa dimanfaatkan untuk push data JSON, XML, atau sejenisnya? Sebenarnya bisa, hanya saja ini akan menyalahi tujuan dari penciptaan server push sendiri dan hasilnya tidak akan optimal, karena sebenernya server push tidak murni bidirectional, masih perlu adanya request ke server untuk mendapatkan data yg sudah di push oleh server itu sendiri.
+
+> HTTP/2 server push bukanlah pengganti dari websocket. Gunakan websocket untuk melakukan komunikasi bidirectional antara server dan client.
+
+Untuk mengecek suport-tidak-nya server push, lakukan casting pada objek `http.ResponseWriter` milik handler ke interface `http.Pusher`, lalu manfaatkan method `Push()` milik interface ini untuk push data dari server.
+
+> Fasilitas server push ini hanya bisa digunakan pada golang versi 1.8 ke-atas.
+
+## C.25.3. Praktek
+
+Mari kita praktekan. Buat project baru, buat file `main.go`, isi dengan kode berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func main() {
+ http.Handle("/static/",
+ http.StripPrefix("/static/",
+ http.FileServer(http.Dir("."))))
+
+ log.Println("Server started at :9000")
+ err := http.ListenAndServeTLS(":9000", "server.crt", "server.key", nil)
+ if err != nil {
+ panic(err)
+ }
+}
+```
+
+Dalam folder proyek baru di atas, siapkan juga beberapa file lain:
+
+ - File `app.js`
+ - File `app.css`
+ - File `server.crt`, salin dari proyek sebelumnya
+ - File `server.key`, salin dari proyek sebelumnya
+
+Selanjutnya siapkan string html, nantinya akan dijadikan sebagai output rute `/`.
+
+```go
+const indexHTML = `
+
+
+
+ Hello World
+
+
+
+
+ Hello, gopher!
+
+
+
+`
+```
+
+Siapkan rute `/`, render string html tadi sebagai output dari endpoint ini.
+
+```go
+http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.NotFound(w, r)
+ return
+ }
+
+ if pusher, ok := w.(http.Pusher); ok {
+ if err := pusher.Push("/static/app.js", nil); err != nil {
+ log.Printf("Failed to push: %v", err)
+ }
+
+ if err := pusher.Push("/static/app.css", nil); err != nil {
+ log.Printf("Failed to push: %v", err)
+ }
+ }
+
+ fmt.Fprintf(w, indexHTML)
+})
+```
+
+Pada handler di atas bisa kita lihat bahwa selain me-render string html, rute ini juga memiliki tugas lain yaitu push assets.
+
+Cara untuk mendeteksi apakah server push di-support, adalah dengan meng-casting objek `http.ResponseWriter` ke tipe `http.Pusher`. Proses casting tersebut mengembalikan dua buah data.
+
+ 1. Data objek yang sudah di casting.
+ 2. Variabel bertipe `bool` penanda aplikasi kita mendukung mendukung server push atau tidak.
+
+Jika nilai variabel `ok` adalah `true`, maka server push di-support.
+
+Method `Push()` milik `http.Pusher` digunakan untuk untuk push data. Endpoint yang disisipkan sebagai argumen, datanya akan di push ke front end oleh server, dalam contoh di atas adalah `/static/app.js` dan `/static/app.css`.
+
+Server push ini adalah per endpoint atau rute. Jika ada rute lain, maka dua assets di atas tidak akan di push, kecuali method `Push()` dipanggil lagi dalam rute lain tersebut.
+
+> Daripada memanggil method push satu-per-satu pada banyak handler, lebih baik jadikan sebagai middleware terpisah.
+
+Kegunaan dari fungsi `fmt.Fprintf()` adalah untuk render html, sama seperti `w.Write()`.
+
+OK, jalankan aplikasi lalu test.
+
+## C.25.4. Testing
+
+Perbedaan antara aplikasi yang menerapkan HTTP/2 dan tidak, atau yang menerapkan server push atau tidak; adalah tidak terasa bedanya jika hanya di-test lewat lokal saja.
+
+Untuk mengecek HTTP/2 diterapkan atau tidak, kita bisa gunakan Chrome extension **HTTP/2 and SPDY indicator**.
+
+Untuk mengecek server push pada tiap request sebenernya bisa hanya cukup menggunakan chrome dev tools, namun fitur ini hanya tersedia pada [Chrome Canary](https://www.google.com/chrome/browser/canary.html). Download browser tersebut lalu install, gunakan untuk testing aplikasi kita.
+
+Pada saat mengakses `https://localhost:9000` pastikan developer tools sudah aktif (klik kanan, inspect element), lalu buka tab **Network**.
+
+
+
+
+Untuk endpoint yang menggunakan server push, pada kolom **Protocol** nilainya adalah **spdy**. Pada screenshot di atas terlihat bahwa assets `app.js` dan `app.css` dikirim lewat server push.
+
+> Jika kolom Protocol tidak muncul, klik kanan pada kolom, lalu centang **Protocol**.
+
+Berikut merupakan variasi nilai pada kolom protocol.
+
+ - **http/1.1**, protokol yang kita gunakan mulai tahun 1999.
+ - **spdy**, versi pertama spesifikasi HTTP/2 dari google, mengawali terciptanya HTTP/2.
+ - **h2**, kependekan dari "HTTP 2".
+ - **h2c**, kependekan dari "HTTP 2 Cleartext". HTTP/2 lewat kanal yang tidak ter-enkripsi.
+
+Selain dari kolom protocol, penanda server push bisa dilihat juga lewat grafik **Waterfall**. Garis warna ungu muda pada grafik tersebut adalah start time. Untuk endpoint yang di push maka bar chart akan muncul sebelum garis ungu atau start time.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From b71d01bfe38ecef6b05a627344d75cc4026dde5c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:42 +0700
Subject: [PATCH 115/522] New translations c-https-tls.md (English)
---
en/content-en/C-https-tls.md | 140 +++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
create mode 100644 en/content-en/C-https-tls.md
diff --git a/en/content-en/C-https-tls.md b/en/content-en/C-https-tls.md
new file mode 100644
index 000000000..88a5207d2
--- /dev/null
+++ b/en/content-en/C-https-tls.md
@@ -0,0 +1,140 @@
+# C.24. HTTPS/TLS Web Server
+
+Pada bagian ini kita akan belajar cara meng-enable fasilitas SSL/TLS pada web server.
+
+## C.24.1. Definisi
+
+#### • SSL
+
+**SSL, Secure Sockets Layer**, adalah standar untuk pengamanan komunikasi lewat internet. Data atau informasi yang sedang dikomunikasikan dari sebuah system ke system lain akan di-proteksi, dengan cara adalah mengacak informasi tersebut menggunakan algoritma enkripsi.
+
+#### • SSL Certificates
+
+**SSL Certificate**, adalah sebuah file berisikan informasi mengenai website, yang nantinya dibutuhkan untuk enkripsi data. SSL Certificate berisi **Public Key**. Public key digunakan untuk meng-enkripsi data yang akan di transfer.
+
+Certificate ditandatangi secara digital oleh **Certificate Authorities (CA)**. Digital Signature atau tanda tangan digital merupakan sebuah kode unik yang di-generate dengan teknologi cryptography **(Public Key Infrastructure)**.
+
+Certificate Authorities sendiri merupakan entitas atau institusi legal yang mengeluarkan dan mem-verifikasi sertifikat digital.
+
+Ketika seorang pengguna internet surfing, mengakses sebuah website yang website tersebut menerapkan SSL, informasi yang dikirim akan di encrypt dengan aman (menggunakan public key) dan hanya bisa di-decrypt menggunakan **Private Key**.
+
+Private Key, atau Secret Key, adalah file terpisah yang diperlukan pada proses dekripsi data yang di-encrypt menggunakan public key.
+
+Berikut merupakan penjelasan dalam bentuk gambar yang diambil dari [coinjolt.com](https://coinjolt.com/what-is-a-public-and-private-key/).
+
+
+
+Kedua file certificate dan file private key harus disimpan dengan sangat super aman di server.
+
+#### • TLS
+
+**TLS, Transport Layer Security**, adalah versi yang lebih update dari SSL.
+
+#### • HTTPS
+
+**HTTPS, Hyper Text Transfer Protocol Secure**, adalah ekstensi dari HTTP yang berguna untuk pengamanan komunikasi lewat internet. Data atau informasi yang dikomunikasikan di-enkripsi menggunakan **TLS**.
+
+## C.24.2. Generate Private Key & Public Key Menggunakan `openssl`
+
+Untuk menerapkan TLS pada web server aplikasi golang, private key dan public key perlu kita siapkan terlebih dahulu.
+
+Gunakan `openssl` untuk generate private key.
+
+```bash
+$ openssl genrsa -out server.key 2048
+$ openssl ecparam -genkey -name secp384r1 -out server.key
+```
+
+Dua command di atas menghasilkan `server.key` yang merupakan private key. Setelah itu, generate *self-signed* certificate (yang berisikan public key) dari private key yang telah dibuat.
+
+```bash
+$ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
+```
+
+Command untuk generate certificate di atas akan memunculkan form. Informasi seperti alamat, email, host diminta untuk di-isi, pastikan isi dengan benar, terutama di bagian **"Common Name"** dan **Email Address**.
+
+ - Pada bagian common name isi dengan **localhost**.
+ - Untuk email isi dengan alamat email yang valid.
+
+Tampilannya kurang lebih seperti pada screenshot berikut.
+
+
+
+> Selain `.crt` dan `.key`, ada ekstensi lain lagi seperti `.pem`. Format `.pem` ini merupakan jenis encoding yang sangat sering digunakan pada file kriptografi sejenis `.key` dan `.crt`. File `.crt` dan `.key` bisa di konversi ke `.pem`, dan juga sebaliknya.
+
+## C.24.3. Project Structure
+
+Buat sebuah project folder, copy 2 file yang telah ter-generate ke dalamnya. Lalu siapkan file `main.go`.
+
+
+
+## C.24.4. Web Servers
+
+Pada `main.go`, siapkan sebuah fungsi `StartNonTLSServer()`, berisikan mux dengan satu buah routing, untuk redirect request dari protokol `http` ke `https`. Nantinya semua request yang mengarah ke `http://localhost` di-redirect ke `https://localhost`. Start mux ini pada port `:80`.
+
+```go
+package main
+
+import (
+ "log"
+ "net/http"
+)
+
+func StartNonTLSServer() {
+ mux := new(http.ServeMux)
+ mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Redirecting to https://localhost/")
+ http.Redirect(w, r, "https://localhost/", http.StatusTemporaryRedirect)
+ }))
+
+ http.ListenAndServe(":80", mux)
+}
+```
+
+Lalu pada fungsi `main()`, buat mux baru lagi, dengan isi satu buah routing, menampilkan text "Hello World!". Start mux menggunakan `http.ListenAndServeTLS()` pada port `:443`, tak lupa sisipkan path dari file private key dan public key sebagai argumen fungsi.
+
+```go
+func main() {
+ go StartNonTLSServer()
+
+ mux := new(http.ServeMux)
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Hello World!"))
+ })
+
+ log.Println("Server started at :443")
+ err := http.ListenAndServeTLS(":443", "server.crt", "server.key", mux)
+ if err != nil {
+ panic(err)
+ }
+}
+```
+
+Tak lupa fungsi `StartNonTLSServer()` juga dipanggil dalam `main()`. Jadi pada satu program terdapat 2 buah web server dijalankan.
+
+OK, jalankan aplikasi.
+
+> Jika error `panic: listen tcp :443: bind: permission denied` muncul, coba jalankan aplikasi menggunakan **sudo**, contoh: `sudo go run main.go`
+
+## C.24.5. Testing
+
+Test aplikasi menggunakan `curl`. Untuk request ke protokol `https` coba tambahkan flag `--insecure` untuk men-disable verifikasi certificate.
+
+
+
+Coba test juga menggunakan browser, jika menggunakan chrome maka akan muncul warning `NET::ERR_CERT_AUTHORITY_INVALID` Klik **Advanced → Proceed to localhost (unsafe)**.
+
+
+
+Warning `NET::ERR_CERT_AUTHORITY_INVALID` muncul ketika mengakses sebuah website menggunakan protokol `https` yang di mana website ini mengaplikasikan **self-signed certificate**, bukan menggunakan certificate yang sudah diverifikasi oleh CA.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 9fa18f76fce576103a27e720fa684b3514111cd8 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:43 +0700
Subject: [PATCH 116/522] New translations
c-parsing-http-request-payload-echo.md (English)
---
.../C-parsing-http-request-payload-echo.md | 124 ++++++++++++++++++
1 file changed, 124 insertions(+)
create mode 100644 en/content-en/C-parsing-http-request-payload-echo.md
diff --git a/en/content-en/C-parsing-http-request-payload-echo.md b/en/content-en/C-parsing-http-request-payload-echo.md
new file mode 100644
index 000000000..ac9183edc
--- /dev/null
+++ b/en/content-en/C-parsing-http-request-payload-echo.md
@@ -0,0 +1,124 @@
+# C.4. Parsing HTTP Request Payload (Echo)
+
+Pada chapter ini kita akan belajar cara parsing beberapa variasi request payload.
+
+Payload dalam HTTP request bisa dikirimkan dalam berbagai bentuk. Kita akan mempelajari cara untuk handle 4 jenis payload berikut.
+
+ - Form Data
+ - JSON Payload
+ - XML Payload
+ - Query String
+
+Secara tidak sadar, kita sudah sering menggunakan jenis payload form data. Contohnya pada html form, ketika event submit di-trigger, data dikirim ke destinasi dalam bentuk form data. Indikatornya adalah data tersebut ditampung dalam request body, dan isi request header `Content-Type` adalah `application/x-www-form-urlencoded` (atau `multipart/form-data`).
+
+JSON payload dan XML payload sebenarnya sama dengan Form Data, pembedanya adalah header`Content-Type` masing-masing request. Untuk JSON payload isi header tersebut adalah `application/json`, sedang untuk XML payload adalah `application/xml`.
+
+Sedang jenis request query string adalah yang paling berbeda. Data tidak disisipkan dalam request body, melainkan pada url nya dalam bentuk key-value.
+
+## C.4.1. Parsing Request Payload
+
+Cara parsing payload request dalam echo sangat mudah, apapun jenis payload nya, API yang digunakan untuk parsing adalah sama.
+
+Mari kita langsung praktekan, buat satu folder project baru, buat `main.go`. Buat struct `User`, nantinya digunakan untuk menampung data payload yang dikirim.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/labstack/echo"
+ "net/http"
+)
+
+type User struct {
+ Name string `json:"name" form:"name" query:"name"`
+ Email string `json:"email" form:"email" query:"email"`
+}
+
+func main() {
+ r := echo.New()
+
+ // routes here
+
+ fmt.Println("server started at :9000")
+ r.Start(":9000")
+}
+```
+
+Selanjutnya siapkan satu buah endpoint `/user` menggunakan `r.Any()`. Method `.Any()` menerima segala jenis request dengan method GET, POST, PUT, atau lainnya.
+
+```go
+r.Any("/user", func(c echo.Context) (err error) {
+ u := new(User)
+ if err = c.Bind(u); err != nil {
+ return
+ }
+
+ return c.JSON(http.StatusOK, u)
+})
+```
+
+Bisa dilihat dalam handler, method `.Bind()` milik `echo.Context` digunakan, dengan disisipi parameter pointer objek (hasil cetakan struct `User`). Parameter tersebut nantinya akan menampung payload yang dikirim, entah apapun jenis nya.
+
+## C.4.2 Testing
+
+Jalankan aplikasi, lakukan testing. Bisa gunakan `curl` ataupun API testing tools sejenis postman atau lainnya.
+
+Di bawah ini shortcut untuk melakukan request menggunakan `curl` pada 4 jenis payload yang kita telah bahas. Response dari seluruh request adalah sama, menandakan bahwa data yang dikirim berhasil ditampung.
+
+#### • Form Data
+
+```bash
+curl -X POST http://localhost:9000/user \
+ -d 'name=Joe' \
+ -d 'email=nope@novalagung.com'
+
+# output => {"name":"Nope","email":"nope@novalagung.com"}
+```
+
+#### • JSON Payload
+
+```bash
+curl -X POST http://localhost:9000/user \
+ -H 'Content-Type: application/json' \
+ -d '{"name":"Nope","email":"nope@novalagung.com"}'
+
+# output => {"name":"Nope","email":"nope@novalagung.com"}
+```
+
+#### • XML Payload
+
+```bash
+curl -X POST http://localhost:9000/user \
+ -H 'Content-Type: application/xml' \
+ -d '\
+ \
+ Joe\
+ nope@novalagung.com\
+ '
+
+# output => {"name":"Nope","email":"nope@novalagung.com"}
+```
+
+#### • Query String
+
+```bash
+curl -X GET http://localhost:9000/user?name=Joe&email=nope@novalagung.com
+
+# output => {"name":"Nope","email":"nope@novalagung.com"}
+```
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 4502c5aacb2815afcf7ce16a4a729f5db54b81b1 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:44 +0700
Subject: [PATCH 117/522] New translations c-project-layout-structure.md
(English)
---
en/content-en/C-project-layout-structure.md | 122 ++++++++++++++++++++
1 file changed, 122 insertions(+)
create mode 100644 en/content-en/C-project-layout-structure.md
diff --git a/en/content-en/C-project-layout-structure.md b/en/content-en/C-project-layout-structure.md
new file mode 100644
index 000000000..f90cf9aa7
--- /dev/null
+++ b/en/content-en/C-project-layout-structure.md
@@ -0,0 +1,122 @@
+# C.1. Go Project Layout Structure
+
+Mari kita awali pembahasan pada pemrograman Go lanjut dengan topik yang paling penting, yaitu tentang bagaimana manajemen file dan folder pada project Go.
+
+[Sebenarnya tidak ada spesifikasi resmi dari Go mengenai bagaimana struktur project harus disusun](https://github.com/golang-standards/project-layout/issues/117). Akan tetapi ada beberapa project open source yang strukturnya digunakan sebagai basis **standar** dalam menyusun file dan folder program. Dan pada chapter ini kita akan mencoba membahas dan mempergunakan project tersebut sebagai acuan dalam membuat program Go.
+
+## C.1.1. Library `golang-standard/project-layout`
+
+Ada open source project yang sangat menarik untuk dipelajari, yaitu [project-layout](https://github.com/golang-standards/project-layout). Project tersebut isinya adalah project layout pada Go yang merupakan hasil kombinasi dari banyak project layout Go terkenal, seperti kubernetes, nats.io, istio, termasuk juga layout dari source code Go itu sendiri.
+
+Perlu saya tekankan, bahwa Go bukan merupakan bahasa *functional* ataupun *object-oriented*, kita selaku programmer diberikan kebebasan terhadap bagaimana penulisan *source code* aplikasi yang dikerjakan. Akan tetapi, memang ada beberapa fitur milik OOP dan bahasa *functional* dalam Go, jadi ... bebas.
+
+Termasuk juga perihal *project layout structure*, kita diberi kebebasan penuh. Di dokumentasi Go tidak ada panduan perihal bagaimana seharusnya desain struktur kode. Argumentasi ini diperkuat oleh [Russ Cox, yang merupakan Tech Lead proyek Go programming language](https://github.com/golang-standards/project-layout/issues/117).
+
+Nah, dari sini sekarang sudah cukup jelas ya.
+
+Ok, sekarang kembali ke project layout milik `golang-standard`. Saya sarankan untuk mempelajari dan mencoba struktur ini karena sangat umum diadopsi dalam pengembangan aplikasi menggunakan bahasa Go.
+
+Pada chapter ini, saya hanya akan membahas garis besarnya saja, selebihnya jika ingin praktik bisa langsung clone dari https://github.com/golang-standards/project-layout.
+
+## C.1.2. Struktur Layout `golang-standard/project-layout`
+
+Ada cukup banyak folder dan subfolder dalam project layout, berikut kami ringkas beberapa file dan direktori yang umumnya dipakai.
+
+```bash
+.
+├── go.mod
+| # file go.mod dipergunakan oleh go module (jika go mod diaktifkan).
+|
+├── Makefile
+| # file Makefile dipergunakan oleh command `make`.
+|
+├── assets/
+| # folder assets berisi static assets, seperti gambar, logo, dll.
+|
+├── build/
+| # folder build isinya adalah files untuk keperluan build dan
+| # juga CI (continous integration). Contoh file yang dimaksud adalah
+| # seperti Dockerfile, file CI tool (.travis-ci.yml, .gitlab-ci.yml)
+| # dan file untuk keperluan build ke bentuk lain seperti file deb, rpm, pkg.
+| |
+│ ├── ci/
+| | # tempatkan file untuk CI dalam folder ini
+| |
+│ └── package/
+| # tempatkan file untuk keperluan build dalam folder ini
+|
+├── cmd/
+| # folder cmd isinya adalah source code utama aplikasi.
+| #
+| # jika aplikasi merupakan sebuah app monolith, maka folder ini isinya
+| # adalah langsung source code utama.
+| # salah satu contoh, folder ini isinya adalah file-file bisnis logic utama,
+| # seperti services dan repositories.
+| #
+| # jika arsitektur microservices diadopsi, dengan layout monorepo,
+| # maka isi dari cmd adalah source code yang dibagi per service.
+| |
+│ ├── your_app_1/
+│ ├── your_app_2/
+│ ├── your_app_3/
+│ └── ...
+|
+├── configs/
+| # folder configs isinya adalah file konfigurasi.
+|
+├── deployments/
+| # folder deployments isinya adalah file yang berhubungan dengan orchestration,
+| # deployments, dan juga CD. Seperti docker-compose.yml, k8s file, dll.
+|
+├── docs/
+| # folder docs isinya adalah file design dan dokumentasi.
+|
+├── examples/
+| # folder examples isinya adalah file example.
+|
+├── init/
+| # folder init isinya adalah file-file system init (systemd, upstart, sysv)
+| # dan file konfigurasi process manager atau supervisor (runit, supervisord).
+|
+├── internal/
+| # folder internal isinya adalah file private aplikasi dan library.
+| # sebetulnya folder ini kegunaannya sama seperti `pkg`, perbedaannya adalah package
+| # dalam folder internal ini hanya bisa di-import dalam project ini, tidak bisa di-import
+| # ke project lain.
+|
+├── pkg/
+| # folder pkg isinya adalah file utility yg di-reuse dalam project yang sama,
+| # atau bisa juga di re-use oleh project lain.
+| |
+│ ├── your_public_lib_1/
+│ ├── your_public_lib_2/
+│ ├── your_public_lib_3/
+│ └── ...
+|
+├── test/
+| # folder test isinya adalah file testing. untuk struktur file-nya sendiri bebas,
+| # mau disusun seperti apa.
+| #
+| # khusus untuk unit test, baiknya tidak ditempatkan di sini,
+| # tapi ditempatkan di dalam package yang sama dengan file yang akan di-unit-test.
+|
+├── vendor/
+| # berisi clone dari 3rd party dependencies. Folder ini digunakan jika konfigurasi vendor diaktifkan
+|
+├── web/
+| # berisi aplikasi web. untuk microservices saya sarankan untuk menempatkan aplikasi web dalam folder `cmd/app`
+|
+└── ...
+```
+
+Hmm, cukup banyak juga ya yang perlu dipelajari? 😅 Tenang, tidak perlu untuk dihafal, cukup dipahami saja. Selain itu semua direktori di atas juga belum tentu dipakai semua, perlu disesuaikan dengan proyek yang sedang teman-teman kembangkan.
+
+Ok, sampai sini saja pembahasan mengenai project layout, selanjutnya silakan mencoba-coba jika berkenan, bisa dengan men-develop mulai awal, atau *clone* existing project untuk dipelajari strukturnya.
+
+---
+
+ - [Standard Go Project Layout](https://github.com/golang-standards/project-layout/), by Kyle Quest
+
+---
+
+
From 78446eaa1e2ff5c40c1a31a4a38a4be35b0cfab2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:45 +0700
Subject: [PATCH 118/522] New translations c-read-write-excel-xlsx-file.md
(English)
---
en/content-en/C-read-write-excel-xlsx-file.md | 201 ++++++++++++++++++
1 file changed, 201 insertions(+)
create mode 100644 en/content-en/C-read-write-excel-xlsx-file.md
diff --git a/en/content-en/C-read-write-excel-xlsx-file.md b/en/content-en/C-read-write-excel-xlsx-file.md
new file mode 100644
index 000000000..ff1c9f63d
--- /dev/null
+++ b/en/content-en/C-read-write-excel-xlsx-file.md
@@ -0,0 +1,201 @@
+# C.19. Read & Write Excel XLSX File (Excelize)
+
+Dalam pengembangan aplikasi web, di bagian reporting, tidak jarang kita akan berususan dengan file excel. Biasanya di tiap report diharuskan ada fasilitas untuk unduh data ke bentuk excel ataupun pdf.
+
+Pada chapter ini kita akan belajar tentang pengolahan file excel menggunakan [excelize](https://github.com/360EntSecGroup-Skylar/excelize).
+
+Dokumentasi lengkap mengenai excelize bisa dilihat di https://xuri.me/excelize/en. Silakan `go get` untuk mengunduh library ini.
+
+```bash
+go get github.com/360EntSecGroup-Skylar/excelize
+```
+
+## C.19.1. Membuat File Excel `.xlsx`
+
+Pembahasan akan dilakukan dengan langsung praktek, dengan skenario: sebuah dummy data bertipe `[]M` disiapkan, data tersebut kemudian ditulis ke dalam excel.
+
+Buat project baru, buat file main, import excelize dan siapkan dummy data-nya.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/360EntSecGroup-Skylar/excelize"
+ "log"
+)
+
+type M map[string]interface{}
+
+var data = []M{
+ M{"Name": "Noval", "Gender": "male", "Age": 18},
+ M{"Name": "Nabila", "Gender": "female", "Age": 12},
+ M{"Name": "Yasa", "Gender": "male", "Age": 11},
+}
+
+func main() {
+ // magic here
+}
+```
+
+Di fungsi `main()` buat objek excel baru, menggunakan `excelize.NewFile()`. Secara default, objek excel memiliki satu buah sheet dengan nama `Sheet1`.
+
+```go
+xlsx := excelize.NewFile()
+
+sheet1Name := "Sheet One"
+xlsx.SetSheetName(xlsx.GetSheetName(1), sheet1Name)
+```
+
+Sheet `Sheet1` kita ubah namanya menjadi `Sheet One` lewat statement `xlsx.SetSheetName()`. Perlu diperhatikan index sheet dimulai dari 1, bukan 0.
+
+Siapkan cell header menggunakan kode berikut.
+
+```go
+xlsx.SetCellValue(sheet1Name, "A1", "Name")
+xlsx.SetCellValue(sheet1Name, "B1", "Gender")
+xlsx.SetCellValue(sheet1Name, "C1", "Age")
+
+err := xlsx.AutoFilter(sheet1Name, "A1", "C1", "")
+if err != nil {
+ log.Fatal("ERROR", err.Error())
+}
+```
+
+Penulisan cell dilakukan lewat method `.SetCellValue()` milik objek excel. Pemanggilannya membutuhkan 3 buah parameter untuk disisipkan.
+
+ 1. Parameter ke-1, sheet name.
+ 2. Parameter ke-2, lokasi cell.
+ 3. Parameter ke-3, nilai/text/isi cell.
+
+Pada kode di atas, cell `A1`, `B1`, dan `C1` disiapkan dan diaktifkan filter di dalamnya. Cara mengeset filter pada cell sendiri dilakukan lewat method `.AutoFilter()`. Tentukan range lokasi cell sebagai parameter.
+
+Statement `xlsx.AutoFilter(sheet1Name, "A1", "C1", "")` artinya filter diaktifkan pada sheet `sheet1Name` mulai cell `A1` hingga `C1`.
+
+Lalu lakukan perulangan pada `data`. Tulis tiap map item sebagai cell berurutan per row setelah cell header.
+
+```go
+for i, each := range data {
+ xlsx.SetCellValue(sheet1Name, fmt.Sprintf("A%d", i+2), each["Name"])
+ xlsx.SetCellValue(sheet1Name, fmt.Sprintf("B%d", i+2), each["Gender"])
+ xlsx.SetCellValue(sheet1Name, fmt.Sprintf("C%d", i+2), each["Age"])
+}
+```
+
+Terakhir simpan objek excel sebagai file fisik. Gunakan `.SaveAs()`, isi parameter dengan path lokasi excel akan disimpan.
+
+```go
+err = xlsx.SaveAs("./file1.xlsx")
+if err != nil {
+ fmt.Println(err)
+}
+```
+
+Jalankan aplikasi, sebuah file bernama `file1.xlsx` akan muncul. Buka file tersebut lihat isinya. Data tersimpan sesuai ekspektasi. Fasilitas filter pada cell header juga aktif.
+
+
+
+## C.19.2. Pembuatan Sheet, Merge Cell, dan Cell Styles
+
+Manajemen sheet menggunakan excelize cukup mudah. Pembuatan sheet dilakukan lewat `xlsx.NewSheet()`. Mari langsung kita praktekan.
+
+Hapus baris kode mulai statement `xlsx.SaveAs()` kebawah. Lalu tambahkan kode berikut.
+
+```go
+sheet2Name := "Sheet two"
+sheetIndex := xlsx.NewSheet(sheet2Name)
+xlsx.SetActiveSheet(sheetIndex)
+```
+
+Statement `xlsx.SetActiveSheet()` digunakan untuk menge-set sheet yang aktif ketika file pertama kali dibuka. Parameter yang dibutuhkan adalah index sheet.
+
+Pada sheet baru, `Sheet two`, tambahkan sebuah text `Hello` pada sheet `A1`, lalu merge cell dengan cell di sebelah kanannya.
+
+```go
+xlsx.SetCellValue(sheet2Name, "A1", "Hello")
+xlsx.MergeCell(sheet2Name, "A1", "B1")
+```
+
+Tambahkan juga style pada cell tersebut. Buat style baru lewat `xlsx.NewStyle()`, sisipkan konfigurasi style sebagai parameter.
+
+```go
+style, err := xlsx.NewStyle(`{
+ "font": {
+ "bold": true,
+ "size": 36
+ },
+ "fill": {
+ "type": "pattern",
+ "color": ["#E0EBF5"],
+ "pattern": 1
+ }
+}`)
+if err != nil {
+ log.Fatal("ERROR", err.Error())
+}
+xlsx.SetCellStyle(sheet2Name, "A1", "A1", style)
+
+err = xlsx.SaveAs("./file2.xlsx")
+if err != nil {
+ fmt.Println(err)
+}
+```
+
+Di excelize, style merupakan objek terpisah. Kita bisa mengaplikasikan style tersebut ke cell mana saja menggunakan `xlsx.SetCellStyle()`.
+
+Silakan merujuk ke https://xuri.me/excelize/en/cell.html#SetCellStyle untuk pembahasan yang lebih detail mengenai cell style.
+
+Sekarang jalankan aplikasi, lalu coba buka file `file2.xlsx`.
+
+
+
+## C.19.3. Membaca File Excel `.xlsx`
+
+Di excelize, objek excel bisa didapat lewat dua cara.
+
+ - Dengan membuat objek excel baru menggunakan `excelize.NewFile()`.
+ - Atau dengan membaca file excel lewat `excelize.OpenFile()`.
+
+Dari objek excel, operasi baca dan tulis bisa dilakukan. Berikut merupakan contoh cara membaca file excel yang sudah kita buat, `file1.xlsx`.
+
+```go
+xlsx, err := excelize.OpenFile("./file1.xlsx")
+if err != nil {
+ log.Fatal("ERROR", err.Error())
+}
+
+sheet1Name := "Sheet One"
+
+rows := make([]M, 0)
+for i := 2; i < 5; i++ {
+ row := M{
+ "Name": xlsx.GetCellValue(sheet1Name, fmt.Sprintf("A%d", i)),
+ "Gender": xlsx.GetCellValue(sheet1Name, fmt.Sprintf("B%d", i)),
+ "Age": xlsx.GetCellValue(sheet1Name, fmt.Sprintf("C%d", i)),
+ }
+ rows = append(rows, row)
+}
+
+fmt.Printf("%v \n", rows)
+```
+
+Pada kode di atas, data tiap cell diambil lalu ditampung ke slice `M`. Gunakan `xlsx.GetCellValue()` untuk mengambil data cell.
+
+Jalankan aplikasi untuk mengecek hasilnya.
+
+
+
+---
+
+ - [Excelize](https://github.com/360EntSecGroup-Skylar/excelize), by 360 Enterprise Security Group Team, BSD 3 Clause license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From ca76d7716e297b56f1f390d97fedde867af83dc3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:46 +0700
Subject: [PATCH 119/522] New translations c-scraping-parsing-html.md (English)
---
en/content-en/C-scraping-parsing-html.md | 199 +++++++++++++++++++++++
1 file changed, 199 insertions(+)
create mode 100644 en/content-en/C-scraping-parsing-html.md
diff --git a/en/content-en/C-scraping-parsing-html.md b/en/content-en/C-scraping-parsing-html.md
new file mode 100644
index 000000000..a5b7050c7
--- /dev/null
+++ b/en/content-en/C-scraping-parsing-html.md
@@ -0,0 +1,199 @@
+# C.22. Scraping & Parsing HTML (goquery)
+
+Golang mempunyai package `net/html`, isinya digunakan untuk keperluan parsing HTML.
+
+Pada chapter ini kita akan belajar parsing HTML dengan cara yang lebih mudah, tidak memanfaatkan package `net/html`, melainkan menggunakan [goquery](https://github.com/PuerkitoBio/goquery). Library ini penggunannya mirip dengan jQuery.
+
+Sebelum dimulai, unduh terlebih dahulu package-nya menggunakan `go get`.
+
+```bash
+go get -u github.com/PuerkitoBio/goquery
+```
+
+> Untuk proses scraping konten html-nya sendiri dilakukan cukup dengan menggunakan fungsi `.Get()` milik package `net/http`.
+
+## C.22.1. Skenario Praktek
+
+Kita akan praktekan penerapan goquery untuk mengambil beberapa data dari website https://novalagung.com; pada website tersebut, di halaman landing, ada beberapa blok artikel muncul. Informasi di setiap artikel akan diambil, ditampung dalam satu objek slice, kemudian ditampilkan sebagai JSON string.
+
+
+
+## C.22.2. Praktek Scraping dan Parsing HTML
+
+Siapkan folder project baru. Pada file main siapkan sebuah struct dengan nama `Article`, isinya 3 merupakan representasi dari metadata tiap artikel, yaitu `Title`, `URL`, dan `Category`.
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "github.com/PuerkitoBio/goquery"
+ "log"
+ "net/http"
+)
+
+type Article struct {
+ Title string
+ URL string
+ Category string
+}
+
+func main() {
+ // code here ...
+}
+```
+
+Dalam fungsi `main()`, dispatch sebuah client GET request ke url https://novalagung.com untuk scraping html-nya.
+
+```go
+res, err := http.Get("https://novalagung.com")
+if err != nil {
+ log.Fatal(err)
+}
+defer res.Body.Close()
+
+if res.StatusCode != 200 {
+ log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
+}
+```
+
+Akses property `.Body` dari objek response (yang tipenya adalah reader), masukan sebagai parameter di pemanggilan `goquery.NewDocumentFromReader()`.
+
+```go
+doc, err := goquery.NewDocumentFromReader(res.Body)
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+Statement di atas mengembalikan salah satunya objek `goquery.Document`, yang ditampung dalam `doc`. Kalau dianalogikan dalam jQuery, objek `doc` adalah instance objek hasil `$(selector)`.
+
+Dari objek `goquery.Document`, gunakan API yang tersedia untuk melakukan operasi sesuai kebutuhan. Kita akan ambil semua element artikel sesuai dengan skema html yang ada pada website target.
+
+
+
+OK mari langsung kita praktekan, ambil objek `.post-feed` kemudian ambil child elements-nya.
+
+```go
+rows := make([]Article, 0)
+
+doc.Find(".post-feed").Children().Each(func(i int, sel *goquery.Selection) {
+ row := new(Article)
+ row.Title = sel.Find(".post-card-title").Text()
+ row.URL, _ = sel.Find(".post-card-content-link").Attr("href")
+ row.Category = sel.Find(".post-card-tags").Text()
+ rows = append(rows, *row)
+})
+
+bts, err := json.MarshalIndent(rows, "", " ")
+if err != nil {
+ log.Fatal(err)
+}
+
+log.Println(string(bts))
+```
+
+Gunakan `.Find()` untuk mencari elemen, isi parameter dengan selector pencarian. Gunakan `.Each()` untuk me-loop semua elemen yang didapat.
+
+Pada contoh di atas, setelah element `.post-feed` didapatkan, child elements diakses menggunakan `.Children()`.
+
+Method `.Text()` digunakan untuk mengambil text dalam elemen. Sedangkan untuk mengambil value atribut elemen, gunakan `.Attr()`.
+
+Di dalam perulangan, 3 informasi di-ekstrak dari masing-masing elemen artiekl, lalu ditampung ke objek `row`, kemudian di-append ke dalam slice.
+
+Di akhir objek slice dikonversi ke bentuk JSON string, lalu ditampilkan.
+
+Jalankan aplikasi, lihat hasilnya.
+
+
+
+## C.22.3. Modifikasi HTML
+
+API yang tersedia dalam goquery tidak hanya untuk keperluan ekstraksi informasi, tapi bisa juga untuk modifikasi/manipulasi HTML, dan operasi lainnya.
+
+Mari kita langsung praktekan saja. Buat file main baru, siapkan string html. Kali ini sumber data bukan berasal dari website asli, melainkan dari string html.
+
+```html
+package main
+
+import (
+ "github.com/PuerkitoBio/goquery"
+ "github.com/yosssi/gohtml"
+ "log"
+ "strings"
+)
+
+const sampleHTML = `
+
+
+ Sample HTML
+
+
+
Header
+
+
+
+
+
+`
+```
+
+HTML string di atas dijadikan ke bentuk `goquery.Document` lewat fungsi `goquery.NewDocumentFromReader()`. Karena parameter yang dibutuhkan bertipe reader, maka konversi string html ke tipe reader lewat `strings.NewReader()`.
+
+```go
+doc, err := goquery.NewDocumentFromReader(strings.NewReader(sampleHTML))
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+Selanjutnya, lakukan beberapa modifikasi.
+
+```go
+doc.Find("h1").AfterHtml("
Lorem Ipsum Dolor Sit Amet Gedhang Goreng
")
+doc.Find("p").AppendHtml(" Tournesol")
+doc.Find("h1").SetAttr("class", "header")
+doc.Find("footer").First().Remove()
+doc.Find("body > *:nth-child(4)").Remove()
+```
+
+Berikut penjelasan beberapa API yang digunakan pada kode di atas.
+
+ - Method `.AfterHtml()`, digunakan untuk menambahkan elemen baru, posisinya ditempatkan setelah elemen objek pemanggilan method.
+ - Method `.AppendHtml()`, digunakan untuk menambahkan child elemen baru.
+ - Method `.SetAttr()`, digunakan untuk menambahkan/mengubah atribut elemen.
+ - Method `.First()`, digunakan untuk mengakses elemen pertama. Pada kode di atas `doc.Find("footer")` mengembalikan semua footer yang ada, sesuai selector. Pengaksesan method `.First()` menjadikan hanya elemen footer pertama yang diambil.
+ - Method `.Remove()`, digunakan untuk menghapus current element.
+
+Ambil bentuk string html dari objek `doc` yang sudah banyak dimodifikasi. Jangan gunakan `doc.Html()` karena yang dikembalikan adalah [inner html](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML). Gunakan `goquery.OuterHtml(doc.Selection)` agar yang dikembalikan [outer html](https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML)-nya.
+
+```go
+modifiedHTML, err := goquery.OuterHtml(doc.Selection)
+if err != nil {
+ log.Fatal(err)
+}
+
+log.Println(gohtml.Format(modifiedHTML))
+```
+
+Pada kode di atas kita menggunakan satu lagi library, [gohtml](https://github.com/yosssi/gohtml). Fungsi `.Format()` dalam library tersebut digunakan untuk code beautification.
+
+Jalankan aplikasi, lihat hasilnya.
+
+
+
+---
+
+ - [goquery](https://github.com/PuerkitoBio/goquery), by Martin Angers, BSD-3-Clause license
+ - [gohtml](https://github.com/yosssi/gohtml), by Keiji Yoshida, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 1228ce804888a2cec47050ece70312e443a4d1d8 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:47 +0700
Subject: [PATCH 120/522] New translations
c-secure-insecure-client-http-request.md (English)
---
.../C-secure-insecure-client-http-request.md | 194 ++++++++++++++++++
1 file changed, 194 insertions(+)
create mode 100644 en/content-en/C-secure-insecure-client-http-request.md
diff --git a/en/content-en/C-secure-insecure-client-http-request.md b/en/content-en/C-secure-insecure-client-http-request.md
new file mode 100644
index 000000000..cef306894
--- /dev/null
+++ b/en/content-en/C-secure-insecure-client-http-request.md
@@ -0,0 +1,194 @@
+# C.27. Secure & Insecure Client HTTP Request
+
+Pada chapter ini topik yang dibahas adalah cara melakukan http request ke SSL/TLS-enabled web server, menggunakan dua teknik:
+
+ - Insecure request
+ - Secure request menggunakan file certificate
+
+## C.27.1. Handshake
+
+Sebelum masuk ke inti pembahasan, kita perlu mempelajari terlebih dahulu tentang pembeda antara secure request dan http request biasa.
+
+Dalam secure request, sebelum data benar-benar diterima oleh server, terjadi proses negosiasi antara client (yg men-dispatch request) dan server (tujuan request), proses ini biasa disebut dengan handshake.
+
+Proses negosiasi tersebut dipecah menjadi 5 fase.
+
+ 1. Fase **Client Hello**. Pada fase ini handshake dimulai dengan client mengirimkan pesan yang kita sebut dengan **client hello** ke se server. Pesan tersebut berisikan semua informasi milik client yang diperlukan oleh server untuk bisa terhubung dengan client via SSL. Informasi yang dimaksud di antaranya adalah versi SSL/TLS dan konfigurasi cipher. Cipher suite sendiri adalah seperangkat algoritma, digunakan untuk membantu pengamanan koneksi yang menerapkan TLS/SSL.
+
+ 2. Fase **Server Hello**. Setelah diterima, server merespon dengan pesan yang mirip, yaitu **server hello**, isinya juga informasi yang kurang lebih sejenis. Informasi ini diperlukan oleh client untuk bisa terhubung balik dengan server.
+
+ 3. Fase **Otentikasi dan Pre-Master Secret**. Setelah kontak antara client dan server terjadi, server mengenalkan dirinya ke client lewat file certificate. Anggap saja certificate tersebut sebagai KTP (Kartu Tanda Penduduk). Client selanjutnya melakukan pengecekan, apakah KTP tersebut valid dan dipercaya, atau tidak. Jika memang terpercaya, client selanjutnya membuat data yang disebut dengan **pre-master secret**, meng-enkripsi-nya menggunakan public key, lalu mengirimnya ke server sebagai response.
+
+ 4. Fase **Decryption dan Master Secret**. Data encrypted pre-master secret yang dikirim oleh client diterima oleh server. Data tersebut kemudian di-decrypt menggunakan private key. Selanjutnya server dan client melakukan beberapa hal untuk men-generate **master secret** lewat cipher yang sudah disepakati.
+
+ 5. Fase **Encryption with Session Key**. Server dan client melakukan pertukaran pesan untuk menginfokan bahwa data yang dikirim dalam request tersebut dan request-request selanjutnya akan di-enkripsi.
+
+Sekarang kita tau, bahwa agar komunikasi antara client dan server bisa terjalin, pada sisi client harus ada file certificate, dan pada sisi server harus private key & certificate.
+
+OK, saya rasa bagian teori sudah cukup, mari kita lanjut ke bagian praktek.
+
+## C.27.2. Persiapan
+
+Salin project pada chapter sebelumnya, [C.26. Advanced Client HTTP Request](/C-client-http-request-advanced.html) sebagai folder project baru.
+
+## C.27.3. Konfigurasi SSL/TLS pada Web Server
+
+Pada chapter [A.55. Simple Client HTTP Request](/A-client-http-request-simple.html) kita telah belajar implementasi client http request, penerapannya dengan 2 buah aplikasi terpisah, satu aplikasi web server dan satu lagi adalah aplikasi consumer.
+
+Kita perlu menambahkan sedikit modifikasi pada aplikasi web server (yang sudah di salin), mengaktifkan SSL/TLS-nya dengan cara mengubah bagian `.ListenAndServe()` menjadi `.ListenAndServeTLS()`, dengan disisipkan dua parameter berisi path certificate dan private key.
+
+```go
+err := server.ListenAndServeTLS("server.crt", "server.key")
+```
+
+Silakan generate certificate dan private key baru, caranya sama seperti pada chapter [C.24. HTTPS/TLS Web Server](/C-https-tls.html).
+
+> Konfigurasi SSL/TLS lewat `server.ListenAndServeTLS("server.crt", "server.key")` merupakan cara yang paling mudah dengan konfigurasi adalah paling minimal.
+
+## C.27.4. Insecure Request
+
+Dari yang sudah dijelaskan di atas, agar komunikasi antara client dan server bisa ter-enkripsi, di sisi client atau consumer harus ada yang namanya file certificate.
+
+Jika client tidak menyertakan certificate dalam request-nya, maka pasti terjadi error (pada saat handshake). Contohnya bisa dilihat pada screenshot berikut.
+
+
+
+Akan tetapi, jika memang client tidak memilik certificate dan komunikasi ingin tetap dilakukan, masih bisa (dengan catatan server meng-allow kapabilitas ini), caranya yaitu menggunakan teknik *insecure request*.
+
+> Dalam insecure request, komunikasi terjalin tanpa ada proses enkripsi data.
+
+Cara membuat insecure request sangat mudah, cukup aktifkan atribut insecure pada request. Misal menggunakan **curl**, maka cukup tambahkan flag `--insecure` pada command.
+
+```bash
+curl -X POST https://localhost/data \
+ --insecure \
+ -H 'Content-Type: application/json' \
+ -d '{"Name": "Noval Agung"}'
+```
+
+Penerapan inscure request dalam golang juga tidak terlalu sulit. Pada object `http.Client`, isi property `.Transport` dengan objek baru buatan struct `http.Transport` yang di dalamnya berisi konfigurasi insecure request.
+
+```go
+client := new(http.Client)
+client.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+}
+```
+
+Ubah kode pada aplikasi client (yang sudah disalin) seperti di atas. Jangan lupa juga untuk mengganti protokol base url destinasi, dari `http` ke `https`.
+
+```go
+baseURL := "https://localhost:9000"
+```
+
+Jalankan ulang aplikasi server yang sudah ssl-enabled dan aplikasi client yang sudah dikonfigurasi untuk insecure request, lalu test hasilnya.
+
+
+
+## C.27.5. Secure Request
+
+Secure request adalah bentuk request yang datanya ter-enkripsi, bisa dibilang kebalikan dari insecure request. Request jenis ini pada sisi client atau consumer membutuhkan konfigurasi di mana file certificate diperlukan.
+
+Secure request bisa dilakukan dengan mudah di golang. Mari langsung saja kita praktekan. Pertama, pada file consumer, tambahkan package `crypto/x509`.
+
+```go
+import (
+ // ...
+ "crypto/x509"
+)
+```
+
+> X.509 adalah standar format public key certificates.
+
+Lalu buat objek baru bertipe `x509.CertPool` lewat `x509.NewCertPool()`. Objek ini nantinya menampung list certificate yang digunakan.
+
+Buat objek menggunakan struct `tls.Config`, dengan isi property `RootCAs` adalah objek list certificate yang sudah dibuat.
+
+Isi `client.Transport` dengan konfigurasi secure request. Hapus saja konfigurasi insecure request sebelumnya.
+
+Kurang lebih kode-nya seperti berikut.
+
+```go
+certFile, err := ioutil.ReadFile("server.crt")
+if err != nil {
+ return nil, err
+}
+
+caCertPool := x509.NewCertPool()
+caCertPool.AppendCertsFromPEM(certFile)
+
+tlsConfig := &tls.Config{ RootCAs: caCertPool }
+tlsConfig.BuildNameToCertificate()
+
+client := new(http.Client)
+client.Transport = &http.Transport{
+ TLSClientConfig: tlsConfig,
+}
+```
+
+Bisa dilihat pada kode di atas, file `server.crt` dibaca isinya, lalu dimasukan ke `caCertPool`. Objek `caCertPool` ini bisa menampung banyak certificate, jika memang dibutuhkan banyak.
+
+OK, silakan langsung run aplikasi untuk testing.
+
+
+
+## C.27.6. Konfigurasi SSL/TLS Lanjutan
+
+Di atas kita sudah belajar cara setting SSL/TLS pada web server, dengan konfigurasi minimal menggunakan `server.ListenAndServeTLS("server.crt", "server.key")`.
+
+Konfigurasi yang lebih complex bisa kita lakukan menggunakan `tls.Config`. Buat objek menggunakan struct tersebut lalu manfaatkan property struct-nya untuk menciptakan konfigurasi yang sesuai dengan kebutuhan. Contoh kurang lebih seperti kode di bawah ini.
+
+```go
+certPair1, err := tls.LoadX509KeyPair("server.crt", "server.key")
+if err != nil {
+ log.Fatalln("Failed to start web server", err)
+}
+
+tlsConfig := new(tls.Config)
+tlsConfig.NextProtos = []string{"http/1.1"}
+tlsConfig.MinVersion = tls.VersionTLS12
+tlsConfig.PreferServerCipherSuites = true
+
+tlsConfig.Certificates = []tls.Certificate{
+ certPair1, /** add other certificates here **/
+}
+tlsConfig.BuildNameToCertificate()
+
+tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven
+tlsConfig.CurvePreferences = []tls.CurveID{
+ tls.CurveP521,
+ tls.CurveP384,
+ tls.CurveP256,
+}
+tlsConfig.CipherSuites = []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+}
+```
+
+Tampung saja objek cetakan `server.TLSConfig` di atas ke dalam `server.TLSConfig`. Jika file certificate dan private key sudah ditambahkan dalam `tlsConfig`, maka dalam pemanggilan `server.ListenAndServeTLS()` kosongkan saja parameter-nya.
+
+```go
+server := new(http.Server)
+server.Handler = mux
+server.Addr = ":9000"
+server.TLSConfig = tlsConfig
+
+err := server.ListenAndServeTLS("", "")
+if err != nil {
+ log.Fatalln("Failed to start web server", err)
+}
+```
+
+Tujuan mengapa penulis tambahkan sub chapter **Konfigurasi SSL/TLS Lanjutan** ini adalah agar pembaca tau bahwa konfigurasi SSL/TLS yang compleks bisa dilakukan dengan mudah dalam aplikasi web golang. Mengenai pembahasan tiap-tiap property silakan pelajari sendiri.
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 08b756c85ba80364718f879d8d524d94ea9ccd6d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:48 +0700
Subject: [PATCH 121/522] New translations c-secure-middleware.md (English)
---
en/content-en/C-secure-middleware.md | 157 +++++++++++++++++++++++++++
1 file changed, 157 insertions(+)
create mode 100644 en/content-en/C-secure-middleware.md
diff --git a/en/content-en/C-secure-middleware.md b/en/content-en/C-secure-middleware.md
new file mode 100644
index 000000000..36ec10786
--- /dev/null
+++ b/en/content-en/C-secure-middleware.md
@@ -0,0 +1,157 @@
+# C.16. Secure Middleware
+
+Pada chapter ini kita akan belajar menggunakan library [secure](https://github.com/unrolled/secure) untuk meningkatkan keamanan aplikasi web.
+
+## C.16.1. Keamanan Web Server
+
+Jika berbicara mengenai keamanan aplikasi web, sangat luas sebenarnya cakupannya, ada banyak hal yang perlu diperhatian dan disiapkan. Mungkin tiga di antaranya sudah kita pelajari sebelumnya, yaitu penerapan Secure Cookie, CORS, dan CSRF.
+
+Selain 3 topik tersebut masih terdapat banyak lagi. Beruntungnya ada library [secure](https://github.com/unrolled/secure). Sesuai tagline-nya, secure library digunakan untuk membantu mengatasi beberapa masalah keamanan aplikasi.
+
+Secure library merupakan middleware, penggunaannya sama seperti middleware pada umumnya.
+
+## C.15.2. Praktek
+
+Mari langsung kita praktekan. Buat folder project baru. Di file main tulis kode berikut. Sebuah aplikasi dibuat, isinya satu buah rute `/index` yang bisa diakses dari mana saja.
+
+```go
+package main
+
+import (
+ "net/http"
+ "github.com/labstack/echo"
+)
+
+func main() {
+ e := echo.New()
+
+ e.GET("/index", func(c echo.Context) error {
+ c.Response().Header().Set("Access-Control-Allow-Origin", "*")
+
+ return c.String(http.StatusOK, "Hello")
+ })
+
+ e.Logger.Fatal(e.StartTLS(":9000", "server.crt", "server.key"))
+}
+```
+
+Perlu diketahui, aplikasi di atas di-start dengan SSL/TLS enabled. Dua buah file dibutuhkan, yaitu file certificate `server.crt` dan file private key `server.key`. Silakan unduh kedua file tersebut dari source code di [GitHub, folder chapter-C.16-secure-middleware](https://github.com/novalagung/dasarpemrogramangolang-example/tree/master/chapter-C.16-secure-middleware). Pada chapter [C.24. HTTPS/TLS Web Server](/C-https-tls.html) nantinya akan kita pelajari lebih lanjut mengenai cara generate kedua file di atas hingga cara penggunannya.
+
+Kembali ke pembahasan, sekarang tambahkan secure middleware. Import package-nya, buat instance middleware, lalu registrasikan ke echo.
+
+```go
+import (
+ // ...
+ "github.com/unrolled/secure"
+)
+
+func main() {
+ // ...
+
+ secureMiddleware := secure.New(secure.Options{
+ AllowedHosts: []string{"localhost:9000", "www.google.com"},
+ FrameDeny: true,
+ CustomFrameOptionsValue: "SAMEORIGIN",
+ ContentTypeNosniff: true,
+ BrowserXssFilter: true,
+ })
+
+ e.Use(echo.WrapMiddleware(secureMiddleware.Handler))
+
+ // ...
+}
+```
+
+Pembuatan objek secure middleware dilakukan menggunakan `secure.New()` dengan isi parameter adalah konfigurasi. Bisa dilihat ada 5 buah property konfigurasi di-set. Berikut merupakan penjelasan tiap-tiap property tersebut.
+
+#### • Konfigurasi `AllowedHosts`
+
+```go
+AllowedHosts: []string{"localhost:9000", "www.google.com"}
+```
+
+Host yang diperbolehkan mengakses web server ditentukan hanya 2, yaitu localhost:9000 yang merupakan web server itu sendiri, dan google.com. Silakan coba mengakses aplikasi kita ini menggunakan AJAX lewat google.com dan domainnya lainnya untuk mengetes apakah fungsionalitas nya berjalan.
+
+#### • Konfigurasi `FrameDeny`
+
+```go
+FrameDeny: true
+```
+
+Secara default sebuah aplikasi web adalah bisa di-load di dalam iframe yang berada host nya berbeda. Misalnya di salah satu laman web www.kalipare.com ada iframe yang atribut src nya berisi www.novalagung.com, hal seperti ini diperbolehkan.
+
+Perijinan apakah website boleh di-load lewat iframe atau tidak, dikontrol lewat header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).
+
+Di library secure, untuk men-disable ijin akses aplikasi dari dalam iframe, bisa dilakukan cukup dengan mengeset proerty `FrameDeny` dengan nilai `true`.
+
+Untuk mengetes, silakan buat aplikasi web terpisah yang mer-render sebuah view. Dalam view tersebut siapkan satu buah iframe yang mengarah ke `https://localhost:9000/index`.
+
+#### • Konfigurasi `CustomFrameOptionsValue`
+
+```go
+CustomFrameOptionsValue: "SAMEORIGIN"
+```
+
+Jika `FrameDeny` di-set sebagai `true`, maka semua host (termasuk aplikasi itu sendiri) tidak akan bisa me-load url lewat iframe.
+
+Dengan menambahkan satu buah property lagi yaitu `CustomFrameOptionsValue: "SAMEORIGIN"` maka ijin pengaksesan url lewat iframe menjadi eksklusif hanya untuk aplikasi sendiri.
+
+Untuk mengetes, buat rute baru yang me-render sebuah view. Dalam view tersebut siapkan satu buah iframe yang mengarah ke `/index`.
+
+#### • Konfigurasi `ContentTypeNosniff`
+
+```go
+ContentTypeNosniff: true
+```
+
+Property `ContentTypeNosniff: true` digunakan untuk disable MIME-sniffing yang dilakukan oleh browser IE. Lebih jelasnya silakan baca [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options).
+
+#### • Konfigurasi `BrowserXssFilter`
+
+```go
+BrowserXssFilter: true
+```
+
+Property di atas digunakan untuk mengaktifkan header [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection), dengan isi header adalah `1; mode=block`.
+
+## C.15.3. Property Library Secure
+
+Selain 5 property yang kita telah pelajari di atas, masih ada banyak lagi konfigurasi yang bisa digunakan.
+
+ - AllowedHosts
+ - HostsProxyHeaders
+ - SSLRedirect
+ - SSLTemporaryRedirect
+ - SSLHost
+ - SSLHostFunc
+ - SSLProxyHeaders
+ - STSSeconds
+ - STSIncludeSubdomains
+ - STSPreload
+ - ForceSTSHeader
+ - FrameDeny
+ - CustomFrameOptionsValue
+ - ContentTypeNosniff
+ - BrowserXssFilter
+ - CustomBrowserXssValue
+ - ContentSecurityPolicy
+ - PublicKey
+ - ReferrerPolicy
+
+Lebih mendetailnya silakan langsung cek halaman official library secure di https://github.com/unrolled/secure.
+
+---
+
+ - [Secure](https://github.com/unrolled/secure), by Cory Jacobsen, MIT license
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From df3851933f828c24f3e73729ed102251d8041525 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:49 +0700
Subject: [PATCH 122/522] New translations c-securecookie.md (English)
---
en/content-en/C-securecookie.md | 162 ++++++++++++++++++++++++++++++++
1 file changed, 162 insertions(+)
create mode 100644 en/content-en/C-securecookie.md
diff --git a/en/content-en/C-securecookie.md b/en/content-en/C-securecookie.md
new file mode 100644
index 000000000..de0e27aa5
--- /dev/null
+++ b/en/content-en/C-securecookie.md
@@ -0,0 +1,162 @@
+# C.12. Secure Cookie (Gorilla Securecookie)
+
+Pada chapter [B.21. HTTP Cookie](/B-cookie.html), kita telah mempelajari tentang cookie dan implementasinya di golang.
+
+Cookie memiliki beberapa atribut, di antaranya adalah `secure`. Dengan mengaktifkan atribut ini, informasi cookie menjadi lebih aman karena di-enkripsi, namun kapabilitas ini hanya akan aktif pada kondisi aplikasi SSL/TLS enabled.
+
+> TL;DR; Jika atribut `secure` di-isi `true`, namun web server TIDAK menggunakan SSL/TLS, maka cookie disimpan seperti biasa tanpa di-enkripsi.
+
+Lalu bagaimana cara untuk membuat cookie aman pada aplikasi yang meng-enable SSL/TLS maupun yang tidak? caranya adalah dengan menambahkan step enkripsi data sebelum disimpan dalam cookie (dan men-decrypt data tersebut saat membaca).
+
+Gorilla toolkit menyediakan library bernama [securecookie](https://github.com/gorilla/securecookie), berguna untuk mempermudah enkripsi informasi cookie, dengan penerapan yang mudah. Pada chapter ini kita akan mempelajari penggunaannya.
+
+## C.12.1. Create & Read Secure Cookie
+
+Penggunaan securecookie cukup mudah, buat objek secure cookie lewat `securecookie.New()` lalu gunakan objek tersebut untuk operasi encode-decode data cookie. Pemanggilan fungsi `.New()` memerlukan 2 buah argument.
+
+ - Hash key, diperlukan untuk otentikasi data cookie menggunakan algoritma kriptografi HMAC.
+ - Block key, adalah opsional, diperlukan untuk enkripsi data cookie. Default algoritma enkripsi yang digunakan adalah AES.
+
+OK, langsung saja kita praktekan. Buat folder project seperti biasa lalu isi `main.go` dengan kode berikut.
+
+```go
+package main
+
+import (
+ "github.com/gorilla/securecookie"
+ "github.com/labstack/echo"
+ gubrak "github.com/novalagung/gubrak/v2"
+ "net/http"
+ "time"
+)
+
+type M map[string]interface{}
+
+var sc = securecookie.New([]byte("very-secret"), []byte("a-lot-secret-yay"))
+```
+
+Variabel `sc` adalah objek secure cookie. Objek ini kita gunakan untuk encode data yang akan disimpan dalam cookie, dan juga untuk decode data.
+
+Buat fungsi `setCookie()`, bertugas untuk mempermudah pembuatan dan penyimpanan cookie.
+
+```go
+func setCookie(c echo.Context, name string, data M) error {
+ encoded, err := sc.Encode(name, data)
+ if err != nil {
+ return err
+ }
+
+ cookie := &http.Cookie{
+ Name: name,
+ Value: encoded,
+ Path: "/",
+ Secure: false,
+ HttpOnly: true,
+ Expires: time.Now().Add(1 * time.Hour),
+ }
+ http.SetCookie(c.Response(), cookie)
+
+ return nil
+}
+```
+
+Method `sc.Encode()` digunakan untuk encoding data dengan identifier adalah isi variabel `name`. Variabel `encoded` menampung data setelah di-encode, lalu variabel ini dimasukan ke dalam objek cookie.
+
+Cara menyimpan cookie masih sama, menggunakan `http.SetCookie`.
+
+Selanjutnya buat fungsi `getCookie()`, untuk mempermudah proses pembacaan cookie yang tersimpan.
+
+```go
+func getCookie(c echo.Context, name string) (M, error) {
+ cookie, err := c.Request().Cookie(name)
+ if err == nil {
+
+ data := M{}
+ if err = sc.Decode(name, cookie.Value, &data); err == nil {
+ return data, nil
+ }
+ }
+
+ return nil, err
+}
+```
+
+Setelah cookie diambil menggunakan `c.Request().Cookie()`, data di dalamnya perlu di-decode agar bisa terbaca. Method `sc.Decode()` digunakan untuk decoding data.
+
+OK, sekarang buat fungsi `main()`, lalu isi dengan kode di bawah ini.
+
+```go
+const CookieName = "data"
+
+e := echo.New()
+
+e.GET("/index", func(c echo.Context) error {
+ data, err := getCookie(c, CookieName)
+ if err != nil && err != http.ErrNoCookie && err != securecookie.ErrMacInvalid {
+ return err
+ }
+
+ if data == nil {
+ data = M{"Message": "Hello", "ID": gubrak.RandomString(32)}
+
+ err = setCookie(c, CookieName, data)
+ if err != nil {
+ return err
+ }
+ }
+
+ return c.JSON(http.StatusOK, data)
+})
+
+e.Logger.Fatal(e.Start(":9000"))
+```
+
+Konstanta `CookieName` disiapkan, kita gunakan sebagai identifier cookie. Dan sebuah rute juga disiapkan dengan tugas menampilkan data cookie jika sudah ada, dan membuat cookie baru jika belum ada.
+
+Dalam handler rute, terdapat beberapa proses terjadi. Pertama, objek cookie dengan identifier `CookieName` diambil, jika muncul error, dan jenisnya adalah selain error karena cookie tidak ada, dan error-nya selain *invalid cookie*, maka kembalikan objek error tersebut.
+
+> `http.ErrNoCookie` adalah variabel penanda error karena cookie kosong, sedangkan `securecookie.ErrMacInvalid` adalah representasi dari invalid cookie.
+
+Lalu, kita cek data cookie yang dikembalikan, jika kosong (bisa karena cookie belum dibuat ataupun sudah ada tetapi datanya kosong) maka buat data baru untuk disimpan dalam cookie. Data tersebut bertipe `map`, salah satu elemen map tersebut ada yg value-nya adalah random.
+
+> Pada kode di atas, generate random string dilakukan dengan memanfaatkan 3rd party library [Gubrak v2](https://github.com/novalagung/gubrak).
+
+Pengaksesan rute akan memunculkan data yang sama. Karena pembuatan cookie hanya dilakukan ketika datanya kosong atau cookie nya belum dibuat.
+
+Jalankan aplikasi untuk mengetes hasilnya. Lakukan refresh beberapa kali, data yang muncul pasti sama.
+
+
+
+Lihat pada response header url `index`, data pada cookie terlihat sudah dalam kondisi encoded dan encrypted.
+
+
+
+## C.12.2. Delete Secure Cookie
+
+Securecookie perannya hanya pada bagian encode-decode data cookie, sedangkan proses simpan baca cookie masih sama seperti penerapan cookie biasa. Maka cara menghapus cookie pun masih sama, yaitu dengan meng-expired-kan cookie yang sudah disimpan.
+
+```go
+cookie := &http.Cookie{}
+cookie.Name = name
+cookie.Path = "/"
+cookie.MaxAge = -1
+cookie.Expires = time.Unix(0, 0)
+http.SetCookie(c.Response(), cookie)
+```
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [Gorilla Securecookie](https://github.com/gorilla/securecookie), by Gorilla web toolkit team, BSD-3-Clause license
+ - [Gubrak v2](https://github.com/novalagung/gubrak), by Noval Agung, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 44e040b5b9dc3d35081c2f0a36220729b6d0185b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:49 +0700
Subject: [PATCH 123/522] New translations c-send-email.md (English)
---
en/content-en/C-send-email.md | 203 ++++++++++++++++++++++++++++++++++
1 file changed, 203 insertions(+)
create mode 100644 en/content-en/C-send-email.md
diff --git a/en/content-en/C-send-email.md b/en/content-en/C-send-email.md
new file mode 100644
index 000000000..50195f667
--- /dev/null
+++ b/en/content-en/C-send-email.md
@@ -0,0 +1,203 @@
+# C.18. Send Mail (`net/smtp`, Gomail v2)
+
+Pada chapter ini kita akan belajar cara mengirim email dari aplikasi golang, menggunakan dua cara berikut.
+
+ 1. Dengan memanfaatkan package `net/smtp`.
+ 2. Menggunakan Library [gomail](https://gopkg.in/gomail.v2).
+
+## C.18.1. Kirim Email Menggunakan `net/smtp`
+
+Golang menyediakan package `net/smtp`, isinya banyak API untuk berkomunikasi via protokol SMTP. Lewat package ini kita bisa melakukan operasi kirim email.
+
+Sebuah akun email diperlukan dalam mengirim email, silakan gunakan provider email apa saja. Pada chapter ini kita gunakan Google Mail (gmail), jadi siapkan satu buah akun gmail untuk keperluan testing.
+
+Mari kita praktekan. Buat folder project baru, salin kode berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/smtp"
+ "strings"
+)
+
+const CONFIG_SMTP_HOST = "smtp.gmail.com"
+const CONFIG_SMTP_PORT = 587
+const CONFIG_SENDER_NAME = "PT. Makmur Subur Jaya "
+const CONFIG_AUTH_EMAIL = "emailanda@gmail.com"
+const CONFIG_AUTH_PASSWORD = "passwordemailanda"
+
+func main() {
+ to := []string{"recipient1@gmail.com", "emaillain@gmail.com"}
+ cc := []string{"tralalala@gmail.com"}
+ subject := "Test mail"
+ message := "Hello"
+
+ err := sendMail(to, cc, subject, message)
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ log.Println("Mail sent!")
+}
+```
+
+Dalam implementasinya, untuk bisa mengirim email, dibutuhkan mail server. Karena kita menggunakan email google, maka mail server milik google digunakan.
+
+Pada kode di atas, konstanta dengan prefix `CONFIG_` adalah konfigurasi yang diperlukan untuk terhubung dengan mail server. Isi dari kedua variabel `CONFIG_AUTH_EMAIL` dan `CONFIG_AUTH_PASSWORD` digunakan untuk keperluan otentikasi dengan SMTP server. Silakan sesuaikan dengan akun yang digunakan di masing-masing.
+
+Di dalam fungsi main bisa dilihat, fungsi `sendMail()` dipanggil untuk mengirim email, dengan empat buah parameter disisipkan.
+
+ - Parameter `to`, adalah tujuan email.
+ - Parameter `cc`, adalah cc tujuan.
+ - Parameter `subject`, adalah subjek email.
+ - Parameter `message`, adalah body email.
+
+Konstanta `CONFIG_SENDER_NAME` isinya dipergunakan sebagai label header pengirim email. Bisa di-isi dengan label apapun.
+
+> Di sini `CONFIG_SENDER_NAME` perannya hanya sebagai label header pengirim email saja. Untuk email otentikasi dengan SMTP server sendiri yang dipergunakan adalah `CONFIG_AUTH_EMAIL`.
+
+OK, selanjutnya buat fungsi `sendMail()` berikut.
+
+```go
+func sendMail(to []string, cc []string, subject, message string) error {
+ body := "From: " + CONFIG_SENDER_NAME + "\n" +
+ "To: " + strings.Join(to, ",") + "\n" +
+ "Cc: " + strings.Join(cc, ",") + "\n" +
+ "Subject: " + subject + "\n\n" +
+ message
+
+ auth := smtp.PlainAuth("", CONFIG_AUTH_EMAIL, CONFIG_AUTH_PASSWORD, CONFIG_SMTP_HOST)
+ smtpAddr := fmt.Sprintf("%s:%d", CONFIG_SMTP_HOST, CONFIG_SMTP_PORT)
+
+ err := smtp.SendMail(smtpAddr, auth, CONFIG_AUTH_EMAIL, append(to, cc...), []byte(body))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+```
+
+Fungsi `sendMail()` digunakan untuk mengirim email. Empat data yang disisipkan pada fungsi tersebut dijadikan satu dalam format tertentu, lalu disimpan ke variabel `body`.
+
+Statement yang ditampung oleh `body` akan menghasilkan string berikut (formatnya adalah baku).
+
+```
+From: PT. Makmur Subur Jaya
+To: recipient1@gmail.com, emaillain@gmail.com
+Cc: tralalala@gmail.com
+Subject: Test mail
+
+Hello
+```
+
+Pengiriman email dilakukan lewat `smtp.SendMail()`. Dalam pemanggilannya 5 buah parameter disisipkan, berikut adalah penjelasan masing-masing parameter.
+
+ - Parameter ke-1, `smtpAddr`, merupakan kombinasi host dan port mail server.
+ - Parameter ke-2, `auth`, menampung credentials untuk keperluan otentikasi ke mail server. Objek ini dicetak lewat `smtp.PlainAuth()`.
+ - Parameter ke-3, `CONFIG_AUTH_EMAIL`, adalah alamat email yang digunakan untuk mengirim email.
+ - Parameter ke-4, Isinya adalah semua email tujuan, termasuk `Cc`.
+ - Parameter ke-5, isinya `body` email.
+
+Jalankan aplikasi. Lihat di console, error muncul.
+
+
+
+Error di atas hanya muncul pada pengiriman email menggunakan akun google mail. Untuk alasan keamanan, google men-disable akun gmail untuk digunakan mengirim email lewat kode program.
+
+Aktifkan fasilitas **less secure apps** untuk meng-enable-nya. Login ke gmail masing-masing, kemudian buka link https://myaccount.google.com/lesssecureapps, lalu klik tombol toggle agar menjadi **OFF**.
+
+
+
+Jalankan ulang aplikasi, email terkirim. Lihat di inbox email tujuan pengiriman untuk mengecek hasilnya.
+
+
+
+## C.18.2. Kirim Email Menggunakan Gomail v2
+
+Dengan library [gomail](https://gopkg.in/gomail.v2), pengiriman email bisa dilakukan dengan mudah. Beberapa operasi seperti membuat email dalam bentuk html, menambahkan attachment, menambahkan bcc, bisa dilakukan dengan mudah lewat gomail.
+
+Mari langsung kita praktekan. Unduh terlebih dahulu library-nya.
+
+```bash
+go get -u gopkg.in/gomail.v2
+```
+
+Lalu tulis kode berikut.
+
+```go
+package main
+
+import (
+ "gopkg.in/gomail.v2"
+ "log"
+)
+
+const CONFIG_SMTP_HOST = "smtp.gmail.com"
+const CONFIG_SMTP_PORT = 587
+const CONFIG_SENDER_NAME = "PT. Makmur Subur Jaya "
+const CONFIG_AUTH_EMAIL = "emailanda@gmail.com"
+const CONFIG_AUTH_PASSWORD = "passwordemailanda"
+
+func main() {
+ mailer := gomail.NewMessage()
+ mailer.SetHeader("From", CONFIG_SENDER_NAME)
+ mailer.SetHeader("To", "recipient1@gmail.com", "emaillain@gmail.com")
+ mailer.SetAddressHeader("Cc", "tralalala@gmail.com", "Tra Lala La")
+ mailer.SetHeader("Subject", "Test mail")
+ mailer.SetBody("text/html", "Hello, have a nice day")
+ mailer.Attach("./sample.png")
+
+ dialer := gomail.NewDialer(
+ CONFIG_SMTP_HOST,
+ CONFIG_SMTP_PORT,
+ CONFIG_AUTH_EMAIL,
+ CONFIG_AUTH_PASSWORD,
+ )
+
+ err := dialer.DialAndSend(mailer)
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ log.Println("Mail sent!")
+}
+```
+
+Siapkan satu buah image bernama `sample.png`, simpan di dalam folder yang sama dengan file main. Untuk meng-attach file ke dalam email, gunakan method `.Attach()` milik `*gomail.Message`.
+
+Pada contoh kali ini email isinya adalah HTML. Gunakan MIME html pada parameter pertama `.SetBody()` untuk mengaktifkan mode html email.
+
+Jalankan aplikasi, lalu cek hasilnya email yang dikirim di inbox.
+
+
+
+## C.18.3. Kirim Email dengan Konfigurasi SMTP Relay / No Auth / Tanpa Otentikasi
+
+Cara untuk mengirim email menggunakan konfigurasi SMTP relay atau *No Auth* sangatlah mudah lewat library gomail.v2.
+
+Cukup lakukan sedikit modifikasi berikut pada kode yang sudah dibuat. Pada inisialisasi object `dialer` ubah statement-nya menjadi berikut:
+
+```go
+dialer := &gomail.Dialer{Host: CONFIG_SMTP_HOST, Port: CONFIG_SMTP_PORT}
+```
+
+Daaaaannnnn ... cukup itu saja penyesuaiannya agar bisa kirim email via konfigurasi SMTP relay :-)
+
+---
+
+ - [Gomail v2](https://gopkg.in/gomail.v2), by Alexandre Cesaro, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 20bbbdf0dbe472ac90c4efc11fb229c2a9375da3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:50 +0700
Subject: [PATCH 124/522] New translations c-session.md (English)
---
en/content-en/C-session.md | 319 +++++++++++++++++++++++++++++++++++++
1 file changed, 319 insertions(+)
create mode 100644 en/content-en/C-session.md
diff --git a/en/content-en/C-session.md b/en/content-en/C-session.md
new file mode 100644
index 000000000..8cdc26415
--- /dev/null
+++ b/en/content-en/C-session.md
@@ -0,0 +1,319 @@
+# C.13. Session (Gorilla Session)
+
+Session adalah sebuah konsep penyimpanan data yang shared antar http request. Session umumnya menggunakan cookie untuk menyimpan identifier (kita sebut sebagai **SessionID**). Informasi SessionID tersebut ber-asosiasi dengan data (kita sebut sebagai **SessionData**) yang disimpan di sisi back end dalam media tertentu.
+
+Di back end, SessionData disimpan dalam media database, atau memory, atau fasilitas penyimpanan lainnya. Bisa saja sebenarnya jika SessionData juga disimpan dalam cookie, dengan memanfaatkan secure cookie maka SessionData tersebut ter-enkripsi dan aman dari peretas. Memang aman, tapi jelasnya lebih aman kalau disimpan di sisi server.
+
+Pada chapter ini kita akan mempelajari penerapan session di golang menggunakan beberapa jenis media penyimpanan, yaitu mongo db, postgres sql db, dan secure cookie.
+
+## C.13.1. Manage Session Menggunakan Gorilla Sessions
+
+[Gorilla Sessions](https://github.com/gorilla/sessions) adalah library untuk manajemen session di golang.
+
+Gorilla menyediakan interface `sessions.Store`, lewat interface ini kita bisa mengakses 3 buah method penting untuk manage session. Store sendiri adalah representasi dari media penyimpanan di back end, bisa berupa database, memory, atau lainnya. Objek store dibuat oleh library lain yang merupakan implementasi dari interface store itu sendiri.
+
+Kembali ke pembahasan mengenai store, 3 buah method yang dimaksud adalah berikut:
+
+ - Method `.Get(r *http.Request, name string) (*Session, error)`, mengembalikan objek session. Jika session yang dengan `name` yang dicari tidak ada, maka objek session baru dikembalikan.
+ - Method `.New(r *http.Request, name string) (*Session, error)`, mengembalikan objek session baru.
+ - Method `.Save(r *http.Request, w http.ResponseWriter, s *Session) error`, digunakan untuk menyimpan session baru.
+
+Dari ketiga method di-atas saya rasa cukup jelas sekilas bagaimana cara mengakses, membuat, dan menyimpan session.
+
+> Kita akan fokus membahas API milik interface `sessions.Store` dahulu, mengenai pembuatan store sendiri ada di pembahasan setelahnya.
+
+Lalu bagaimana dengan operasi hapus/delete? Seperti yang sudah dijelaskan sebelumnya, informasi session dipisah menjadi dua, pertama adalah SessionID yang disimpan di cookie, dan kedua adalah SessionData yang disimpan di back end. Cara untuk menghapus session adalah cukup dengan meng-expired-kan cookie yang menyimpan SessionID.
+
+Cookie merupakan salah satu header pada http request, operasi yang berhubungan dengan cookie pasti membutuhkan objek `http.Request` dan `http.ResponseWriter`. Jika menggunakan echo, kedua objek tersebut bisa diakses lewat objek http context `echo.Context`.
+
+## C.13.2. Membuat Objek Session Baru
+
+Berikut adalah contoh cara membuat session lewat store.
+
+```go
+e.GET("/set", func(c echo.Context) error {
+ session, _ := store.Get(c.Request(), SESSION_ID)
+ session.Values["message1"] = "hello"
+ session.Values["message2"] = "world"
+ session.Save(c.Request(), c.Response())
+
+ return c.Redirect(http.StatusTemporaryRedirect, "/get")
+})
+```
+
+Statement `store.Get()` mengembalikan dua objek dengan tipe `session.Session` dan `error`. Pemanggilan method ini memerlukan dua buah parameter untuk disisipkan, yaitu objek http request, dan nama/key SessionID yang disiapkan di konstanta `SESSION_ID`. Method `.Get()` ini akan selalu mengembalikan objek session, ada ataupun tidak ada session yang dicari, objek session tetap dikembalikan.
+
+> Pembuatan objek session baru bisa dilakukan lewat `store.New()` maupun `store.Get()`.
+
+Dari objek session, akses property mutable `.Values` untuk mengambil ataupun mengisi data session. Objek ini bertipe `map[interface{}]interface{}`, berarti SessionData yang akan disimpan juga harus memiliki identifier.
+
+Pada contoh di atas, dua buah data bertipe string disimpan, dengan identifier data yang juga string.
+
+ - SessionData `"hello"` disimpan dengan identifier adalah `message1`.
+ - SessionData `"world"` disimpan dengan identifier adalah `message2`.
+
+Cara menyimpan session adalah dengan memanggil method `.Save()` milik objek session, dengan parameter adalah http request dan response.
+
+## C.13.3. Mengakses SessionData
+
+SessionData diakses dari objek session, berikut merupakan contoh caranya.
+
+```go
+e.GET("/get", func(c echo.Context) error {
+ session, _ := store.Get(c.Request(), SESSION_ID)
+
+ if len(session.Values) == 0 {
+ return c.String(http.StatusOK, "empty result")
+ }
+
+ return c.String(http.StatusOK, fmt.Sprintf(
+ "%s %s",
+ session.Values["message1"],
+ session.Values["message2"],
+ ))
+})
+```
+
+Seperti yang sudah dibahas di atas, objek `session` kembalian `store.Get()` TIDAK akan pernah berisi `nil`. Ada atau tidak, objek session selalu dikembalikan.
+
+Dari objek session dilakukan pengecekan ada tidaknya SessionData, caranya dengan cara menghitung isi property `.Values` yang tipenya `map`. Jika isinya kosong maka session belum ada (atau mungkin ada hanya saja expired, atau bisa saja ada tapi invalid).
+
+Pada kode di atas, jika SessionData kosong maka string `empty result` ditampilkan ke layar. Sedangkan jika ada, maka kedua SessionData (message1 dan message2) diambil lalu ditampilkan.
+
+## C.13.4. Menghapus Session
+
+Cara menghapus session adalah dengan meng-expired-kan max age cookie-nya. Property max age bisa diakses lewat `session.Options.MaxAge`.
+
+```go
+e.GET("/delete", func(c echo.Context) error {
+ session, _ := store.Get(c.Request(), SESSION_ID)
+ session.Options.MaxAge = -1
+ session.Save(c.Request(), c.Response())
+
+ return c.Redirect(http.StatusTemporaryRedirect, "/get")
+})
+```
+
+Isi dengan `-1` agar expired, lalu simpan ulang kembali session-nya.
+
+## C.13.5. Session Store dan Context Clear Handler
+
+Session Store adalah representasi dari media tempat di mana data asli session disimpan. Gorilla menyediakan `CookieStore`, penyimpanan data asli pada store ini adalah juga di dalam cookie, namun di-encode dan di-enkripsi menggunakan [Securecookie](https://github.com/gorilla/securecookie).
+
+Selain CookieStore, ada banyak store lain yang bisa kita gunakan. Komunitas begitu baik telah menyediakan berbagai macam store berikut.
+
+ - [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
+ - [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
+ - [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
+ - [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
+ - [github.com/savaki/dynastore](https://github.com/savaki/dynastore) - DynamoDB on AWS (Official AWS library)
+ - [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
+ - [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
+ - [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
+ - [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
+ - [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster
+ - [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
+ - [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
+ - [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
+ - [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
+ - [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
+ - [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
+ - [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql
+ - [github.com/quasoft/memstore](https://github.com/quasoft/memstore) - In-memory implementation for use in unit tests
+ - [github.com/lafriks/xormstore](https://github.com/lafriks/xormstore) - XORM (MySQL, PostgreSQL, SQLite, Microsoft SQL Server, TiDB)
+
+Objek store dibuat sekali di awal (atau bisa saja berkali-kali di tiap handler, tergantung kebutuhan). Pada pembuatan objek store, umumya ada beberapa konfigurasi yang perlu disiapkan dan dua buah keys: authentication key dan encryption key.
+
+Dari objek store tersebut, dalam handler, kita bisa mengakses objek session dengan menyisipkan context http request. Silakan lihat kode berikut untuk lebih jelasnya. Store direpresentasikan oleh variabel objek `store`.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/gorilla/context"
+ "github.com/gorilla/sessions"
+ "github.com/labstack/echo"
+ "net/http"
+)
+
+const SESSION_ID = "id"
+
+func main() {
+ store := newMongoStore()
+
+ e := echo.New()
+
+ e.Use(echo.WrapMiddleware(context.ClearHandler))
+
+ e.GET("/set", func(c echo.Context) error {
+ session, _ := store.Get(c.Request(), SESSION_ID)
+ session.Values["message1"] = "hello"
+ session.Values["message2"] = "world"
+ session.Save(c.Request(), c.Response())
+
+ return c.Redirect(http.StatusTemporaryRedirect, "/get")
+ })
+
+ // ...
+```
+
+Sesuai dengan README Gorilla Session, library ini jika digabung dengan library lain selain gorilla mux, akan berpotensi menyebabkan memory leak. Untuk mengcover isu ini maka middleware `context.ClearHandler` perlu diregistrasikan. Middleware tersebut berada dalam library [Gorilla Context](https://github.com/gorilla/context).
+
+## C.13.6. Mongo DB Store
+
+Kita akan mempelajari pembuatan session store dengan media adalah mongo db. Sebelum kita mulai, ada dua library yang perlu di `go get`.
+
+ - [gopkg.in/mgo.v2](https://gopkg.in/mgo.v2)
+ - [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore)
+
+Library pertama, `mgo.v2` merupakan driver mongo db untuk golang. Koneksi dari golang ke mongodb akan kita buat lewat API library ini.
+
+Library kedua, merupakan implementasi dari interface `sessions.Store` untuk mongo db.
+
+Silakan kombinasikan semua koding yang sudah kita tulis di atas agar menjadi satu aplikasi. Lalu buat fungsi `newMongoStore()`.
+
+```go
+import (
+ "fmt"
+ "github.com/gorilla/context"
+ "github.com/kidstuff/mongostore"
+ "github.com/labstack/echo"
+ "gopkg.in/mgo.v2"
+ "log"
+ "net/http"
+ "os"
+)
+
+// ...
+
+func newMongoStore() *mongostore.MongoStore {
+ mgoSession, err := mgo.Dial("localhost:27123")
+ if err != nil {
+ log.Println("ERROR", err)
+ os.Exit(0)
+ }
+
+ dbCollection := mgoSession.DB("learnwebgolang").C("session")
+ maxAge := 86400 * 7
+ ensureTTL := true
+ authKey := []byte("my-auth-key-very-secret")
+ encryptionKey := []byte("my-encryption-key-very-secret123")
+
+ store := mongostore.NewMongoStore(
+ dbCollection,
+ maxAge,
+ ensureTTL,
+ authKey,
+ encryptionKey,
+ )
+ return store
+}
+```
+
+Statement `mgo.Dial()` digunakan untuk terhubung dengan mongo db server. Method dial mengembalikan dua objek, salah satunya adalah mgo session.
+
+> Pada saat pembuatan buku ini, penulis menggunakan mongo db server yang up pada port `27123`, silakan menyesuaikan connection string dengan credentials mongo db yang digunakan.
+
+Dari mgo session akses database lewat method `.DB()`, lalu akses collection yang ingin digunakan sebagai media penyimpanan data asli session lewat method `.C()`.
+
+Statement `mongostore.NewMongoStore()` digunakan untuk membuat mongo db store. Ada beberapa parameter yang diperlukan: objek collection mongo di atas, dan dua lagi lainnya adalah authentication key dan encryption key.
+
+Jika pembaca merasa bingung, silakan langsung buka [source code untuk chapter ini di Github](https://github.com/novalagung/dasarpemrogramangolang-example/), mungkin membantu.
+
+## C.13.7. Postgres SQL Store
+
+Pembuatan postgres store caranya kurang lebih sama dengan mongo store. Library yang dipakai adalah [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore).
+
+Gunakan `pgstore.NewPGStore()` untuk membuat store. Isi parameter pertama dengan connection string postgres server, lalu authentication key dan encryption key.
+
+```go
+import (
+ "fmt"
+ "github.com/antonlindstrom/pgstore"
+ "github.com/gorilla/context"
+ "github.com/labstack/echo"
+ "log"
+ "net/http"
+ "os"
+)
+
+// ...
+
+func newPostgresStore() *pgstore.PGStore {
+ url := "postgres://novalagung:@127.0.0.1:5432/novalagung?sslmode=disable"
+ authKey := []byte("my-auth-key-very-secret")
+ encryptionKey := []byte("my-encryption-key-very-secret123")
+
+ store, err := pgstore.NewPGStore(url, authKey, encryptionKey)
+ if err != nil {
+ log.Println("ERROR", err)
+ os.Exit(0)
+ }
+
+ return store
+}
+```
+
+## C.13.8. Secure Cookie Store
+
+Penggunaan cookie store kurang penulis anjurkan, meski sebenarnya cukup aman. Implementasi store jenis ini adalah yang paling mudah, karena tidak butuh database server atau media lainnya; dan juga karena API untuk cookie store sudah tersedia dalam gorilla sessions secara default.
+
+```go
+import (
+ "fmt"
+ "github.com/gorilla/context"
+ "github.com/gorilla/sessions"
+ "github.com/labstack/echo"
+ "net/http"
+)
+
+// ...
+
+func newCookieStore() *sessions.CookieStore {
+ authKey := []byte("my-auth-key-very-secret")
+ encryptionKey := []byte("my-encryption-key-very-secret123")
+
+ store := sessions.NewCookieStore(authKey, encryptionKey)
+ store.Options.Path = "/"
+ store.Options.MaxAge = 86400 * 7
+ store.Options.HttpOnly = true
+
+ return store
+}
+```
+
+Tentukan path dan default max age cookie lewat `store.Options`.
+
+## C.13.9. Test Aplikasi
+
+Silakan gabung semua kode yang sudah kita pelajari (kecuali bagian store), lalu pilih salah satu implementasi store di atas. Jalankan aplikasi untuk testing.
+
+Tujuan dari kode yang kita tulis kurang lebih sebagai berikut.
+
+ 1. Ketika `/get` diakses untuk pertama kali, `empty result` muncul, tidak ada data session yang disimpan sebelumnya.
+ 2. Rute `/set` diakses, lalu sebuah session disimpan, dari rute ini pengguna di-redirect ke `/get`, sebuah pesan muncul yang sumber datanya tak lain adalah dari session.
+ 3. Rute `/delete` diakses, session dihapus, lalu di-redirect lagi ke `/get`, pesan `empty result` muncul kembali karena session sudah tidak ada (dihapus).
+
+
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [Gorilla Sessions](https://github.com/gorilla/sessions), by Gorilla web toolkit team, BSD-3-Clause license
+ - [Gorilla Context](https://github.com/gorilla/context), by Gorilla web toolkit team, BSD-3-Clause license
+ - [Gorilla Securecookie](https://github.com/gorilla/securecookie), by Gorilla web toolkit team, BSD-3-Clause license
+ - [PG Store](https://github.com/antonlindstrom/pgstore), by Anton Lindström, MIT License
+ - [Mongo Store](https://github.com/kidstuff/mongostore), by Nguyễn Văn Cao Nguyên, BSD-3-Clause License
+ - [Mgo v2, Golang Mongo Driver](https://labix.org/mgo), by Gustavo Niemeyer, Simplified BSD License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 698ee9291151472c3a83852069cf715994159a20 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:51 +0700
Subject: [PATCH 125/522] New translations c-singleflight.md (English)
---
en/content-en/C-singleflight.md | 244 ++++++++++++++++++++++++++++++++
1 file changed, 244 insertions(+)
create mode 100644 en/content-en/C-singleflight.md
diff --git a/en/content-en/C-singleflight.md b/en/content-en/C-singleflight.md
new file mode 100644
index 000000000..260fc53e6
--- /dev/null
+++ b/en/content-en/C-singleflight.md
@@ -0,0 +1,244 @@
+# C.37. Singleflight
+
+Pada chapter ini kita akan belajar tentang pengaplikasian singleflight API.
+
+Singleflight berguna untuk men-*suppress* atau menekan pemanggilan fungsi yang duplikat hanya menjadi 1 saja. Pemanggilan fungsi yang duplikat di sini maksudnya adalah: blok fungsi atau statement yang dipanggil bersamaan oleh beberapa pengguna, dan pemanggilan tersebut menghasilkan output yang sama persis.
+
+Satu contoh yang relevan dengan pemanggilan fungsi duplikat adalah proses *generate report*. Untuk aplikasi dengan dataset yang masif, pastinya proses pembuatan report butuh waktu lebih lama, tidak instan dan tidak bisa *realtime*.
+
+Contoh: suatu ketika `user A` men-trigger *report generation*, di waktu yang hampir bersamaan `user B` juga men-trigger *report generation*, padahal report yg di-inginkan adalah sama persis dengan report `user A`, ini menjadikan 2 proses tersebut menjadi redundan. Idealnya cukup 1 proses saja yang jalan. Nah, di sinilah peran dari singleflight API.
+
+> Singleflight API masuk dalam dependensi [golang.org/x/sync/singleflight](https://pkg.go.dev/golang.org/x/sync/singleflight).
+
+Kita akan memulai pembelajaran dengan membuat 1 buah contoh program sederhana yang mensimulasikan proses *generate report*. Setelahnya kita akan *tune* kode tersebut agar bisa mengantisipasi pemanggilan fungsi duplikat/redundan.
+
+## C.37.1. Persiapan
+
+Siapkan sebuah web server API dengan isi satu buah router untuk method `GET` dengan endpoint path adalah `/api/report/download/{reportID}`.
+
+> Di sini kita gunakan `reportID` sebagai unique identifier report. Umumnya report perlu lebih banyak parameter identifier seperti date filter dan filter lainnya. Tapi pada contoh berikut kita hanya akan pakai `reportID`.
+
+Silakan gunakan router library yg cocok di hati. Pada contoh ini saya akan pakai [go-chi](https://github.com/go-chi/chi).
+
+```go
+package main
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "time"
+
+ chi "github.com/go-chi/chi/v5"
+)
+
+func main() {
+ r := chi.NewRouter()
+ r.Route("/api", func(r chi.Router) {
+ r.Get("/report/download/{reportID}", HandlerDownloadReport)
+ })
+
+ host := ":8080"
+ fmt.Printf("starting web server at %s \n", host)
+ http.ListenAndServe(host, r)
+}
+```
+
+Selanjutnya siapkan `HandlerDownloadReport`, yang isinya kurang lebih men-generate report sesuai `reportID`.
+
+```go
+func HandlerDownloadReport(w http.ResponseWriter, r *http.Request) {
+ reportID := chi.URLParam(r, "reportID")
+
+ // construct the report path
+ reportName := fmt.Sprintf("report-%s.txt", reportID)
+ path := filepath.Join(os.TempDir(), reportName)
+
+ // if the report is not exists, generate it first.
+ // otherwise, immediatelly download it
+ if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
+ log.Println("generate report", reportName, path)
+
+ // simulate long-running process to generate report
+ time.Sleep(5 * time.Second)
+
+ f, err := os.Create(path)
+ if err != nil {
+ f.Close()
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ f.Write([]byte("this is a report"))
+ f.Close()
+ }
+
+ // ...
+}
+```
+
+Dalam handler bisa dilihat, parameter `reportID` value-nya diambil, kemudian dijadikan bagian nama file report dengan format `report-{reportID}.txt`.
+
+Yang dilakukan setelahnya, kita cek file report tersebut, apakah sudah ada atau belum. Jika belum, maka generate dulu report nya.
+
+Di dalam blok kode *report generation* disimulasikan proses memakan waktu `5 detik`.
+
+Ok, sekarang tambahkan kode berikut:
+
+```go
+func HandlerDownloadReport(w http.ResponseWriter, r *http.Request) {
+ // ...
+
+ // open the file, download it
+ f, err := os.OpenFile(path, os.O_RDONLY, 0644)
+ if f != nil {
+ defer f.Close()
+ }
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ contentDisposition := fmt.Sprintf("attachment; filename=%s", reportName)
+ w.Header().Set("Content-Disposition", contentDisposition)
+
+ if _, err := io.Copy(w, f); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ http.Error(w, "", http.StatusBadRequest)
+}
+```
+
+File report yang sudah ter-generate, kita buka lagi dengan mode `READONLY`. Kemudian isi file tersebut di-copy ke objek `ResponseWriter` diiringi dengan header `Content-Disposition` yang di-set, agar response di-proses oleh browser sebagai operasi generate report.
+
+Ok, sekarang mari kita test.
+
+## C.37.2. Download File
+
+Pada contoh berikut penulis menggunakan command `curl` untuk mengetes endpoint. Endpoint yang sama di-call secara hampir bersamaan dari 2 terminal prompt berbeda.
+
+```bash
+curl -s -w "elapsed: %{time_total}s\n" -i -X GET http://localhost:8080/api/report/download/sales-2021-10
+```
+
+
+
+Ok, bisa dilihat, dua operasi `curl` yang hampir bersamaan, masing-masing butuh minimal 5 detik untuk selesai, ini karena kita set proses generate report 5 detik.
+
+Sedangkan pada bagian web server log, terlihat report di-generate 2x padahal report tersebut ekuivalen atau sama persis.
+
+Coba jalankan lagi, harusnya pada api call berikut response akan sangat cepat, karena report sudah terbuat.
+
+
+
+Ok, mantab, hanya butuh 0,xxx detik untuk mengunduh report. Ok, sampai sini berarti sudah jelas bagian mana yang perlu di-*tune*, yaitu di bagian proses generate report concurrent-nya, agar tidak duplikat pengeksekusiannya untuk report yang sama di waktu eksekusi yang hampir bersamaan.
+
+## C.37.3. Pengaplikasian `singleflight` API
+
+Sekarang coba duplikat kode sebelumnya pada file baru, lalu tambahkan modifikasi berikut.
+
+Pertama import dulu dependensi-nya. Lalu siapkan variabel baru dengan tipe `singleflight.Group`.
+
+> Jangan lupa `go get -u golang.org/x/sync/singleflight`
+
+```go
+package main
+
+import (
+ // ...
+ "golang.org/x/sync/singleflight"
+)
+
+var singleflightGroupDownloadReport singleflight.Group
+```
+
+Variabel `singleflightGroupDownloadReport` akan kita gunakan untuk men-*suppress* duplicate call terhadap kode generate report.
+
+Selanjutnya, modifikasi isi handler antara statement `path := ...` dan statement `f, err := ...`
+
+```go
+func HandlerDownloadReport(w http.ResponseWriter, r *http.Request) {
+ // ...
+ path := filepath.Join(os.TempDir(), reportName)
+
+ sharedProcessKey := fmt.Sprintf("generate %s", reportName)
+ _, err, shared := singleflightGroupDownloadReport.Do(sharedProcessKey, func() (interface{}, error) {
+ // if the report is not exists, generate it first.
+ // otherwise, immediatelly download it
+ if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
+ log.Println("generate report", reportName, path)
+
+ // simulate long-running process to generate report
+ time.Sleep(5 * time.Second)
+
+ f, err := os.Create(path)
+ if err != nil {
+ f.Close()
+ return nil, err
+ }
+
+ f.Write([]byte("this is a report"))
+ f.Close()
+ }
+
+ return true, nil
+ })
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if shared {
+ log.Printf("generation of report %v is shared with others", reportName)
+ }
+
+ // open the file, download it
+ f, err := os.OpenFile(path, os.O_RDONLY, 0644)
+ // ...
+}
+```
+
+Pada kode di atas, kurang lebih modifikasi yang ditambahkan hanya membungkus kode untuk generate report dengan `singleflightGroupDownloadReport.Do()`.
+
+Variabel `singleflightGroupDownloadReport` kita siapkan sebagai variabel global, yang berarti *shared* dan *accessible* pada setiap API call.
+
+Kegunaan `singleflightGroupDownloadReport.Do()` adalah semua statement yang berada di dalamnya akan dipanggil 1x saja meskipun kode dipanggil banyak kali secara hampir bersamaan, dengan ketentuan data `key` yang disisipkan pada parameter pertama itu sama. Misalnya, jika ada 10 api call untuk generate report yang sama, `report-sales-2021-10.txt`, maka di sisi backend proses generate report hanya akan terjadi sekali.
+
+Melanjutkan pembahasan pada contoh yang dijelaskan, berarti dari 10 api call hanya 1 request yang benar-benar men-generate report. Lalu bagaimana dengan 9 api call yang lain. Yang terjadi adalah request yang lain juga akan menunggu hingga `singleflightGroupDownloadReport.Do()` selesai dieksekusi, hanya saja tanpa mengeksekusi kode di dalam `singleflightGroupDownloadReport.Do()`.
+
+Agar lebih jelas, mari kita test saja.
+
+## C.37.4. Generate report dengan `singleflight` API
+
+```bash
+curl -s -w "elapsed: %{time_total}s\n" -i -X GET http://localhost:8080/api/report/download/sales-2021-09
+```
+
+
+
+Bisa dilihat pada gambar di atas, log `generate report report-sales....` hanya dipanggil sekali meskipun ada dua concurrent api call ke endpoint tersebut. Ini tandanya fungsi untuk generate `report-sales-2021-08.txt` di-suppress agar tidak redundant.
+
+Log setelahnya, yaitu `generation of report report-sales-2021-08.txt is shared with others` hanya akan muncul jika ada lebih dari satu api call yang hampir bersamaan di waktu proses singleflight sedang berjalan. Variabel `shared` di situ menandakan bahwa proses di-share di api call lainnya.
+
+Cukup berguna bukan? Dengan adahnya singleflight API ini, beban backend akan sedikit diringankan. Proses yang sifatnya duplikat hanya akan dijalankan 1 saja dengan tanpa merusak flow proses.
+
+---
+
+ - [go-chi](https://github.com/go-chi/chi), by Peter Kieltyka, MIT license
+ - [Singleflight](https://pkg.go.dev/golang.org/x/sync/singleflight), by Go Team, BSD-3-Clause
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 2b8e1748921c1e5c9cbd6a8641222f4a5edb774a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:52 +0700
Subject: [PATCH 126/522] New translations c-write-pdf-file.md (English)
---
en/content-en/C-write-pdf-file.md | 96 +++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 en/content-en/C-write-pdf-file.md
diff --git a/en/content-en/C-write-pdf-file.md b/en/content-en/C-write-pdf-file.md
new file mode 100644
index 000000000..c553083cb
--- /dev/null
+++ b/en/content-en/C-write-pdf-file.md
@@ -0,0 +1,96 @@
+# C.20. Write PDF File (gofpdf)
+
+Reporting pada aplikasi web, selain ke bentuk file excel biasanya ke bentuk file pdf. Pada chapter ini kita akan mempelajari cara membuat file pdf di golang menggunakan [gofpdf](https://github.com/jung-kurt/gofpdf).
+
+**gofpdf** adalah library yang berguna untuk membuat dokumen PDF dari golang. Penggunannya tidak terlalu sulit. Jadi mari belajar sambil praktek seperti biasanya.
+
+## C.20.1. Membuat PDF Menggunakan gofpdf
+
+Pertama `go get` library-nya.
+
+```bash
+go get -u github.com/jung-kurt/gofpdf
+```
+
+Buat folder project baru, isi main dengan kode berikut.
+
+```go
+package main
+
+import (
+ "github.com/jung-kurt/gofpdf"
+ "log"
+)
+
+func main() {
+ pdf := gofpdf.New("P", "mm", "A4", "")
+ pdf.AddPage()
+ pdf.SetFont("Arial", "B", 16)
+ pdf.Text(40, 10, "Hello, world")
+ pdf.Image("./sample.png", 56, 40, 100, 0, false, "", 0, "")
+
+ err := pdf.OutputFileAndClose("./file.pdf")
+ if err != nil {
+ log.Println("ERROR", err.Error())
+ }
+}
+```
+
+Statement `gofpdf.New()` digunakan untuk membuat objek dokumen baru. Fungsi `.New()` tersebut membutuhkan 4 buah parameter.
+
+ 1. Parameter ke-1, orientasi dokumen, apakah portrait (`P`) atau landscape (`L`).
+ 2. Parameter ke-2, satuan ukuran yang digunakan, `mm` berarti milimeter.
+ 3. Parameter ke-3, ukuran dokumen, kira pilih A4.
+ 4. Parameter ke-4, path folder font.
+
+Fungsi `.New()` mengembalikan objek PDF. Dari situ kita bisa mengakses banyak method sesuai kebutuhan, beberapa di antaranya adalah 4 buah method yang dicontohkan di atas.
+
+#### • Method `.AddPage()`
+
+Method ini digunakan untuk menambah halaman baru. Defaultnya, objek dokumen yang baru dibuat tidak memiliki halaman. Dengan memanggil `.AddPage()` maka halaman baru dibuat.
+
+Setelah *at least* satu halaman tersedia, kita bisa lanjut ke proses tulis menulis.
+
+#### • Method `.SetFont()`
+
+Method ini digunakan untuk menge-set konfigurasi font dokumen. Font Family, Font Style, dan Font Size disisipkan dalam parameter secara berurutan.
+
+#### • Method `.Text()`
+
+Digunakan untuk menulis text pada koordinat tertentu. Pada kode di atas, `40` artinya `40mm` dari kiri, sedangkan `10` artinya `10mm` dari atas. Satuan milimeter digunakan karena pada saat penciptaan objek dipilih `mm` sebagai satuan.
+
+Method ini melakukan penulisan text pada current page.
+
+#### • Method `.Image()`
+
+Digunakan untuk menambahkan image. Method ini memerlukan beberapa parameter.
+
+- Parameter ke-1 adalah path image.
+- Paraketer ke-2 adalah x offset. Nilai `56` artinya `56mm` dari kiri.
+- Parameter ke-3 adalah y offset. Nilai `40` artinya `40mm` dari atas.
+- Parameter ke-4 adalah width gambar. Jika diisi dengan nilai lebih dari 0 maka gambar akan di-resize secara proporsional sesuai angka. Jika di-isi `0`, maka gambar akan muncul sesuai ukuran aslinya. Pada kode di atas, gambar `sample.png` digunakan, silakan gunakan gambar apa saja bebas.
+- Parameter ke-5 adalah height gambar.
+
+Sebenarnya masih banyak lagi method yang tersedia, selengkapnya cek saja di https://godoc.org/github.com/jung-kurt/gofpdf#Fpdf.
+
+Setelah selesai bermain dengan objek pdf, gunakan `.OutputFileAndClose()` untuk menyimpan hasil sebagai file fisik PDF.
+
+Coba jalankan aplikasi untuk melihat hasilnya. Buka generated file `file.pdf`, isinya kurang lebih seperti gambar berikut.
+
+
+
+
+---
+
+- [gofpdf](https://github.com/jung-kurt/gofpdf), by Kurt Jung, MIT license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From c06c96c7fdceea03dc39a0ed02738204b0bd9243 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:53 +0700
Subject: [PATCH 127/522] New translations c-xml-parser.md (English)
---
en/content-en/C-xml-parser.md | 283 ++++++++++++++++++++++++++++++++++
1 file changed, 283 insertions(+)
create mode 100644 en/content-en/C-xml-parser.md
diff --git a/en/content-en/C-xml-parser.md b/en/content-en/C-xml-parser.md
new file mode 100644
index 000000000..21c8df66b
--- /dev/null
+++ b/en/content-en/C-xml-parser.md
@@ -0,0 +1,283 @@
+# C.23. Parse & Generate XML (etree)
+
+Pada chapter ini kita akan belajar cara parsing file xml, dan cara membuat xml baru. Library yang digunakan adalah [etree](https://github.com/beevik/etree), silakan `go get` terlebih dahulu.
+
+```go
+go get -u github.com/beevik/etree
+```
+
+## C.23.1. Membaca dan Parsing File XML
+
+Mari langsung kita praktekan, siapkan folder project baru. Buat satu buah file `data.xml`, isinya sebagai berikut.
+
+```xml
+
+
+ Noval Agung
+ https://novalagung.com
+
+
+ Server
+ Connect to Oracle Server using Golang and Go-OCI8 on Ubuntu
+ /go-oci8-oracle-linux/
+
+
+ Server
+ Easy Setup OpenVPN Using Docker DockVPN
+ /easy-setup-openvpn-docker/
+
+
+ Server
+ Setup Ghost v0.11-LTS, Ubuntu, Nginx, Custom Domain, and SSL
+ /ghost-v011-lts-ubuntu-nginx-custom-domain-ssl/
+
+
+
+```
+
+Silakan perhatikan xml di atas, akan kita ambil semua element `article` beserta isinya, untuk kemudian ditampung dalam slice.
+
+Buat file main, di dalamnya, buat objek dokumen bertipe `etree.Document` lewat fungsi `etree.NewDocument()`. Dari objek tersebut, baca file xml yang sudah dibuat, gunakan method `.ReadFromFile()` untuk melakukan proses baca file.
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "github.com/beevik/etree"
+ "log"
+)
+
+type M map[string]interface{}
+
+func main() {
+ doc := etree.NewDocument()
+ if err := doc.ReadFromFile("./data.xml"); err != nil {
+ log.Fatal(err.Error())
+ }
+
+ // ...
+}
+```
+
+Dari objek `doc`, ambil root element ``, lalu akses semua element `` kemudian lakukan perulangan. Di dalam tiap perulangan, ambil informasi `title`, `url`, dan `category`, tampung sebagai element slice `rows`.
+
+```go
+root := doc.SelectElement("website")
+rows := make([]M, 0)
+
+for _, article := range root.FindElements("//article") {
+ row := make(M)
+ row["title"] = article.SelectElement("title").Text()
+ row["url"] = article.SelectElement("url").Text()
+
+ categories := make([]string, 0)
+ for _, category := range article.SelectElements("category") {
+ categories = append(categories, category.Text())
+ }
+ row["categories"] = categories
+
+ if info := article.SelectAttr("info"); info != nil {
+ row["info"] = info.Value
+ }
+
+ rows = append(rows, row)
+}
+```
+
+Objek element menyediakan beberapa method untuk keperluan seleksi dan pencarian element. Empat di antaranya sebagai berikut.
+
+ - Method `.SelectElement()`, untuk mengambil satu buah child element sesuai selector.
+ - Method `.SelectElements()`, sama seperti `.SelectElement()`, perbedannya yang dikembalikan adalah semua child elements (sesuai selector).
+ - Method `.FindElement()`, untuk mencari elements dalam current element, bisa berupa child, grand child, atau level yang lebih dalam, sesuai selector. Yang dikembalikan satu buah element saja.
+ - Method `.FindElements()`, sama seperti `.FindElement()`, perbedannya yang dikembalikan adalah semua elements (sesuai selector).
+
+Pada kode di atas, hasil dari statement `root.FindElements("//article")` di looping. Statement tersebut mengembalikan banyak element sesuai selector pencarian. Arti selector `//article` sendiri adalah melakukan pencarian element dengan nama `article` secara rekursif.
+
+Di tiap perulangan, child element `title` dan `url` diambil. Gunakan method `.Text()` untuk mengambil isi element.
+
+Sedangkan pada element `` pencarian child elements dilakukan menggunakan method `.SelectElements()`, karena beberapa artikel memiliki lebih dari satu category.
+
+Untuk mengakses value dari atribut, gunakan method `.SelectAttr()`.
+
+Setelah perulangan selesai, data artikel ada dalam objek `rows`. Tampilkan isinya sebagai JSON string.
+
+```go
+bts, err := json.MarshalIndent(rows, "", " ")
+if err != nil {
+ log.Fatal(err)
+}
+
+log.Println(string(bts))
+```
+
+Jalankan aplikasi, lihat hasilnya.
+
+
+
+## C.23.2. XML Query
+
+XQuery atau XML Query adalah bahasa query untuk pengolahan XML. Spesifikasinya bisa dilihat di https://www.w3.org/TR/xquery-31.
+
+Pada pembahasan di atas kita menggunakan query `//article` untuk melakukan pencarian semua element artikel secara rekursif.
+
+Berikut adalah contoh lain implementasi xquery yang lebih kompleks.
+
+```go
+popularArticleText := root.FindElement(`//article[@info='popular article']/title`)
+if popularArticleText != nil {
+ log.Println("Popular article", popularArticleText.Text())
+}
+```
+
+Penjelasan mengenai xquery `//article[@info='popular article']/title` dipecah menjadi 3 tahap agar mudah untuk dipahami.
+
+ 1. Selector bagian `//article`, artinya dilakukan pencarian rekursif dengan kriteria: element bernama `article`.
+ 2. Selector bagian `[@info='popular article']`, artinya dilakukan pencarian dengan kriteria: element memiliki atribut `info` yang berisi `popular article`.
+ 3. Selector bagian `/title`, artinya dilakukan pencarian child element dengan kriteria: element bernama `title`.
+
+Jika 3 penjelasan bagian di atas digabungkan, maka kurang lebih arti dari `//article[@info='popular article']/title` adalah, dilakukan pencarian secara rekursif dengan kriteria adalah: element bernama `article` dan harus memiliki atribut `info` yang berisi `popular article`, setelah diketemukan, dicari child element-nya menggunakan kriteria: element bernama `title`.
+
+Berikut adalah hasil dari query di atas.
+
+
+
+Silakan coba explore sendiri mengenai xquery untuk contoh lainnya.
+
+## C.23.3. Membuat XML dari Golang
+
+Di atas kita telah mempelajari cara baca XML; kali ini kita akan coba buat file XML menggunakan etree. Informasi yang akan ditulis ke file xml datanya bersumber dari JSON string (yang nantinya di-decode terlebih dahulu ke bentuk objek sebelum digunakan).
+
+Siapkan file baru, buat struct `Document`. Nantinya sebuah objek dicetak lewat struk ini, tugasnya sendiri adalah menampung data hasil proses decoding json.
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "github.com/beevik/etree"
+ "log"
+)
+
+type Document struct {
+ Title string
+ URL string
+ Content struct {
+ Articles []struct {
+ Title string
+ URL string
+ Categories []string
+ Info string
+ }
+ }
+}
+
+func main () {
+ // code here
+}
+```
+
+Siapkan JSON string.
+
+```go
+const jsonString = `{
+ "Title": "Noval Agung",
+ "URL": "https://novalagung.com",
+ "Content": {
+ "Articles": [{
+ "Categories": [ "Server" ],
+ "Title": "Connect to Oracle Server using Golang and Go-OCI8 on Ubuntu",
+ "URL": "/go-oci8-oracle-linux/"
+ }, {
+ "Categories": [ "Server", "VPN" ],
+ "Title": "Easy Setup OpenVPN Using Docker DockVPN",
+ "URL": "/easy-setup-openvpn-docker/"
+ }, {
+ "Categories": [ "Server" ],
+ "Info": "popular article",
+ "Title": "Setup Ghost v0.11-LTS, Ubuntu, Nginx, Custom Domain, and SSL",
+ "URL": "/ghost-v011-lts-ubuntu-nginx-custom-domain-ssl/"
+ }]
+ }
+}`
+```
+
+Decode JSON string di atas ke objek cetakan `Document`.
+
+```go
+data := Document{}
+err := json.Unmarshal([]byte(jsonString), &data)
+if err != nil {
+ log.Fatal(err.Error())
+}
+```
+
+Selanjutnya buat objek etree baru, siapkan root element `website`. Di dalamnya buat 2 child elements: `title` dan `url`, nilai masing-masing didapat dari objek `data`.
+
+```go
+doc := etree.NewDocument()
+doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
+
+website := doc.CreateElement("website")
+
+website.CreateElement("title").SetText(data.Title)
+website.CreateElement("url").SetText(data.URL)
+```
+
+Method `.CreateElement()` digunakan untuk membuat child element baru. Pemanggilannya disertai dengan satu parameter, yang merupakan representasi dari nama element yang ingin dibuat.
+
+Method `.SetText()` digunakan untuk menge-set nilai element.
+
+Siapkan satu element lagi di bawah root, namanya `contents`. Loop objek slice artikel, dan di tiap perulangannya, buat element dengan nama `article`, sisipkan sebagai child `contents`.
+
+```go
+content := website.CreateElement("contents")
+
+for _, each := range data.Content.Articles {
+ article := content.CreateElement("article")
+ article.CreateElement("title").SetText(each.Title)
+ article.CreateElement("url").SetText(each.URL)
+
+ for _, category := range each.Categories {
+ article.CreateElement("category").SetText(category)
+ }
+
+ if each.Info != "" {
+ article.CreateAttr("info", each.Info)
+ }
+}
+```
+
+Khusus untuk objek artikel yang property `.Info`-nya tidak kosong, buat atribut dengan nama `info` pada element `article` yang bersangkutan, simpan nilai property sebagai nilai atribut tersebut.
+
+Terakhir simpan objek dokumen etree sebagai file.
+
+```go
+doc.Indent(2)
+
+err = doc.WriteToFile("output.xml")
+if err != nil {
+ log.Println(err.Error())
+}
+```
+
+Method `.Indent()` di atas digunakan untuk menentukan indentasi element dalam file.
+
+Jalankan aplikasi, lihat hasilnya.
+
+
+
+---
+
+ - [etree](https://github.com/beevik/etree), by Brett Vickers, BSD-2-Clause license
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From 851c531bdd22560a8668a3c3f4ab8270bb0f5c05 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:54 +0700
Subject: [PATCH 128/522] New translations contributing.md (English)
---
en/content-en/CONTRIBUTING.md | 79 +++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 en/content-en/CONTRIBUTING.md
diff --git a/en/content-en/CONTRIBUTING.md b/en/content-en/CONTRIBUTING.md
new file mode 100644
index 000000000..c20782465
--- /dev/null
+++ b/en/content-en/CONTRIBUTING.md
@@ -0,0 +1,79 @@
+# Author & Contributors
+
+E-book Dasar Pemrograman Golang adalah proyek *open source*. Siapapun bebas untuk berkontribusi di sini, bisa dalam bentuk perbaikan *typo*, update kalimat, maupun submit tulisan baru. Bagi teman-teman yang berminat untuk berkontribusi, silakan ikuti petunjuk berikut:
+
+1. *Fork* https://github.com/novalagung/dasarpemrogramangolang.
+1. *Commit* perbaikan anda ke branch baru.
+1. Kemudian submit *pull request* ke GitHub kami. Silakan sertakan issue jika diperlukan.
+1. Kami akan me-review PR tersebut untuk kemudian di-*merge*.
+
+## Local Development
+
+Jalankan command berikut untuk run project ini di local environment:
+
+```bash
+npm install
+npm run serve
+```
+
+## Original Author
+
+E-book ini di-inisialisasi oleh [Noval Agung Prayogo](https://linktr.ee/novalagung).
+
+## Contributors
+
+Berikut merupakan *hall of fame* kontributor yang sudah berbaik hati menyisihkan waktunya untuk membantu pengembangan e-book ini.
+
+1. [Acep Saepudin](https://github.com/acepsaepudin)
+1. [Adev Saputra](https://github.com/adev22)
+1. [Agus Budiono](https://github.com/dyon048)
+1. [Ahmad Syafiq Aqil Wafi](https://github.com/Syafiqjos)
+1. [Akul Nurislamimanudin](https://github.com/akulnurislam)
+1. [Alfiyanto Kondolele](https://github.com/haloapping)
+1. [Amin Rasul Kamsena](https://github.com/seno-ark)
+1. [Ananda Wiradharma](https://github.com/anandawira)
+1. [Andreas Giovani](https://github.com/compatc)
+1. [Arian Saputra](https://github.com/Rhyanz46)
+1. [Burhanudin Yahya](https://github.com/burhanudinyahya)
+1. [Dipta Harimbawa](https://github.com/diptarimba)
+1. [Dwi Hujianto](https://github.com/dwihujianto)
+1. [Edi Santoso](https://github.com/repodevs)
+1. [Eky Pradhana](https://github.com/ekypradhana)
+1. [Fadhil Riyanto](https://github.com/fadhil-riyanto)
+1. [Faizar Septiawan](https://github.com/icarrr)
+1. [Fajar Islami](https://github.com/Fajar-Islami)
+1. [Febrian](https://github.com/febri4n)
+1. [Ganjar Gingin Tahyudin](https://github.com/zarszz)
+1. [Gusman Widodo](https://github.com/gusmanwidodo)
+1. [Hafiz Kurnia Aji](https://github.com/hafizkurniaaji)
+1. [I Gede Tirtanata](https://github.com/gedenata)
+1. [Ibnul Mutaki](https://github.com/cacing69)
+1. [Imam Ahmad Fahrezi](https://github.com/imamcenter)
+1. [Ivan Aulia Rahman](https://github.com/ivanauliaa)
+1. [Jono](https://github.com/josterand)
+1. [Kiswono Prayogo](https://github.com/kokizzu)
+1. [Lufri Rais Maulana](https://github.com/raismaulana)
+1. [M Rafi Raihandika](https://github.com/mananispiwpiw)
+1. [M. Ilham Syaputra](https://github.com/ilhamsyaputra)
+1. [Ma'mur Rezeki](https://github.com/erzqy)
+1. [MH Rohman Masyhar](https://github.com/rohmanhm)
+1. [Muhammad Faris 'Afif](https://github.com/muhfaris)
+1. [Muhammad Ridho](https://github.com/reedho)
+1. [Mulia Nasution](https://github.com/mul14)
+1. [nekonako](https://github.com/nekonako)
+1. [Nuevo Querto](https://github.com/NuevoQuerto)
+1. [Rico](https://github.com/goldsky)
+1. [Rizky Zhang](https://github.com/rizkyzhang)
+1. [Ryan Febriansyah](https://github.com/sodrooome)
+1. [Sal Prima](https://github.com/salprima)
+1. [Seno](https://github.com/seno-ark)
+1. [Shabrina](https://github.com/renaissains)
+1. [Sultan Naufal Abdillah](https://github.com/sultannaufal)
+1. [Teuku Mulia Ichsan](https://github.com/xans-me)
+1. [Tiara Dewangga](https://github.com/ktiarad)
+1. [Wanda Ichsanul Isra](https://github.com/wndisra)
+1. [Wahyu Kristianto](https://github.com/Kristories)
+1. [Widodo](https://github.com/purwowd)
+1. [Yofriadi Yahya](https://github.com/yofriadi)
+1. [Zulfikar Ali Muzakir](https://github.com/zulfikarmuzakir)
+1. ... anda :-)
From f465a8dfca91a5bad2d63bf1b0d36ba8b2193d72 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:55 +0700
Subject: [PATCH 129/522] New translations d-golang-web-socket-chatting-app.md
(English)
---
.../D-golang-web-socket-chatting-app.md | 423 ++++++++++++++++++
1 file changed, 423 insertions(+)
create mode 100644 en/content-en/D-golang-web-socket-chatting-app.md
diff --git a/en/content-en/D-golang-web-socket-chatting-app.md b/en/content-en/D-golang-web-socket-chatting-app.md
new file mode 100644
index 000000000..6c1ebeb17
--- /dev/null
+++ b/en/content-en/D-golang-web-socket-chatting-app.md
@@ -0,0 +1,423 @@
+# D.3. Web Socket: Chatting App
+
+Pada chapter ini kita akan belajar penerapan web socket di Go, untuk membuat sebuah aplikasi chatting. Web socket server dibuat menggunakan library [Gorilla Web Socket](https://github.com/gorilla/websocket), dan di sisi front end kita menggunakan native API milik javascript yaitu [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications) untuk melakukan komunikasi dengan socket server.
+
+> Jelasnya kapabilitas web socket bisa dicapai dengan cukup menggunakan default package yang disediakan Go. Namun pada chapter ini pembelajaran dilakukan menggunakan 3rd party library.
+
+Seperti biasanya proses belajar dilakukan sambil praktek. Kita buat aplikasi chatting minimalis, dengan kode se-sedikit mungkin agar mudah dipahami, development dilakukan *from scratch*.
+
+Nantinya saat testing akan ada banyak user terhubung dengan socket server, dalam satu room. Setiap pesan yang ditulis oleh salah seorang user, bisa dibaca oleh semua user lainnya.
+
+Kurang lebih aplikasi yang kita kembangkan seperti gambar di bawah ini.
+
+
+
+## D.3.1. Back End
+
+Buat folder project baru.
+
+```bash
+mkdir chapter-d3
+cd chapter-d3
+go mod init chapter-d3
+
+go get -u github.com/gorilla/websocket@v1.4.1
+go get -u github.com/novalagung/gubrak/v2
+```
+
+Siapkan dua buah file, `main.go` dan `index.html`. Kita akan buat socket server terlebih dahulu. Silakan tulis kode berikut ke dalam `main.go`.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/gorilla/websocket"
+ gubrak "github.com/novalagung/gubrak/v2"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "strings"
+)
+
+type M map[string]interface{}
+
+const MESSAGE_NEW_USER = "New User"
+const MESSAGE_CHAT = "Chat"
+const MESSAGE_LEAVE = "Leave"
+
+var connections = make([]*WebSocketConnection, 0)
+```
+
+Konstanta dengan prefix `MESSAGE_*` adalah representasi dari jenis message yang dikirim dari socket server ke semua client (yang terhubung).
+
+ - Konstanta `MESSAGE_NEW_USER`. Ketika ada user baru terhubung ke room, maka sebuah pesan **User XXX: connected** akan muncul. Konstanta ini digunakan oleh socket server dalam mem-broadcast informasi tersebut.
+ - Konstanta `MESSAGE_CHAT`. Ketika user/client mengirim message/pesan ke socket server, message tersebut kemudian diteruskan ke semua client lainnya oleh socket server. Isi pesan di-broadcast oleh socket server ke semua user yang terhubung menggunakan konstanta ini.
+ - Konstanta `MESSAGE_LEAVE`. Digunakan oleh socket server untuk menginformasikan semua client lainnya, bahwasanya ada client yang keluar dari room (terputus dengan socket server). Pesan **User XXX: disconnected** dimunculkan.
+
+Selain 3 konstanta di atas, ada variabel `connections`. Variabel ini digunakan untuk menampung semua client yang terhubung ke socket server.
+
+OK, setelah kode di atas ditulis, siapkan tiga buah struct berikut.
+
+ - Struct `SocketPayload`, digunakan untuk menampung payload yang dikirim dari front end.
+
+ ```go
+ type SocketPayload struct {
+ Message string
+ }
+ ```
+
+ - Struct `SocketResponse`, digunakan oleh back end (socket server) sewaktu mem-broadcast message ke semua client yang terhubung. Field `Type` akan berisi salah satu dari konstanta dengan prefix `MESSAGE_*`.
+
+ ```go
+ type SocketResponse struct {
+ From string
+ Type string
+ Message string
+ }
+ ```
+
+ - Struct `WebSocketConnection`. Nantinya setiap client yang terhubung, objek koneksi-nya disimpan ke slice `connections` yang tipenya adalah `[]*WebSocketConnection`.
+
+ ```go
+ type WebSocketConnection struct {
+ *websocket.Conn
+ Username string
+ }
+ ```
+
+Selanjutnya buat fungsi `main()`, siapkan satu buah rute, `/`, isinya menampilkan template view `index.html`. Siapkan juga rute `/ws` yang akan menjadi gateway komunikasi socket.
+
+```go
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ content, err := ioutil.ReadFile("index.html")
+ if err != nil {
+ http.Error(w, "Could not open requested file", http.StatusInternalServerError)
+ return
+ }
+
+ fmt.Fprintf(w, "%s", content)
+ })
+
+ http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
+ // socket code here
+ })
+
+ fmt.Println("Server starting at :8080")
+ http.ListenAndServe(":8080", nil)
+}
+```
+
+Handler `/ws` diisi dengan proses untuk konversi koneksi HTTP ke koneksi web socket. Statement `websocket.Upgrade()` digunakan untuk ini. Pada statement tersebut, parameter ke-4 adalah besar read buffer, sedangkan parameter ke-5 adalah besar write buffer.
+
+```go
+http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
+ currentGorillaConn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
+ if err != nil {
+ http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
+ }
+
+ username := r.URL.Query().Get("username")
+ currentConn := WebSocketConnection{Conn: currentGorillaConn, Username: username}
+ connections = append(connections, ¤tConn)
+
+ go handleIO(¤tConn, connections)
+})
+```
+
+Di sisi client, ketika inisialisasi koneksi web socket, informasi **username** disisipkan sebagai query string. Lalu di back end diambil untuk ditempelkan ke objek koneksi socket (yang kemudian dimasukan ke `connections`).
+
+```js
+app.ws = new WebSocket("ws://localhost:8080/ws?username=" + name)
+```
+
+Objek `currentGorillaConn` yang merupakan objek *current* koneksi web server, kita cast ke tipe `WebSocketConnection`, kemudian ditampung ke `currentConn`. Informasi username di atas ditambahkan sebagai identifier dalam objek tersebut.
+
+Slice `connections` menampung semua koneksi web socket, termasuk `currentConn`.
+
+Di akhir handler, fungsi `handleIO()` dipanggil sebagai sebuah goroutine, dalam pemanggilannya objek `currentConn` dan `connections` disisipkan. Tugas fungsi `handleIO()` ini adalah untuk me-manage komunikasi antara client dan server. Proses broadcast message ke semua client yg terhubung dilakukan dalam fungsi ini.
+
+Berikut adalah isi fungsi `handleIO()`.
+
+```go
+func handleIO(currentConn *WebSocketConnection, connections []*WebSocketConnection) {
+ defer func() {
+ if r := recover(); r != nil {
+ log.Println("ERROR", fmt.Sprintf("%v", r))
+ }
+ }()
+
+ broadcastMessage(currentConn, MESSAGE_NEW_USER, "")
+
+ for {
+ payload := SocketPayload{}
+ err := currentConn.ReadJSON(&payload)
+ if err != nil {
+ if strings.Contains(err.Error(), "websocket: close") {
+ broadcastMessage(currentConn, MESSAGE_LEAVE, "")
+ ejectConnection(currentConn)
+ return
+ }
+
+ log.Println("ERROR", err.Error())
+ continue
+ }
+
+ broadcastMessage(currentConn, MESSAGE_CHAT, payload.Message)
+ }
+}
+```
+
+Ketika koneksi terjalin untuk pertama kalinya, antara socket client dan socket server, fungsi `broadcastMessage()` dipanggil. Semua client yang terhubung (kecuali `currentConn`) dikirimi pesan dengan jenis `MESSAGE_NEW_USER`, menginformasikan bahwa ada user baru terhubung ke room.
+
+Selanjutnya, ada perulangan tanpa henti. Statement `currentConn.ReadJSON()` dalam loop adalah blocking. Statement tersebut hanya akan tereksekusi ketika ada payload (berupa message/pesan) dikirim dari socket client. Payload tersebut diterima oleh socket server, kemudian di-broadcast ke semua client yang terhubung (kecuali `currentConn`) dengan jenis message terpilih adalah `MESSAGE_CHAT`. Data message sendiri disisipkan sebagai parameter ke-3 pemanggilan `broadcastMessage()`.
+
+Ketika ada client terputus koneksinya dengan socket server, method `.ReadJSON()` otomatis terpanggil, namun tidak melakukan apa-apa dan **pasti** mengembalikan error. Berikut adalah error message-nya.
+
+```
+websocket: close 1001 (going away)
+```
+
+Error di atas adalah indikator bahwa *current* client terputus koneksinya dengan socket server. Ketika hal ini terjadi, maka akan ada message yang di-broadcast ke semua client yang terhubung (kecuali `currentConn`) dengan jenis message adalah `MESSAGE_LEAVE`, untuk menginformasikan bahwa ada user (yaitu `currentConn`) yang leave room. Tak lupa, objek `currentConn` dikeluarkan dari slice `connections` lewat fungsi `ejectConnection()`.
+
+Berikut adalah deklarasi fungsi `ejectConnection()` dan `broadcastMessage()`.
+
+ - Fungsi `ejectConnection()`:
+
+ ```go
+ func ejectConnection(currentConn *WebSocketConnection) {
+ filtered := gubrak.From(connections).Reject(func(each *WebSocketConnection) bool {
+ return each == currentConn
+ }).Result()
+ connections = filtered.([]*WebSocketConnection)
+ }
+ ```
+
+ - Fungsi `broadcastMessage()`:
+
+ ```go
+ func broadcastMessage(currentConn *WebSocketConnection, kind, message string) {
+ for _, eachConn := range connections {
+ if eachConn == currentConn {
+ continue
+ }
+
+ eachConn.WriteJSON(SocketResponse{
+ From: currentConn.Username,
+ Type: kind,
+ Message: message,
+ })
+ }
+ }
+ ```
+
+Method `.WriteJSON()` milik `websocket.Conn` digunakan untuk mengirim data dari socket server ke socket client (yang direpresentasikan oleh `eachConn`). Dalam fungsi `broadcastMessage()` di atas, semua client yang terhubung dikirimi data (sesuai parameter), terkecuali untuk current client.
+
+Bagian back end sudah cukup. Sekarang lanjut ke layer front end.
+
+## D.3.2. Front End
+
+Siapkan terlebih dahulu basis layout front end. Ada dua section penting yg harus disiapkan.
+
+ 1. Sebuah div dengan ukuran besar, nantinya diisi dengan message yang dikirim oleh current client dan client lainnya.
+ 2. Sebuah form dengan isi satu inputan text dan satu button. Nantinya user menulis pesan yang ingin di-broadcast ke inputan text, lalu klik button untuk submit message tersebut.
+
+Kurang lebih kode-nya seperti berikut.
+
+```html
+
+
+
+ WebSocket
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Tambahkan beberapa stylesheet agar terlihat cantik.
+
+```css
+.form {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background-color: #f9f9f9;
+ border-top: 1px solid #78b8ef;
+ padding: 5px 10px;
+}
+.form .placeholder, .form .input-message, .form button {
+ display: block;
+ margin-bottom: 5px;
+}
+.form .input-message {
+ padding: 7px;
+ border: 1px solid #ecebeb;
+ border-radius: 4px;
+ width: -webkit-fill-available;
+}
+.form button {
+ width: 100%;
+ color: white;
+ padding: 7px 10px;
+ border-radius: 4px;
+ background-color: #78b8ef;
+ border: 1px solid #5a9ed8;
+}
+.container { margin-bottom: 50px; }
+.container p { display: block; }
+```
+
+Tampilan sekilas aplikasi bisa dilihat pada gambar di bawah ini.
+
+
+
+OK, sekarang saatnya masuk ke bagian yang paling disukai anak jaman now (?), yaitu javascript. Siapkan beberapa property, satu untuk menampung objek client socket server, dan satu lagi menampung element container (element inilah yang nantinya akan diisi message yang di-broadcast oleh server).
+
+```js
+var app = {}
+app.ws = undefined
+app.container = undefined
+
+app.init = function () {
+ if (!(window.WebSocket)) {
+ alert('Your browser does not support WebSocket')
+ return
+ }
+
+ var name = prompt('Enter your name please:') || "No name"
+ document.querySelector('.username').innerText = name
+
+ app.container = document.querySelector('.container')
+
+ app.ws = new WebSocket("ws://localhost:8080/ws?username=" + name)
+
+ // ...
+}
+
+window.onload = app.init
+```
+
+Fungsi `app.init()` dipanggil pada event `window.onload`.
+
+Di saat pertama kali page load, muncul prompt yang meminta inputan nama user. Nantinya user yang diinput dijadikan sebagai *current* username pada aplikasi chatting ini.
+
+
+
+Property `app.ws` digunakan untuk menampung objek client web socket. Dari objek tersebut, buat 3 buah event listener. Tulis deklarasi event-nya dalam `app.init`.
+
+ - Event `onopen`. Event ini dieksekusi ketika *current* socket client berhasil terhubung dengan socket server.
+
+ ```js
+ app.ws.onopen = function() {
+ var message = 'me: connected'
+ app.print(message)
+ }
+ ```
+
+ - Event `onmessage`. Event ini terpanggil ketika socket server mengirim data dan diterima oleh masing-masing socket client. Di dalam event init, message yang di-broadcast oleh socket server ditampilkan sesuai jenis message-nya, apakah `New User`, `Leave`, atau `Chat`.
+
+ ```js
+ app.ws.onmessage = function (event) {
+ var res = JSON.parse(event.data)
+
+ var messsage = ''
+ if (res.Type === 'New User') {
+ message = 'User ' + res.From + ': connected'
+ } else if (res.Type === 'Leave') {
+ message = 'User ' + res.From + ': disconnected'
+ } else {
+ message = '' + res.From + ': ' + res.Message
+ }
+
+ app.print(message)
+ }
+ ```
+
+ - Event `onclose`. Seperti yang sudah disinggung di atas, ketika koneksi *current* socket terputus dengan server, event ini terpanggil secara otomatis.
+
+ ```js
+ app.ws.onclose = function () {
+ var message = 'me: disconnected'
+ app.print(message)
+ }
+ ```
+
+Kemudian tambahkan fungsi `app.print()`, fungsi ini digunakan untuk mencetak pesan ke `.container`.
+
+```js
+app.print = function (message) {
+ var el = document.createElement("p")
+ el.innerHTML = message
+ app.container.append(el)
+}
+```
+
+Dan fungsi `app.doSendMessage()`, fungsi ini digunakan untuk mengirim payload message (inputan user) ke socket server.
+
+```js
+app.doSendMessage = function () {
+ var messageRaw = document.querySelector('.input-message').value
+ app.ws.send(JSON.stringify({
+ Message: messageRaw
+ }));
+
+ var message = 'me: ' + messageRaw
+ app.print(message)
+
+ document.querySelector('.input-message').value = ''
+}
+```
+
+OK, aplikasi sudah siap, mari lanjut ke bagian testing.
+
+## D.3.3. Testing
+
+Buka beberapa tab, gunakan username apa saja di masing-masing tab. Coba berinteraksi satu sama lain.
+
+
+
+Bisa dilihat, ketika ada user baru, semua client yang sudah terhubung mendapat pesan **User XXX: connected**.
+
+Pesan yang ditulis oleh satu client bisa dilihat oleh client lainnya.
+
+Ketika salah satu user leave, pesan **User XXX: disconnected** akan di-broadcast ke semua user lainnya. Pada gambar di bawah ini dicontohkan user **Noval Agung** leave.
+
+
+
+---
+
+ - [Gorilla Web Socket](https://github.com/gorilla/websocket), by Gary Burd, BSD-2-Clause License
+ - [Gubrak v2](https://github.com/novalagung/gubrak), by Noval Agung, MIT License
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From db5ce901f4d301515293c1388311675bd487850d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:56 +0700
Subject: [PATCH 130/522] New translations d-google-api-search.md (English)
---
en/content-en/D-google-api-search.md | 331 +++++++++++++++++++++++++++
1 file changed, 331 insertions(+)
create mode 100644 en/content-en/D-google-api-search.md
diff --git a/en/content-en/D-google-api-search.md b/en/content-en/D-google-api-search.md
new file mode 100644
index 000000000..1ed19c49e
--- /dev/null
+++ b/en/content-en/D-google-api-search.md
@@ -0,0 +1,331 @@
+# D.2. Google API Search Dengan Timeout
+
+Pada chapter ini kita akan mencoba studi kasus yaitu membuat web service API untuk *wrap* pencarian ke Google Search API.
+
+Proses pembelajaran dilakukan dengan praktek membuat sebuah aplikasi web service kecil, yang tugasnya melakukan pencarian data. Nantinya akan dibuat juga middleware `MiddlewareUtility`, tugasnya menyisipkan informasi origin dispatcher request, ke dalam context request, sebelum akhirnya sampai pada handler endpoint yg sedang diakses.
+
+## D.2.1. Context Value
+
+Ok, langsung saja, siapkan folder project baru dengan struktur seperti berikut.
+
+```bash
+mkdir chapter-d2
+cd chapter-d2
+go mod init chapter-d2
+
+# then prepare underneath structures
+tree .
+.
+├── main.go
+└── middleware.go
+
+0 directories, 2 files
+```
+
+Pada file `middleware.go` isi dengan `CustomMux` yang pada pembahasan-pembahasan sebelumnya sudah pernah kita gunakan.
+
+```go
+package main
+
+import "net/http"
+
+type CustomMux struct {
+ http.ServeMux
+ middlewares []func(next http.Handler) http.Handler
+}
+
+func (c *CustomMux) RegisterMiddleware(next func(next http.Handler) http.Handler) {
+ c.middlewares = append(c.middlewares, next)
+}
+
+func (c *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ var current http.Handler = &c.ServeMux
+
+ for _, next := range c.middlewares {
+ current = next(current)
+ }
+
+ current.ServeHTTP(w, r)
+}
+```
+
+Lalu pada file `main.go`, buat satu buah endpoint `/api/search`, dengan isi handler menampilkan data `from` yang diambil dari request context. Data `from` ini di set ke dalam request context oleh middleware `MiddlewareUtility`.
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+)
+
+type M map[string]interface{}
+
+func main() {
+ mux := new(CustomMux)
+ mux.RegisterMiddleware(MiddlewareUtility)
+
+ mux.HandleFunc("/api/search", func(w http.ResponseWriter, r *http.Request) {
+ from := r.Context().Value("from").(string)
+ w.Write([]byte(from))
+ })
+
+ server := new(http.Server)
+ server.Handler = mux
+ server.Addr = ":80"
+
+ fmt.Println("Starting server at", server.Addr)
+ server.ListenAndServe()
+}
+```
+
+Cara mengakses context request adalah lewat method `.Context()` milik objek request. Lalu chain dengan method `Value()` untuk mengambil data sesuai dengan key yang disisipkan pada parameter.
+
+Untuk sekarang, tugas dari endpoint `/api/search` hanya menampilkan data tersebut, tidak lebih.
+
+Selanjutnya siapkan middleware `MiddlewareUtility`. Di dalamnya, ada pengecekan header `Referer`, jika ada maka dijadikan value data `from` (yang kemudian disimpan pada context); sedangkan jika tidak ada maka value-nya berasal dari property `.Host` milik objek request.
+
+```go
+func MiddlewareUtility(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ from := r.Header.Get("Referer")
+ if from == "" {
+ from = r.Host
+ }
+
+ ctx = context.WithValue(ctx, "from", from)
+
+ requestWithContext := r.WithContext(ctx)
+ next.ServeHTTP(w, requestWithContext)
+ })
+}
+```
+
+Objek request context bisa didapat lewat pengaksesan method `.Context()` milik `*http.Request`. Objek ini bertipe `context.Context` dengan zero type adalah `nil`.
+
+Pada kode di atas, jika context adalah `nil`, maka di-inisialisasi dengan context baru lewat `context.Background()`.
+
+Objek `ctx` yang merupakan `context.Context` di sini kita tempeli data `from`. Cara melakukannya dengan memanggil statement `context.WithValue()` dengan disisipi 3 buah parameter.
+
+ 1. Parameter ke-1, isinya adalah objek context.
+ 2. Parameter ke-2, isinya key dari data yang akan disimpan.
+ 3. Parameter ke-3, adalah value data yang akan disimpan.
+
+Fungsi `.WithValue()` di atas mengembalikan objek context, isinya adalah objek context yang disisipkan di parameter pertama pemanggilan fungsi, tapi sudah disisipi data dengan key dari parameter ke-2 dan value dari parameter ke-3. Jadi tampung saja objek context kembalian statement ini ke objek yang sama, yaitu `ctx`.
+
+Ok, sekarang objek `ctx` sudah dimodifikasi. Objek ini perlu untuk ditempelkan lagi ke objek request. Caranya dengan mengakses method `.WithContext()` milik objek request, lalu gunakan nilai baliknya pada `next.ServeHTTP()`.
+
+Jalankan aplikasi, hasilnya kurang lebih seperti gambar berikut.
+
+
+
+O iya, penulis tidak menggunakan `http://localhost` untuk mengakses aplikasi, melainkan menggunakan `http://mysampletestapp.com`, dengan catatan domain ini sudah saya arahkan ke 127.0.0.1.
+
+
+
+Ok, untuk sekarang sepertinya cukup jelas mengenai penggunaan context pada objek http request. Tinggal kembangkan saja sesuai kebutuhan, seperti contohnya: context untuk menyimpan data session, yang diambil dari database sessuai dengan session id nya.
+
+## D.2.2. Context Timeout & Cancellation
+
+Melanjutkan program yang sudah dibuat, nantinya pada endpoint `/api/search` akan dilakukan sebuah pencarian ke Google sesuai dengan keyword yang diinginkan. Pencarian dilakukan dengan memanfaatkan [Custom Search JSON API](https://developers.google.com/custom-search/json-api/v1/overview) milik Google.
+
+Sekarang ubah isi handler endpoint tersebut menjadi seperti berikut.
+
+```go
+mux.HandleFunc("/api/search", func(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+
+ keyword := r.URL.Query().Get("keyword")
+ chanRes := make(chan []byte)
+ chanErr := make(chan error)
+
+ go doSearch(ctx, keyword, chanRes, chanErr)
+
+ select {
+ case res := <-chanRes:
+ w.Header().Set("Content-type", "application/json")
+ w.Write(res)
+ case err := <-chanErr:
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+})
+```
+
+Proses pencarian dilakukan secara *asynchronous* lewat fungsi `doSearch()` yang nantinya akan kita buat. Pemanggilannya menggunakan keyword `go` dan disisipkan beberapa parameter yang dua di antaranya bertipe channel.
+
+ - Channel `chanRes`, digunakan jika proses pencarian sukses. Data hasil pencarian dilempar ke main routine lewat channel ini, untuk kemudian diteruskan sebagai response endpoint
+ - Channel `chanErr`, digunakan untuk pass objek error, jika memang terjadi error.
+
+Ok, lanjut, siapkan dua buah konstanta baru.
+
+ - Konstanta `SEARCH_MAX_DURATION`, digunakan untuk menge-set max response time. Jika melebihi, maka request langsung di-cancel.
+ ```go
+ var SEARCH_MAX_DURATION = 4 * time.Second
+ ```
+
+ - Konstanta `GOOGLE_SEARCH_API_KEY`, ini merupakan API key yang diperlukan dalam penggunaan Custom Search API. Silakan merujuk ke laman [Google Cloud Platform](https://console.cloud.google.com/apis/dashboard) untuk mendapatkan API key.
+ ```go
+ var GOOGLE_SEARCH_API_KEY = "ASSVnHfjD_ltXXXXSyB6WWWWWWWWveMFgE"
+ ```
+
+Sekarang kita mulai masuk ke bagian pembuatan fungsi pencarian `doSearch()`. Silakan tulis kode berikut.
+
+```go
+func doSearch(
+ ctx context.Context,
+ keyword string,
+ chanRes chan []byte,
+ chanErr chan error,
+) {
+ innerChanRes := make(chan []byte)
+ innerChanErr := make(chan error)
+
+ url := "https://www.googleapis.com/customsearch/v1"
+ url = fmt.Sprintf("%s?key=%s", url, GOOGLE_SEARCH_API_KEY)
+ url = fmt.Sprintf("%s&cx=017576662512468239146:omuauf_lfve", url)
+ url = fmt.Sprintf("%s&callback=hndlr", url)
+ url = fmt.Sprintf("%s&q=%s", url, keyword)
+
+ from := ctx.Value("from").(string)
+
+ // ...
+}
+```
+
+Di dalam fungsi tersebut, `url` pencarian dibentuk, data API key dan keyword pencarian disisipkan. Selain itu disiapkan pula `innerChanRes` dan `innerChanErr` yang kegunaannya mirip seperti objek channel yang disisipkan pada pemanggilan fungsi, hanya saja dua channel baru ini digunakan hanya dalam fungsi ini saja.
+
+Lanjut tulis kode berikut.
+
+```go
+ctx, cancel := context.WithTimeout(ctx, SEARCH_MAX_DURATION)
+defer cancel()
+
+req, err := http.NewRequest("GET", url, nil)
+if err != nil {
+ innerChanErr <- err
+ return
+}
+
+req = req.WithContext(ctx)
+req.Header.Set("Referer", from)
+
+transport := new(http.Transport)
+client := new(http.Client)
+client.Transport = transport
+```
+
+Objek `ctx` yang di-pass dari luar, dibungkus lagi menggunakan `context.WithTimeout()`. Fungsi ini mengembalikan objek context yang sudah ditambahi data deadline. Tepat setelah statement ini dieksekusi, dalam durasi `SEARCH_MAX_DURATION` context akan di-cancel.
+
+> Pengesetan deadline context bisa dilakukan lewat `context.WithTimeout()`, atau bisa juga lewat `context.WithDeadline()`. Perbedaannya pada fungsi `.WithDeadline()` parameter yang disisipkan bertipe `time.Time`.
+
+Disiapkan juga objek `req` yang merupakan objek request. Objek ini dibungkus menggunakan `req.WithContext()` dengan isi parameter objek `ctx`, menjadikan objek context yang sudah kita buat tertempel pada request ini. Kegunaannya nanti akan dibahas.
+
+Selain itu, data `from` yang didapat dari request context disisipkan sebagai request header pada objek `req`.
+
+Disiapkan juga objek `transport` yang bertipe `*http.Transport` dan objek `client` yang bertipe `*http.Client`.
+
+Setelah ini, tulis kode untuk dispatch request yang sudah dibuat lalu handle response nya. Jalankan proses-nya sebagai goroutine.
+
+```go
+go func() {
+ resp, err := client.Do(req)
+ if err != nil {
+ innerChanErr <- err
+ return
+ }
+
+ if resp != nil {
+ defer resp.Body.Close()
+ resData, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ innerChanErr <- err
+ return
+ }
+ innerChanRes <- resData
+ } else {
+ innerChanErr <- errors.New("No response")
+ }
+}()
+```
+
+Request di-trigger lewat statement `client.Do(req)`. Jika menghasilkan error, maka kirim informasi errornya ke channel `innerChanErr`. Cek juga objek response hasil request tersebut, jika kosong maka lempar sebuah error ke channel yang sama.
+
+Baca response body, jika tidak ada masalah, lempar result-nya ke channel `innerChanRes`.
+
+Selanjutnya, kita lakukan pengecekan menggunakan teknik `select case` untuk mengetahui channel mana yang menerima data.
+
+```go
+select {
+case res := <-innerChanRes:
+ chanRes <- res
+ return
+case err := <-innerChanErr:
+ transport.CancelRequest(req)
+ chanErr <- err
+ return
+case <-ctx.Done():
+ transport.CancelRequest(req)
+ chanErr <- errors.New("Search proccess exceed timeout")
+ return
+}
+```
+
+Silakan perhatikan kode di atas, kita akan bahas 2 `case` pertama.
+
+ - Jika channel `innerChanRes` mendapati kiriman data, maka langsung diteruskan ke channel `chanRes`.
+ - Jika channel `innerChanErr` mendapati kiriman data, maka langsung diteruskan ke channel `chanErr`. Tak lupa cancel request lewat method `transport.CancelRequest(req)`, ini diperlukan karena request gagal.
+
+Untuk case terakhir `case <-ctx.Done()`, penjelasannya agak sedikit panjang. Objek context, memiliki method `ctx.Done()` yang mengembalikan channel. Channel ini akan melewatkan data jika deadline timeout context-nya terpenuhi. Dan jika itu terjadi, pada kode di atas langsung dikembalikan sebuah error ke channel `chanErr` dengan isi error `Search proccess exceed timeout`; tak lupa request-nya juga di-cancel.
+
+Request perlu di-cancel karena jika waktu sudah mencapai deadline context (yaitu `SEARCH_MAX_DURATION`), pada saat tersebut bisa saja request belum selesai, maka dari itu request perlu di-cancel.
+
+Jika di-summary, maka yang dilakukan oleh fungsi `doSearch` kurang lebih sebagai berikut sebagai berikut.
+
+ - Jika request pencarian tak lebih dari `SEARCH_MAX_DURATION`:
+ - Jika hasilnya sukses, maka kembalikan response body lewat channel `chanRes`.
+ - Jika hasilnya gagal, maka kembalikan error-nya lewat channel `chanErr`.
+ - Jika request pencarian lebih dari `SEARCH_MAX_DURATION`, maka dianggap *timeout*, langsung lempar error timeout ke channel `chanErr`.
+
+Jalankan, lihat hasilnya.
+
+
+
+Informasi tambahan: best practice mengenai cancelation context adalah untuk selalu menambahkan `defer cancel()` setelah (cancelation) context dibuat. Lebih detailnya silakan baca https://blog.golang.org/context.
+
+## D.2.3. Google Search API Restrictions Referer
+
+Di bagian awal chapter ini, kita belajar mengenai context value. Kenapa penulis memilih menggunakan context untuk menyisipkan data referer, kenapa tidak contoh yg lebih umum seperti session dan lainnya? sebenarnya ada alasannya.
+
+Silakan coba akses kedua url berikut.
+
+ - http://mysampletestapp.com/api/search?keyword=golang
+ - http://localhost/api/search?keyword=golang
+
+Harusnya yang dikembalikan sama, tapi kenyataannya pengaksesan lewat url `localhost` menghasilkan error.
+
+
+
+Error message:
+
+
The referrer localhost does not match the referrer restrictions configured on your API key. Please use the API Console to update your key restrictions.
+
+Error di atas muncul karena, host `localhost` belum didaftarkan pada API console. Berbeda dengan `mysampletestapp.com` yang sudah didaftarkan, host ini berhak mengakses menggunakan API key yang kita gunakan.
+
+
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From d4b52a4e86fb5909f0a2c2051e63f1603e38be4e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:57 +0700
Subject: [PATCH 131/522] New translations
d-insert-1mil-csv-record-into-db-in-a-minute.md (English)
---
...ert-1mil-csv-record-into-db-in-a-minute.md | 351 ++++++++++++++++++
1 file changed, 351 insertions(+)
create mode 100644 en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md
diff --git a/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md b/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md
new file mode 100644
index 000000000..9e4ef99c3
--- /dev/null
+++ b/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md
@@ -0,0 +1,351 @@
+# D.1. Insert 1 Juta Data dari File CSV Ke Database Server, Menggunakan Teknik Worker Pool, Database Connection Pool, dan Mekanisme Failover.
+
+Pada chapter ini kita akan praktek penerapan salah satu teknik concurrent programming di Go yaitu worker pool, dikombinasikan dengan database connection pool, untuk membaca 1 juta rows data dari sebuah file csv untuk kemudian di-insert-kan ke mysql server.
+
+Pada bagian insert data kita terapkan mekanisme failover, jadi ketika ada operasi insert gagal, maka akan otomatis di recover dan di retry. Jadi idealnya di akhir, semua data, sejumlah satu juta, akan berhasil di-insert.
+
+## D.1.1. Penjelasan
+
+#### • Worker Pool
+
+Worker pool adalah teknik manajemen goroutine dalam *concurrent programming* pada Go. Sejumlah worker dijalankan dan masing-masing memiliki tugas yang sama yaitu menyelesaikan sejumlah jobs.
+
+Dengan metode worker pool ini, maka penggunaan memory dan performansi program akan bisa optimal.
+
+#### • Database Connection Pool
+
+*Connection pool* adalah metode untuk manajemen sejumlah koneksi database, agar bisa digunakan secara optimal.
+
+Connection pool sangat penting dalam kasus operasi data yang berhubungan dengan database yang di mana concurrent programming diterapkan.
+
+Karena pada concurrent programming, beberapa proses akan berjalan bersamaan, maka penggunaan 1 koneksi db akan menghambat proses tersebut. Perlu ada beberapa koneksi database, agar goroutine tidak rebutan objek koneksi database.
+
+#### • Failover
+
+Failover merupakan mekanisme backup ketika sebuah proses gagal. Pada konteks ini, failover mengarah ke proses untuk me-retry operasi insert ketika gagal.
+
+## D.1.2. Persiapan
+
+File [majestic-million-csv](https://blog.majestic.com/development/majestic-million-csv-daily/) digunakan sebagai bahan dalam praktek. File tersebut gratis dengan lisensi CCA3. Isinya adalah list dari top website berjumlah 1 juta.
+
+Silakan download file nya di sini http://downloads.majestic.com/majestic_million.csv.
+
+Setelah itu siapkan My SQL database server, create database dan sebuah tabel di dalamnya dengan nama domain.
+
+```sql
+CREATE DATABASE IF NOT EXISTS test;
+USE test;
+CREATE TABLE IF NOT EXISTS domain (
+ GlobalRank int,
+ TldRank int,
+ Domain varchar(255),
+ TLD varchar(255),
+ RefSubNets int,
+ RefIPs int,
+ IDN_Domain varchar(255),
+ IDN_TLD varchar(255),
+ PrevGlobalRank int,
+ PrevTldRank int,
+ PrevRefSubNets int,
+ PrevRefIPs int
+);
+```
+
+Setelah itu buat project baru, dan sebuah file `main.go`, dan tempatkan file csv yang sudah didownload dalam folder yang sama.
+
+Karena di contoh ini saya menggunakan My SQL, maka perlu untuk go get driver RDBMS ini untuk go.
+
+```bash
+go get -u github.com/go-sql-driver/mysql
+```
+
+Jika pembaca ingin menggunakan driver lain, juga silakan.
+
+## D.1.3. Praktek
+
+#### • Definisi Konstanta
+
+Ada beberapa konstanta yang perlu dipersiapkan. Pertama connection string untuk komunikasi ke database server. Sesuaikan value nya dengan yang dipergunakan.
+
+```go
+const dbConnString = "root@/test"
+```
+
+Lalu jumlah koneksi idle yang diperbolehkan, kita set saja 4, karena nantinya semua connection yg di create akan sibuk untuk bekerja meng-insert data.
+
+```go
+const dbMaxIdleConns = 4
+```
+
+Jumlah maksimum koneksi database dalam pool.
+
+```go
+const dbMaxConns = 100
+```
+
+Jumlah worker yang akan bekerja untuk menjalankan job.
+
+```go
+const totalWorker = 100
+```
+
+Path dari file CSV. Karena file berada satu level dengan `main.go` maka tulis saja nama file nya.
+
+```go
+const csvFile = "majestic_million.csv"
+```
+
+Terakhir, siapkan variabel untuk menampung data header dari pembacaan CSV nanti.
+
+```go
+var dataHeaders = make([]string, 0)
+```
+
+#### • Fungsi Buka Koneksi Database
+
+Buat fungsi untuk buka koneksi database, yg dikembalikan objek database kembalian fungsi `sql.Open()`.
+
+Jangan lupa set nilai `MaxOpenConns` dan `MaxIdleConns`.
+
+O iya, untuk yang tidak menggunakan mysql, maka sesuaikan saja nilai argument pertama statement `sql.Open()`.
+
+```go
+func openDbConnection() (*sql.DB, error) {
+ log.Println("=> open db connection")
+
+ db, err := sql.Open("mysql", dbConnString)
+ if err != nil {
+ return nil, err
+ }
+
+ db.SetMaxOpenConns(dbMaxConns)
+ db.SetMaxIdleConns(dbMaxIdleConns)
+
+ return db, nil
+}
+```
+
+O iya jangan lupa untuk import driver nya.
+
+```go
+import _ "github.com/go-sql-driver/mysql"
+```
+
+#### • Fungsi Baca CSV
+
+Buka file CSV, lalu gunakan objek file untuk membuat objek CSV reader baru.
+
+```go
+func openCsvFile() (*csv.Reader, *os.File, error) {
+ log.Println("=> open csv file")
+
+ f, err := os.Open(csvFile)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ reader := csv.NewReader(f)
+ return reader, f, nil
+}
+```
+
+#### • Fungsi Menjalankan Workers
+
+Ok, sekarang kita mulai masuk ke aspek konkurensi dari pembahasan ini. Siapkan fungsi yang isinya men-dispatch beberapa goroutine sejumlah `totalWorker`.
+
+Tiap-tiap goroutine tersebut adalah worker atau pekerja, yang tugasnya nanti akan meng-insert data ke database.
+
+Saat aplikasi dijalankan, sejumlah 100 worker akan berlomba-lomba menyelesaikan job insert data sejumlah 1 juta data.
+
+1 job adalah 1 data, maka rata-rata setiap worker akan menyelesaikan operasi insert sekitar 10k. Tapi ini jelasnya tidak pasti karena worker akan berkompetisi dalam penyelesaian job, jadi sangat besar kemungkinan akan ada job yang menyelesaikan lebih dari 10k jobs, ataupun yg di bawah 10k jobs.
+
+```go
+func dispatchWorkers(db *sql.DB, jobs <-chan []interface{}, wg *sync.WaitGroup) {
+ for workerIndex := 0; workerIndex <= totalWorker; workerIndex++ {
+ go func(workerIndex int, db *sql.DB, jobs <-chan []interface{}, wg *sync.WaitGroup) {
+ counter := 0
+
+ for job := range jobs {
+ doTheJob(workerIndex, counter, db, job)
+ wg.Done()
+ counter++
+ }
+ }(workerIndex, db, jobs, wg)
+ }
+}
+```
+
+Bisa dilihat dalam fungsi di atas, di dalam goroutine/worker, isi channel jobs (yang berupa data dari proses pembacaan CSV), didistribusikan ke worker, ke goroutine.
+
+Fungsi `doTheJob()` yang nantinya kita buat, isinya adalah operasi insert data ke database server. Setiap satu operasi insert selesai, `wg.Done()` untuk menandai bahwa 1 job adalah selesai.
+
+Idealnya di akhir aplikasi akan terjadi pemanggilan `wg.Done()` sejumlah 1 juta karena ada 1 juta jobs.
+
+#### • Fungsi Baca CSV dan Pengiriman Jobs ke Worker
+
+Proses pembacaan CSV, apapun metodenya pasti yang dijalankan adalah membaca data dari line ke line dari baris paling bawah.
+
+> Proses baca satu file tidak bisa di-konkurensi-kan
+
+```go
+func readCsvFilePerLineThenSendToWorker(csvReader *csv.Reader, jobs chan<- []interface{}, wg *sync.WaitGroup) {
+ for {
+ row, err := csvReader.Read()
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ break
+ }
+
+ if len(dataHeaders) == 0 {
+ dataHeaders = row
+ continue
+ }
+
+ rowOrdered := make([]interface{}, 0)
+ for _, each := range row {
+ rowOrdered = append(rowOrdered, each)
+ }
+
+ wg.Add(1)
+ jobs <- rowOrdered
+ }
+ close(jobs)
+}
+```
+
+Data dibaca dalam perulangan per baris. Pada pembacaan pertama, rows akan ditampung ke variabel `dataHeaders`. Selanjutnya, data dikirimkan ke worker lewat channel `jobs`.
+
+Setelah proses baca data selesai, channel di close. Karena pengiriman dan penerimaan data pada channel bersifat synchronous untuk unbuffered channel. Jadi aman untuk berasumsi bahwa ketika semua data berhasil dikirim, maka semua data tersebut juga berhasil diterima.
+
+Jika blok kode perulangan dalam fungsi di atas selesai, maka sudah tidak ada lagi operasi kirim terima data, maka kita close channelnya.
+
+#### • Fungsi Insert Data ke Database
+
+```go
+func doTheJob(workerIndex, counter int, db *sql.DB, values []interface{}) {
+ for {
+ var outerError error
+ func(outerError *error) {
+ defer func() {
+ if err := recover(); err != nil {
+ *outerError = fmt.Errorf("%v", err)
+ }
+ }()
+
+ conn, err := db.Conn(context.Background())
+ query := fmt.Sprintf("INSERT INTO domain (%s) VALUES (%s)",
+ strings.Join(dataHeaders, ","),
+ strings.Join(generateQuestionsMark(len(dataHeaders)), ","),
+ )
+
+ _, err = conn.ExecContext(context.Background(), query, values...)
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = conn.Close()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+ }(&outerError)
+ if outerError == nil {
+ break
+ }
+ }
+
+ if counter%100 == 0 {
+ log.Println("=> worker", workerIndex, "inserted", counter, "data")
+ }
+}
+
+func generateQuestionsMark(n int) []string {
+ s := make([]string, 0)
+ for i := 0; i < n; i++ {
+ s = append(s, "?")
+ }
+ return s
+}
+```
+
+Pada kode di atas bisa dilihat bahwa kode insert dibungkus dalam IIFE dalam sebuah perulangan.
+
+Kenapa butuh perulangan? keyword `for` di atas perannya sangat penting. Di sini diterapkan mekanisme failover di mana ketika proses insert gagal akan di recover dan di-retry ulang.
+
+Nah jadi ketika operasi insert di atas gagal, maka error tetap di tampilkan tapi kemudian diulang kembali insert data yang gagal tadi, hingga sukses.
+
+O iya, mengenai kode untuk manajemen db connection poll mana ya? sepertinya tidak ada. Yups, memang tidak ada. As per Go official documentation untuk package `sql/database`, connection pool di manage oleh Go, kita engineer cukup panggil method `.Conn()` milik `*sql.DB` untuk mengambil pool item, yang pool item ini bisa berupa connection lama yang di reuse atau connection yang baru dibuat.
+
+> Conn returns a single connection by either opening a new connection or returning an existing connection from the connection pool. Conn will block until either a connection is returned or ctx is canceled. Queries run on the same Conn will be run in the same database session. Every Conn must be returned to the database pool after use by calling Conn.Close.
+
+Btw, di atas juga ada satu fungsi lagi, `generateQuestionsMark()`, gunanya untuk membantu pembentukan query insert data secara dinamis.
+
+#### • Fungsi Main
+
+Terakhir, panggil semua fungsi yang sudah dibuat pada main.
+
+```go
+func main() {
+ start := time.Now()
+
+ db, err := openDbConnection()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ csvReader, csvFile, err := openCsvFile()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+ defer csvFile.Close()
+
+ jobs := make(chan []interface{}, 0)
+ wg := new(sync.WaitGroup)
+
+ go dispatchWorkers(db, jobs, wg)
+ readCsvFilePerLineThenSendToWorker(csvReader, jobs, wg)
+
+ wg.Wait()
+
+ duration := time.Since(start)
+ fmt.Println("done in", int(math.Ceil(duration.Seconds())), "seconds")
+}
+```
+
+Di akhir fungsi main ditambahkan log untuk benchmark performa.
+
+## D.1.4. Eksekusi Program
+
+Ok, sekarang mari kita coba eksekusi program-nya.
+
+
+
+Nah, bisa dilihat operasi insert selesai dalam waktu sekitar 1 menitan. Saya menggunakan laptop dengan spek berikut untuk run program:
+
+- Core i7-8750H 2.20GHz (6 core, 12 logical prosesor)
+- RAM 16GB
+- SSD
+
+Kalau diperhatikan, hanya 25.8% utilisasi CPU, dan hanya 12MB saja resource RAM yang dibutuhkan. Di Task Manager CPU usage nya 100% karena My SQL server di lokal saya benar-benar bekerja keras untuk menjalankan operasi insert.
+
+> Kecepatan aplikasi sangat tergantung dengan spesifikasi hardware laptop/server yang dipakai.
+
+Kalau dibandingkan dengan operasi insert data secara sekuensial, yang tanpa worker pool dan tanpa db connection pool, memakan waktu hingga **20 MENIT!**. Metode pada chapter ini jauh lebih cepat.
+
+Praktek pada chapter ini sifatnya adalah POC, jadi sangat mungkin diperlukan penyesuaian untuk kasus nyata. Kode di atas sebenarnya juga masih bisa di optimize lagi dari banyak sisi.
+
+---
+
+ - [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql), by Go SQL Driver Team, Mozilla Public License 2.0
+
+---
+
+
+
Source code praktek chapter ini tersedia di Github
+
+---
+
+
From b0b7f09bdee6f55f5e43b8a5080f95f56a8663ae Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:58 +0700
Subject: [PATCH 132/522] New translations distribution.md (English)
---
en/content-en/DISTRIBUTION.md | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 en/content-en/DISTRIBUTION.md
diff --git a/en/content-en/DISTRIBUTION.md b/en/content-en/DISTRIBUTION.md
new file mode 100644
index 000000000..490951ff6
--- /dev/null
+++ b/en/content-en/DISTRIBUTION.md
@@ -0,0 +1,17 @@
+# Lisensi dan Distribusi Konten
+
+Ebook Dasar Pemrograman Go gratis untuk disebarluaskan secara bebas, dengan catatan sesuai dengan aturan lisensi [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0) yang kurang lebih sebagai berikut:
+
+- Diperbolehkan menyebar, mencetak, dan menduplikasi material dalam konten ini ke siapapun.
+- Diperbolehkan memodifikasi, mengubah, atau membuat konten baru menggunakan material yang ada dalam ebook ini untuk keperluan komersil maupun tidak.
+
+Dengan catatan:
+
+- Harus ada credit sumber aslinya, yaitu **Dasar Pemrograman Golang** atau **novalagung**
+- Tidak mengubah lisensi aslinya, yaitu **CC BY-SA 4.0**
+- Tidak ditambahi *restrictions* baru
+
+Lebih jelasnya silakan cek [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/).
+
+[](https://app.fossa.io/projects/git%2Bgithub.com%2Fnovalagung%2Fdasarpemrogramangolang?ref=badge_large)
+
From 2f6ed13146ce7b4a3267cf6de152329b50d8c632 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:58 +0700
Subject: [PATCH 133/522] New translations readme.md (English)
---
en/content-en/README.md | 54 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
create mode 100644 en/content-en/README.md
diff --git a/en/content-en/README.md b/en/content-en/README.md
new file mode 100644
index 000000000..5478ce8a5
--- /dev/null
+++ b/en/content-en/README.md
@@ -0,0 +1,54 @@
+# Dasar Pemrograman Golang
+
+[Golang](https://golang.org/), atau Go adalah bahasa pemrograman yang lahir di tahun 2009. Golang memiliki banyak kelebihan, terbukti dengan banyaknya perusahaan besar yang [menggunakan bahasa ini](https://github.com/golang/go/wiki/GoUsers) dalam pengembangan produk-produk mereka, hingga level production tentunya.
+
+Ebook ini merupakan salah satu dari sekian banyak referensi yang bisa dijadikan bahan belajar pemrograman Go. Topik-topik yang disediakan sangat bervariasi mulai dari hal yang basic (dari 0), hingga chapter yang sifatnya advance.
+
+
+
+Ada total sekitar 120 chapter yang dibahas dalam ebook ini. Kumpulan chapter tersebut dibagi menjadi 4 kategori besar yang berurutan dan berkesinambungan satu sama lain.
+
+
+
+ Pemrograman Go Dasar. Pada bagian ini topik yang dibahas sangat dasar, cocok untuk orang yang belum pernah tau atau belum menggunakan bahasa Go. Pembahasan dimulai dari instalasi, eksekusi, hello word, dilanjutkan dengan topik seperti pembahasan beberapa keyword Go, pointer, struct, interface, reflect, goroutine, channel, date time, dan lainnya.
+
+
+ Pemrograman Web Go Dasar. Pada bagian ini kita akan fokus belajar ilmu dasar yang diperlukan untuk pengembangan aplikasi web menggunakan Go, di antaranya seperti: routing, multiplexer, middleware, cookie, dan lainnya. Pada chapter ini kita tidak menggunakan framework atau library external, hanya menggunakan API internal yang disediakan Go saja.
+
+
+ Pemrograman Go Lanjut. Di bagian ini akan mulai dibahas topik yang lebih advance, beberapa di antaranya akan menggunakan library-library Go yang sudah cukup terkenal di komunitas. Topik-topik tersebut antara lain: http, ssl, cors, crsf, mail, pdf, excel, ftp, ssh, web socket, protobuf, gRPC + protobuf, atau topik advance web atau non-web lainnya.
+
+
+ Studi Kasus. Di bagian ini akan dibahas mengenai Proof of Concept dari problem solving kasus penerapan aplikasi Go di real project.
+
+
+
+Versi e-book: **((VERSION))**, dan versi Go **1.20**.
+
+## Download File E-book (pdf, epub, mobi)
+
+Ebook ini bisa di-download dalam bentuk file via link berikut:
+
+- [PDF](https://github.com/novalagung/dasarpemrogramangolang/raw/ebooks/dasarpemrogramangolang.pdf?v=((VERSION)))
+- [Epub](https://github.com/novalagung/dasarpemrogramangolang/raw/ebooks/dasarpemrogramangolang.epub?v=((VERSION)))
+- [Mobi](https://github.com/novalagung/dasarpemrogramangolang/raw/ebooks/dasarpemrogramangolang.mobi?v=((VERSION)))
+
+Untuk mendapatkan konten buku yang paling update, silakan baca langsung versi web secara online atau download ulang e-book versi terbaru.
+
+## Source Code Praktek
+
+Source code contoh-contoh program bisa diunduh di [github.com/novalagung/dasarpemrogramangolang-example](https://github.com/novalagung/dasarpemrogramangolang-example). Dianjurkan untuk tidak copy-paste dari source code dalam proses belajar, usahakan untuk menulis sendiri kode program agar cepat terbiasa dengan bahasa Go.
+
+## Kontribusi
+
+Ebook ini merupakan project open source, jadi teruntuk siapapun yang ingin berkontribusi silakan langsung saja cek GitHub kami di [github.com/novalagung/dasarpemrogramangolang](https://github.com/novalagung/dasarpemrogramangolang). Silakan lihat juga [laman kontributor](https://dasarpemrogramangolang.novalagung.com/CONTRIBUTING.html) untuk lebih detailnya mengenai kontribusi ke project ini.
+
+## Lisensi dan Status FOSSA
+
+Ebook Dasar Pemrograman Go gratis untuk disebarluaskan secara bebas, baik untuk komersil maupun tidak, dengan catatan harus disertakan credit sumber aslinya (yaitu **Dasar Pemrograman Golang** atau **novalagung**) dan tidak mengubah lisensi aslinya (yaitu **CC BY-SA 4.0**). Lebih detailnya silakan cek halaman [lisensi dan distribusi konten](https://dasarpemrogramangolang.novalagung.com/LICENSE.html).
+
+[](https://app.fossa.io/projects/git%2Bgithub.com%2Fnovalagung%2Fdasarpemrogramangolang?ref=badge_large)
+
+## Author
+
+Ebook ini dibuat oleh **Noval Agung Prayogo**. Untuk pertanyaan, kritik, dan saran, silakan drop email ke [hello@novalagung.com](mailto:hello@novalagung.com).
From a7a201280ac6baffb12867eb7625d9be1f0e538d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sat, 21 Oct 2023 14:41:59 +0700
Subject: [PATCH 134/522] New translations summary.md (English)
---
en/content-en/SUMMARY.md | 159 +++++++++++++++++++++++++++++++++++++++
1 file changed, 159 insertions(+)
create mode 100644 en/content-en/SUMMARY.md
diff --git a/en/content-en/SUMMARY.md b/en/content-en/SUMMARY.md
new file mode 100644
index 000000000..c65590d8a
--- /dev/null
+++ b/en/content-en/SUMMARY.md
@@ -0,0 +1,159 @@
+# Summary
+
+* [Dasar Pemrograman Golang](README.md)
+* [Download versi PDF](https://github.com/novalagung/dasarpemrogramangolang/raw/ebooks/dasarpemrogramangolang.pdf)
+* [Author & Contributors](CONTRIBUTING.md)
+* [Lisensi dan Distribusi Konten](DISTRIBUTION.md)
+
+
+
+* Referensi Belajar Lainnya
+ * [📖 Dasar Pemrograman Python](https://dasarpemrogramanpython.novalagung.com/)
+ * [📖 Dasar Pemrograman Rust](https://dasarpemrogramanrust.novalagung.com/)
+ * [📖 How To](https://howto.novalagung.com)
+ * [▶️ Udemy Course: Belajar Docker & Kubernetes (FREE Juni 2023)](https://www.udemy.com/course/praktis-belajar-docker-dan-kubernetes-untuk-pemula/?couponCode=FREE-202306)
+
+
+
+* A. Pemrograman Go Dasar
+ * [A.1. Belajar Golang](1-berkenalan-dengan-golang.md)
+ * [A.2. Instalasi Golang (Stable & Unstable)](2-instalasi-golang.md)
+ * [A.3. Setup Go Modules](A-setup-go-project-dengan-go-modules.md)
+ * [A.4. Setup GOPATH Dan Workspace](A-gopath-dan-workspace.md)
+ * [A.5. Instalasi Editor](A-instalasi-editor.md)
+ * [A.6. Go Command](A-go-command.md)
+ * [A.7. Program Pertama: Hello World](A-hello-world.md)
+ * [A.8. Komentar](A-komentar.md)
+ * [A.9. Variabel](A-variabel.md)
+ * [A.10. Tipe Data](A-tipe-data.md)
+ * [A.11. Konstanta](A-konstanta.md)
+ * [A.12. Operator](A-operator.md)
+ * [A.13. Seleksi Kondisi](A-seleksi-kondisi.md)
+ * [A.14. Perulangan](A-perulangan.md)
+ * [A.15. Array](A-array.md)
+ * [A.16. Slice](A-slice.md)
+ * [A.17. Map](A-map.md)
+ * [A.18. Fungsi](A-fungsi.md)
+ * [A.19. Fungsi Multiple Return](A-fungsi-multiple-return.md)
+ * [A.20. Fungsi Variadic](A-fungsi-variadic.md)
+ * [A.21. Fungsi Closure](A-fungsi-closure.md)
+ * [A.22. Fungsi Sebagai parameter](A-fungsi-sebagai-parameter.md)
+ * [A.23. Pointer](A-pointer.md)
+ * [A.24. Struct](A-struct.md)
+ * [A.25. Method](A-method.md)
+ * [A.26. Properti Public dan Private (Exported vs Unexported)](A-properti-public-dan-private.md)
+ * [A.27. Interface](A-interface.md)
+ * [A.28. Interface Kosong (Any)](A-interface-kosong.md)
+ * [A.29. Reflect](A-reflect.md)
+ * [A.30. Goroutine](A-goroutine.md)
+ * [A.31. Channel](A-channel.md)
+ * [A.32. Buffered Channel](A-buffered-channel.md)
+ * [A.33. Channel - Select](A-channel-select.md)
+ * [A.34. Channel - Range & Close](A-channel-range-close.md)
+ * [A.35. Channel - Timeout](A-channel-timeout.md)
+ * [A.36. Defer & Exit](A-defer-exit.md)
+ * [A.37. Error, Panic, & Recover](A-error-panic-recover.md)
+ * [A.38. Layout Format String](A-string-format.md)
+ * [A.39. Random](A-random.md)
+ * [A.40. Time, Parsing Time, & Format Time](A-time-parsing-format.md)
+ * [A.41. Timer, Ticker, & Scheduler](A-timer-ticker-scheduler.md)
+ * [A.42. Time Duration](A-time-duration.md)
+ * [A.43. Konversi Antar Tipe Data](A-data-type-conversion.md)
+ * [A.44. Fungsi String](A-strings.md)
+ * [A.45. Regex](A-regex.md)
+ * [A.46. Encode - Decode Base64](A-encoding-base64.md)
+ * [A.47. Hash Sha1](A-hash-sha1.md)
+ * [A.48. Arguments & Flag](A-command-line-args-flag.md)
+ * [A.49. Exec](A-exec.md)
+ * [A.50. File](A-file.md)
+ * [A.51. Web Server](A-web-server.md)
+ * [A.52. URL Parsing](A-url-parsing.md)
+ * [A.53. JSON Data](A-json.md)
+ * [A.54. Web Service API Server](A-web-service-api.md)
+ * [A.55. Simple Client HTTP Request](A-client-http-request-simple.md)
+ * [A.56. SQL](A-sql.md)
+ * [A.57. NoSQL MongoDB](A-mongodb.md)
+ * [A.58. Unit Test](A-unit-test.md)
+ * [A.59. sync.WaitGroup](A-waitgroup.md)
+ * [A.60. sync.Mutex](A-mutex.md)
+ * [A.61. Go Vendoring](A-go-vendoring.md)
+ * [A.62. Concurrency Pattern: Pipeline](A-concurrency-pipeline.md)
+ * [A.63. Concurrency Pattern: Simplified Fan-in Fan-out Pipeline](A-simplified-fan-in-fan-out-pipeline.md)
+ * [A.64. Concurrency Pattern: Context Cancellation Pipeline](A-pipeline-context-cancellation.md)
+ * [A.65. Go Generics](A-golang-generics.md)
+
+
+
+* B. Pemrograman Web Go Dasar
+ * [B.1. Golang Web App: Hello World](B-golang-web-hello-world.md)
+ * [B.2. Routing http.HandleFunc](B-routing-http-handlefunc.md)
+ * [B.3. Routing Static Assets](B-routing-static-assets.md)
+ * [B.4. Template: Render HTML Template](B-template-render-html.md)
+ * [B.5. Template: Render Partial HTML Template](B-template-render-partial-html.md)
+ * [B.6. Template: Actions & Variables](B-template-actions-variables.md)
+ * [B.7. Template: Functions](B-template-functions.md)
+ * [B.8. Template: Custom Functions](B-template-custom-functions.md)
+ * [B.9. Template: Render Specific HTML Template](B-render-specific-html-template.md)
+ * [B.10. Template: Render HTML String](B-render-html-string.md)
+ * [B.11. HTTP Method: POST & GET](B-http-method-basic.md)
+ * [B.12. Form Value](B-form-value.md)
+ * [B.13. Form Upload File](B-form-upload-file.md)
+ * [B.14. AJAX JSON Payload](B-ajax-json-payload.md)
+ * [B.15. AJAX JSON Response](B-ajax-json-response.md)
+ * [B.16. AJAX Multiple File Upload](B-ajax-multi-upload.md)
+ * [B.17. Download File](B-download-file.md)
+ * [B.18. HTTP Basic Auth](B-http-basic-auth.md)
+ * [B.19. Middleware http.Handler](B-middleware-using-http-handler.md)
+ * [B.20. Custom Multiplexer](B-custom-mux-multiplexer.md)
+ * [B.21. HTTP Cookie](B-cookie.md)
+ * [B.22. Simple Configuration](B-simple-configuration.md)
+ * [B.23. Server Handler HTTP Request Cancellation](B-server-handler-http-request-cancellation.md)
+
+
+
+* C. Pemrograman Go Lanjut
+ * [C.1. Project Layout Structure](C-project-layout-structure.md)
+ * [C.2. Go Web Framework](C-golang-web-framework.md)
+ * [C.3. Echo Framework & Routing](C-echo-routing.md)
+ * [C.4. Parsing HTTP Request Payload (Echo)](C-parsing-http-request-payload-echo.md)
+ * [C.5. HTTP Request Payload Validation (Validator v9, Echo)](C-http-request-payload-validation.md)
+ * [C.6. HTTP Error Handling (Validator v9, Echo)](C-http-error-handling.md)
+ * [C.7. Template Rendering in Echo](C-echo-template-rendering.md)
+ * [C.8. Advanced Middleware & Logging (Logrus, Echo Logger)](C-advanced-middleware-and-logging.md)
+ * [C.9. CLI Flag Parser (Kingpin)](C-flag-parser.md)
+ * [C.10. Advanced Configuration: Viper](C-advanced-configuration-viper.md)
+ * [C.11. Best Practice Configuration: Environment Variable](C-best-practice-configuration-env-var.md)
+ * [C.12. Secure Cookie (Gorilla Securecookie)](C-securecookie.md)
+ * [C.13. Session (Gorilla Session)](C-session.md)
+ * [C.14. CORS & Preflight Request](C-cors-preflight-request.md)
+ * [C.15. CSRF](C-csrf.md)
+ * [C.16. Secure Middleware](C-secure-middleware.md)
+ * [C.17. HTTP Gzip Compression (gziphandler)](C-http-gzip-compression.md)
+ * [C.18. Send Mail (net/smtp, Gomail v2)](C-send-email.md)
+ * [C.19. Read & Write Excel XLSX File (Excelize)](C-read-write-excel-xlsx-file.md)
+ * [C.20. Write PDF File (gofpdf)](C-write-pdf-file.md)
+ * [C.21. Convert HTML to PDF (go-wkhtmltopdf)](C-convert-html-to-pdf.md)
+ * [C.22. Scraping & Parsing HTML (goquery)](C-scraping-parsing-html.md)
+ * [C.23. Parse & Generate XML (etree)](C-xml-parser.md)
+ * [C.24. HTTPS/TLS Web Server](C-https-tls.md)
+ * [C.25. HTTP/2 & HTTP/2 Server Push](C-http2-server-push.md)
+ * [C.26. Advanced Client HTTP Request](C-client-http-request-advanced.md)
+ * [C.27. Secure & Insecure Client HTTP Request](C-secure-insecure-client-http-request.md)
+ * [C.28. FTP](C-golang-ftp.md)
+ * [C.29. SSH & SFTP](C-golang-ssh-sftp.md)
+ * [C.30. Protobuf](C-golang-protobuf-implementation.md)
+ * [C.31. gRPC + Protobuf](C-golang-grpc-protobuf.md)
+ * [C.32. JSON Web Token (JWT)](C-golang-jwt.md)
+ * [C.33. LDAP Authentication](C-golang-ldap-authentication.md)
+ * [C.34. SSO SAML (Service Provider)](C-golang-sso-saml-sp.md)
+ * [C.35. Dockerize Aplikasi Golang](C-dockerize-golang.md)
+ * [C.36. Redis](C-golang-redis.md)
+ * [C.37. Singleflight](C-singleflight.md)
+ * [C.38. AWS S3](C-golang-aws-s3.md)
+
+
+
+* D. Studi Kasus
+ * [D.1. Insert 1 Juta Data dari File CSV Ke Database Server, Menggunakan Teknik Worker Pool, Database Connection Pool, dan Mekanisme Failover](D-insert-1mil-csv-record-into-db-in-a-minute.md)
+ * [D.2. Google API Search Dengan Timeout](D-google-api-search.md)
+ * [D.3. Web Socket: Chatting App](D-golang-web-socket-chatting-app.md)
From 61b9915774ab4f4c4231f050ecadd49c1aca257c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:35 +0700
Subject: [PATCH 135/522] New translations a-concurrency-pipeline.md (English)
---
en/content-en/A-concurrency-pipeline.md | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/en/content-en/A-concurrency-pipeline.md b/en/content-en/A-concurrency-pipeline.md
index 1f9805f43..368958e85 100644
--- a/en/content-en/A-concurrency-pipeline.md
+++ b/en/content-en/A-concurrency-pipeline.md
@@ -74,7 +74,6 @@ package main
import (
"fmt"
- "io/ioutil"
"log"
"math/rand"
"os"
@@ -128,7 +127,7 @@ func generateFiles() {
for i := 0; i < totalFile; i++ {
filename := filepath.Join(tempPath, fmt.Sprintf("file-%d.txt", i))
content := randomString(contentLength)
- err := ioutil.WriteFile(filename, []byte(content), os.ModePerm)
+ err := os.WriteFile(filename, []byte(content), os.ModePerm)
if err != nil {
log.Println("Error writing file", filename)
}
@@ -164,7 +163,6 @@ package main
import (
"crypto/md5"
"fmt"
- "io/ioutil"
"log"
"os"
"path/filepath"
@@ -209,7 +207,7 @@ func proceed() {
counterTotal++
// read file
- buf, err := ioutil.ReadFile(path)
+ buf, err := os.ReadFile(path)
if err != nil {
return err
}
@@ -241,7 +239,7 @@ Cukup panjang isi fungsi ini, tetapi isinya cukup *straightforward* kok.
* Kedua, kita siapkan `counterRenamed` sebagai counter jumlah file yang berhasil di-rename. Untuk ini juga idealnya sama dengan nilai pada `counterTotal`, kecuali ada error
* Kita gunakan `filepath.Walk` untuk melakukan pembacaan semua file yang ada dalam folder `$TEMP/chapter-A.59-pipeline-temp`.
* File akan dibaca secara sekuensial, di tiap pembacaan jika ada error dan ditemukan sebuah direktori, maka kita ignore kemudian lanjut pembacaan file selanjutnya.
-* File dibaca menggunakan `ioutil.ReadFile()`, kemudian lewat fungsi `md5.Sum()` kita cari md5 hash sum dari konten file.
+* File dibaca menggunakan `os.ReadFile()`, kemudian lewat fungsi `md5.Sum()` kita cari md5 hash sum dari konten file.
* Setelahnya, kita rename file dengan nama `file-.txt`.
Semoga cukup jelas. Kalo iya, jalankan programnya.
@@ -274,7 +272,6 @@ package main
import (
"crypto/md5"
"fmt"
- "io/ioutil"
"log"
"os"
"path/filepath"
@@ -329,7 +326,7 @@ func readFiles() <-chan FileInfo {
return nil
}
- buf, err := ioutil.ReadFile(path)
+ buf, err := os.ReadFile(path)
if err != nil {
return err
}
From 0736bb4b7ebab9482e8014364d8a0563bd40bad7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:36 +0700
Subject: [PATCH 136/522] New translations a-pipeline-context-cancellation.md
(English)
---
en/content-en/A-pipeline-context-cancellation.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/en/content-en/A-pipeline-context-cancellation.md b/en/content-en/A-pipeline-context-cancellation.md
index b0ed84d02..4554282cc 100644
--- a/en/content-en/A-pipeline-context-cancellation.md
+++ b/en/content-en/A-pipeline-context-cancellation.md
@@ -26,7 +26,6 @@ package main
import (
"fmt"
- "io/ioutil"
"log"
"math/rand"
"os"
@@ -147,7 +146,7 @@ func createFiles(chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
for job := range chanIn {
filePath := filepath.Join(tempPath, job.FileName)
content := randomString(contentLength)
- err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+ err := os.WriteFile(filePath, []byte(content), os.ModePerm)
log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
@@ -381,7 +380,7 @@ func createFiles(ctx context.Context, chanIn <-chan FileInfo, numberOfWorkers in
default:
filePath := filepath.Join(tempPath, job.FileName)
content := randomString(contentLength)
- err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+ err := os.WriteFile(filePath, []byte(content), os.ModePerm)
log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
From c1f01c26bdafce34696d23995cb908623624ec46 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:37 +0700
Subject: [PATCH 137/522] New translations a-properti-public-dan-private.md
(English)
---
en/content-en/A-properti-public-dan-private.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/en/content-en/A-properti-public-dan-private.md b/en/content-en/A-properti-public-dan-private.md
index a3488e7ee..550fe82c6 100644
--- a/en/content-en/A-properti-public-dan-private.md
+++ b/en/content-en/A-properti-public-dan-private.md
@@ -201,10 +201,11 @@ Dari contoh program di atas, bisa disimpulkan bahwa untuk menggunakan `struct` y
## A.26.5. Import Dengan Prefix Tanda Titik
+> PERINGATAN! Penggunaan tanda titik pada saat import package bisa menyebabkan kode menjadi ambigu, karena alasan tersebut teknik import ini kurang direkomendasikan.
+
Seperti yang kita tahu, untuk mengakses fungsi/struct/variabel yg berada di package lain, nama package nya perlu ditulis, contohnya seperti pada penggunaan `library.Student` dan `fmt.Println()`.
Di Go, komponen yang berada di package lain yang di-import bisa dijadikan se-level dengan komponen package peng-import, caranya dengan menambahkan tanda titik (`.`) setelah penulisan keyword `import`. Maksud dari se-level di sini adalah, semua properti di package lain yg di-import bisa diakses tanpa perlu menuliskan nama package, seperti ketika mengakses sesuatu dari file yang sama.
-
```go
import (
. "belajar-golang-level-akses/library"
From 4dd6b57243003393881a1aabd84ebdc473a20195 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:38 +0700
Subject: [PATCH 138/522] New translations
a-simplified-fan-in-fan-out-pipeline.md (English)
---
en/content-en/A-simplified-fan-in-fan-out-pipeline.md | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/en/content-en/A-simplified-fan-in-fan-out-pipeline.md b/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
index 9dc5493c1..bc8042795 100644
--- a/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
+++ b/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
@@ -27,7 +27,6 @@ package main
import (
"fmt"
- "io/ioutil"
"log"
"math/rand"
"os"
@@ -82,7 +81,7 @@ func generateFiles() {
for i := 0; i < totalFile; i++ {
filename := filepath.Join(tempPath, fmt.Sprintf("file-%d.txt", i))
content := randomString(contentLength)
- err := ioutil.WriteFile(filename, []byte(content), os.ModePerm)
+ err := os.WriteFile(filename, []byte(content), os.ModePerm)
if err != nil {
log.Println("Error writing file", filename)
}
@@ -113,7 +112,6 @@ package main
import (
"fmt"
- "io/ioutil"
"log"
"math/rand"
"os"
@@ -269,7 +267,7 @@ func createFiles(chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
// do the jobs
filePath := filepath.Join(tempPath, job.FileName)
content := randomString(contentLength)
- err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+ err := os.WriteFile(filePath, []byte(content), os.ModePerm)
log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
From 55c0e500206092afe3cca636b01d84bc3e97ca82 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:39 +0700
Subject: [PATCH 139/522] New translations
b-server-handler-http-request-cancellation.md (English)
---
en/content-en/B-server-handler-http-request-cancellation.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/en/content-en/B-server-handler-http-request-cancellation.md b/en/content-en/B-server-handler-http-request-cancellation.md
index 383a4a70b..366a11340 100644
--- a/en/content-en/B-server-handler-http-request-cancellation.md
+++ b/en/content-en/B-server-handler-http-request-cancellation.md
@@ -26,6 +26,7 @@ import (
"net/http"
"strings"
"time"
+ "log"
)
func handleIndex(w http.ResponseWriter, r *http.Request) {
@@ -97,7 +98,7 @@ go func() {
// do the process here
// simulate a long-time request by putting 10 seconds sleep
- body, err := ioutil.ReadAll(r.Body)
+ body, err := io.ReadAll(r.Body)
// ...
time.Sleep(10 * time.Second)
From c553a9791f1e3ef8ac47006eb7d7df86b7d0d41c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:40 +0700
Subject: [PATCH 140/522] New translations b-simple-configuration.md (English)
---
en/content-en/B-simple-configuration.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/en/content-en/B-simple-configuration.md b/en/content-en/B-simple-configuration.md
index d83de8811..530136e9b 100644
--- a/en/content-en/B-simple-configuration.md
+++ b/en/content-en/B-simple-configuration.md
@@ -51,7 +51,6 @@ package conf
import (
"encoding/json"
- "io/ioutil"
"os"
"path/filepath"
"time"
@@ -93,7 +92,7 @@ func init() {
return
}
- bts, err := ioutil.ReadFile(filepath.Join(basePath, "conf", "config.json"))
+ bts, err := os.ReadFile(filepath.Join(basePath, "conf", "config.json"))
if err != nil {
panic(err)
return
From c19bb0f9ae9aadc7dfef58386f0f2242777f6e6d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:42 +0700
Subject: [PATCH 141/522] New translations c-advanced-configuration-viper.md
(English)
---
en/content-en/C-advanced-configuration-viper.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/en/content-en/C-advanced-configuration-viper.md b/en/content-en/C-advanced-configuration-viper.md
index 1c5d4a012..3ecdc7b26 100644
--- a/en/content-en/C-advanced-configuration-viper.md
+++ b/en/content-en/C-advanced-configuration-viper.md
@@ -58,6 +58,11 @@ Berikut merupakan list format yang didukung oleh viper.
- properties
- props
- prop
+ - env
+ - dotenv
+ - tfvars
+ - ini
+ - hcl
Fungsi `.AddConfigPath()` digunakan untuk mendaftarkan path folder di mana file-file konfigurasi berada. Fungsi ini bisa dipanggil beberapa kali, jika memang ada banyak file konfigurasi tersimpan dalam path berbeda.
From 9655f1f3f8aee4e70257288b0c1598e50b2ecc43 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:43 +0700
Subject: [PATCH 142/522] New translations c-golang-ftp.md (English)
---
en/content-en/C-golang-ftp.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/en/content-en/C-golang-ftp.md b/en/content-en/C-golang-ftp.md
index 7dee6f497..9607d2d6e 100644
--- a/en/content-en/C-golang-ftp.md
+++ b/en/content-en/C-golang-ftp.md
@@ -157,7 +157,7 @@ if err != nil {
log.Fatal(err.Error())
}
-test1ContentInBytes, err := ioutil.ReadAll(fileTest1)
+test1ContentInBytes, err := io.ReadAll(fileTest1)
fileTest1.Close()
if err != nil {
log.Fatal(err.Error())
@@ -166,9 +166,9 @@ if err != nil {
fmt.Println(" ->", fileTest1Path, "->", string(test1ContentInBytes))
```
-Baca isi objek response tersebut menggunakan method `.Read()` miliknya, atau bisa juga menggunakan `ioutil.ReadAll()` lebih praktisnya (nilai baliknya bertipe `[]byte` maka cast ke tipe `string` terlebih dahulu untuk menampilkan isinya).
+Baca isi objek response tersebut menggunakan method `.Read()` miliknya, atau bisa juga menggunakan `io.ReadAll()` lebih praktisnya (nilai baliknya bertipe `[]byte` maka cast ke tipe `string` terlebih dahulu untuk menampilkan isinya).
-> Jangan lupa untuk import package `io/ioutil`.
+> Jangan lupa untuk import package `io`.
Di kode di atas file `test1.txt` dibaca. Lakukan operasi yang sama pada file `somefolder/test3.txt`.
@@ -179,7 +179,7 @@ if err != nil {
log.Fatal(err.Error())
}
-test2ContentInBytes, err := ioutil.ReadAll(fileTest2)
+test2ContentInBytes, err := io.ReadAll(fileTest2)
fileTest2.Close()
if err != nil {
log.Fatal(err.Error())
From ea7856b94f292d80349bd02b46bdbafd586c0851 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:44 +0700
Subject: [PATCH 143/522] New translations c-golang-jwt.md (English)
---
en/content-en/C-golang-jwt.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/en/content-en/C-golang-jwt.md b/en/content-en/C-golang-jwt.md
index f60c6719e..7900dda1c 100644
--- a/en/content-en/C-golang-jwt.md
+++ b/en/content-en/C-golang-jwt.md
@@ -120,7 +120,6 @@ import (
"context"
"encoding/json"
"fmt"
- "io/ioutil"
"net/http"
"os"
"path/filepath"
@@ -261,7 +260,7 @@ Bagian otentikasi dan generate token sebenarnya cukup sampai di sini. Tapi seben
func authenticateUser(username, password string) (bool, M) {
basePath, _ := os.Getwd()
dbPath := filepath.Join(basePath, "users.json")
- buf, _ := ioutil.ReadFile(dbPath)
+ buf, _ := os.ReadFile(dbPath)
data := make([]M, 0)
err := json.Unmarshal(buf, &data)
From 557d9b38bee477ae04172714083aed36921fe28a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:45 +0700
Subject: [PATCH 144/522] New translations c-golang-ssh-sftp.md (English)
---
en/content-en/C-golang-ssh-sftp.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/en/content-en/C-golang-ssh-sftp.md b/en/content-en/C-golang-ssh-sftp.md
index 1941183d2..d3b988f21 100644
--- a/en/content-en/C-golang-ssh-sftp.md
+++ b/en/content-en/C-golang-ssh-sftp.md
@@ -15,7 +15,6 @@ package main
import (
"golang.org/x/crypto/ssh"
- "io/ioutil"
"log"
"os"
)
@@ -57,7 +56,7 @@ sshConfig := &ssh.ClientConfig{
}
func PublicKeyFile(file string) ssh.AuthMethod {
- buffer, err := ioutil.ReadFile(file)
+ buffer, err := os.ReadFile(file)
if err != nil {
return nil
}
From 7d1aa99c99df29c686e448635a1f1e971efe37ca Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:46 +0700
Subject: [PATCH 145/522] New translations
c-secure-insecure-client-http-request.md (English)
---
en/content-en/C-secure-insecure-client-http-request.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-secure-insecure-client-http-request.md b/en/content-en/C-secure-insecure-client-http-request.md
index cef306894..ccf1da525 100644
--- a/en/content-en/C-secure-insecure-client-http-request.md
+++ b/en/content-en/C-secure-insecure-client-http-request.md
@@ -109,7 +109,7 @@ Isi `client.Transport` dengan konfigurasi secure request. Hapus saja konfigurasi
Kurang lebih kode-nya seperti berikut.
```go
-certFile, err := ioutil.ReadFile("server.crt")
+certFile, err := os.ReadFile("server.crt")
if err != nil {
return nil, err
}
From 2a9679623cb078a433d173911b3d74ab850c94bd Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:47 +0700
Subject: [PATCH 146/522] New translations contributing.md (English)
---
en/content-en/CONTRIBUTING.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/en/content-en/CONTRIBUTING.md b/en/content-en/CONTRIBUTING.md
index c20782465..7a2eb0008 100644
--- a/en/content-en/CONTRIBUTING.md
+++ b/en/content-en/CONTRIBUTING.md
@@ -26,6 +26,7 @@ Berikut merupakan *hall of fame* kontributor yang sudah berbaik hati menyisihkan
1. [Acep Saepudin](https://github.com/acepsaepudin)
1. [Adev Saputra](https://github.com/adev22)
+1. [Afifurrohman](afifurrohman-id)
1. [Agus Budiono](https://github.com/dyon048)
1. [Ahmad Syafiq Aqil Wafi](https://github.com/Syafiqjos)
1. [Akul Nurislamimanudin](https://github.com/akulnurislam)
From ee8c3087fd4c07cd5351e9e9722af8c53f3ba642 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:48 +0700
Subject: [PATCH 147/522] New translations d-golang-web-socket-chatting-app.md
(English)
---
en/content-en/D-golang-web-socket-chatting-app.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/en/content-en/D-golang-web-socket-chatting-app.md b/en/content-en/D-golang-web-socket-chatting-app.md
index 6c1ebeb17..a7fe99801 100644
--- a/en/content-en/D-golang-web-socket-chatting-app.md
+++ b/en/content-en/D-golang-web-socket-chatting-app.md
@@ -34,7 +34,7 @@ import (
"fmt"
"github.com/gorilla/websocket"
gubrak "github.com/novalagung/gubrak/v2"
- "io/ioutil"
+ "os"
"log"
"net/http"
"strings"
@@ -91,7 +91,7 @@ Selanjutnya buat fungsi `main()`, siapkan satu buah rute, `/`, isinya menampilka
```go
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- content, err := ioutil.ReadFile("index.html")
+ content, err := os.ReadFile("index.html")
if err != nil {
http.Error(w, "Could not open requested file", http.StatusInternalServerError)
return
From 3162f241ccdb43bfc1424299f461f3bcc5304ec2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 8 Nov 2023 16:17:49 +0700
Subject: [PATCH 148/522] New translations d-google-api-search.md (English)
---
en/content-en/D-google-api-search.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/en/content-en/D-google-api-search.md b/en/content-en/D-google-api-search.md
index 1ed19c49e..0ea6ff67a 100644
--- a/en/content-en/D-google-api-search.md
+++ b/en/content-en/D-google-api-search.md
@@ -56,7 +56,10 @@ package main
import (
"context"
+ "io"
"fmt"
+ "errors"
+ "time"
"net/http"
)
@@ -242,7 +245,7 @@ go func() {
if resp != nil {
defer resp.Body.Close()
- resData, err := ioutil.ReadAll(resp.Body)
+ resData, err := io.ReadAll(resp.Body)
if err != nil {
innerChanErr <- err
return
From 064a143960ef7fb32ba78ca760a6bdcbb1b343c0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Fri, 22 Dec 2023 16:47:26 +0700
Subject: [PATCH 149/522] New translations c-golang-grpc-protobuf.md (English)
---
en/content-en/C-golang-grpc-protobuf.md | 139 +++++++++++++-----------
1 file changed, 74 insertions(+), 65 deletions(-)
diff --git a/en/content-en/C-golang-grpc-protobuf.md b/en/content-en/C-golang-grpc-protobuf.md
index ef2c9f60c..1cd181fad 100644
--- a/en/content-en/C-golang-grpc-protobuf.md
+++ b/en/content-en/C-golang-grpc-protobuf.md
@@ -23,15 +23,13 @@ Sekedar informasi bahwa sebelum mulai mengikuti pembelajaran pada chapter ini, *
Siapkan satu project baru dengan struktur sebagai berikut.
```bash
-# perform go get outside of go mod project, so the binary will be stored in $GOPATH/bin.
-# more details, take a look at https://stackoverflow.com/a/30097929/1467988
-go get -u github.com/golang/protobuf/protoc-gen-go
+# Install protobuf go runtime
+go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
mkdir chapter-c30
cd chapter-c30
go mod init chapter-c30
-go get -u github.com/golang/protobuf@v1.3.2
go get -u google.golang.org/grpc@v1.26.0
# then prepare underneath structures
@@ -44,7 +42,6 @@ tree .
│ └── model
│ ├── garage.proto
│ ├── user.proto
-│ └── util.proto
│
├── client
│ └── main.go
@@ -87,8 +84,8 @@ Siapkan file bernama `config.go` dalam `common/config`. Di dalam file config ini
package config
const (
- SERVICE_GARAGE_PORT = ":7000"
- SERVICE_USER_PORT = ":9000"
+ ServiceGaragePort = ":7000"
+ ServiceUserPort = ":9000"
)
```
@@ -107,7 +104,7 @@ Buka file `user.proto`, tambahkan kode berikut di akhir baris.
```protobuf
// ...
-import "google/protobuf/Empty.proto";
+import "google/protobuf/empty.proto";
service Users {
rpc Register(User) returns (google.protobuf.Empty) {}
@@ -124,7 +121,7 @@ Silakan dilihat bagaimana cara untuk membuat service pada kode atas.
Pada method `Register()` sebenarnya kita tidak butuh nilai balik. Tapi karena requirements dari protobuf mewajibkan semua rpc method harus memiliki nilai balik, maka kita gunakan model `Empty` milik google protobuf.
-Cara penggunaan model `Empty` adalah dengan meng-import file proto-nya terlebih dahulu, `google/protobuf/Empty.proto`, lalu menggunakan `google.protobuf.Empty` sebagai model.
+Cara penggunaan model `Empty` adalah dengan meng-import file proto-nya terlebih dahulu, `google/protobuf/empty.proto`, lalu menggunakan `google.protobuf.Empty` sebagai model.
Juga, pada method `List()`, sebenarnya argument tidak dibutuhkan, tapi karena protobuf mewajibkan pendefinisian rpc method untuk memiliki satu buah argument dan satu buah return type, maka mau tidak mau harus ada argument.
@@ -134,8 +131,8 @@ Setelah di-compile, dua buah interface terbuat dengan skema nama ` Fungsi `RegisterUsersServer()` otomatis digenerate oleh protoc, karena service `Users` didefinisikan. Contoh lainnya misal service `SomeServiceTest` disiapkan, maka fungsi `RegisterSomeServiceTestServer()` dibuat.
-Selanjutnya, siapkan objek listener yang listen ke port `config.SERVICE_USER_PORT`, lalu gunakan listener tersebut sebagai argument method `.Serve()` milik objek rpc server.
+Selanjutnya, siapkan objek listener yang listen ke port `config.ServiceUserPort`, lalu gunakan listener tersebut sebagai argument method `.Serve()` milik objek rpc server.
```go
// ...
-l, err := net.Listen("tcp", config.SERVICE_USER_PORT)
+l, err := net.Listen("tcp", config.ServiceUserPort)
if err != nil {
- log.Fatalf("could not listen to %s: %v", config.SERVICE_USER_PORT, err)
+ log.Fatalf("could not listen to %s: %v", config.ServiceUserPort, err)
}
log.Fatal(srv.Serve(l))
@@ -307,7 +314,9 @@ func init() {
localStorage.List = make(map[string]*model.GarageList)
}
-type GaragesServer struct{}
+type GaragesServer struct {
+ model.UnimplementedGaragesServer
+}
```
Tugas `localStorage` kurang lebih sama seperti pada `service-user`, hanya saja pada aplikasi ini data garage disimpan per user dalam map.
@@ -317,33 +326,33 @@ Selanjutnya buat implementasi interface `model.GaragesServer`, lalu siapkan meth
- Method `Add()`
```go
- func (GaragesServer) Add(ctx context.Context, param *model.GarageAndUserId) (*empty.Empty, error) {
- userId := param.UserId
- garage := param.Garage
+ func (GaragesServer) Add(_ context.Context, param *model.GarageAndUserId) (*empty.Empty, error) {
+ userId := param.UserId
+ garage := param.Garage
- if _, ok := localStorage.List[userId]; !ok {
- localStorage.List[userId] = new(model.GarageList)
- localStorage.List[userId].List = make([]*model.Garage, 0)
- }
- localStorage.List[userId].List = append(localStorage.List[userId].List, garage)
+ if _, ok := localStorage.List[userId]; !ok {
+ localStorage.List[userId] = new(model.GarageList)
+ localStorage.List[userId].List = make([]*model.Garage, 0)
+ }
+ localStorage.List[userId].List = append(localStorage.List[userId].List, garage)
- log.Println("Adding garage", garage.String(), "for user", userId)
+ log.Println("Adding garage", garage.String(), "for user", userId)
- return new(empty.Empty), nil
+ return new(empty.Empty), nil
}
```
- Method `List()`
```go
- func (GaragesServer) List(ctx context.Context, param *model.GarageUserId) (*model.GarageList, error) {
- userId := param.UserId
+ func (GaragesServer) List(_ context.Context, param *model.GarageUserId) (*model.GarageList, error) {
+ userId := param.UserId
- return localStorage.List[userId], nil
+ return localStorage.List[userId], nil
}
```
-Start rpc server, gunakan `config.SERVICE_GARAGE_PORT` sebagai port aplikasi.
+Start rpc server, gunakan `config.ServiceGaragePort` sebagai port aplikasi.
```go
func main() {
@@ -351,11 +360,11 @@ func main() {
var garageSrv GaragesServer
model.RegisterGaragesServer(srv, garageSrv)
- log.Println("Starting RPC server at", config.SERVICE_GARAGE_PORT)
+ log.Println("Starting RPC server at", config.ServiceGaragePort)
- l, err := net.Listen("tcp", config.SERVICE_GARAGE_PORT)
+ l, err := net.Listen("tcp", config.ServiceGaragePort)
if err != nil {
- log.Fatalf("could not listen to %s: %v", config.SERVICE_GARAGE_PORT, err)
+ log.Fatalf("could not listen to %s: %v", config.ServiceGaragePort, err)
}
log.Fatal(srv.Serve(l))
@@ -366,12 +375,12 @@ func main() {
Buat file `client/main.go`, import package yang sama seperti pada `service-user` maupun `service-garage`. Lalu siapkan dua buah method yang mengembalikan rpc client yang terhubung ke dua service yang sudah kita buat.
- - RPC client garage:
+- RPC client garage:
```go
func serviceGarage() model.GaragesClient {
- port := config.SERVICE_GARAGE_PORT
- conn, err := grpc.Dial(port, grpc.WithInsecure())
+ port := config.ServiceGaragePort
+ conn, err := grpc.Dial(port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal("could not connect to", port, err)
}
@@ -384,8 +393,8 @@ Buat file `client/main.go`, import package yang sama seperti pada `service-user`
```go
func serviceUser() model.UsersClient {
- port := config.SERVICE_USER_PORT
- conn, err := grpc.Dial(port, grpc.WithInsecure())
+ port := config.ServiceUserPort
+ conn, err := grpc.Dial(port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal("could not connect to", port, err)
}
@@ -425,7 +434,7 @@ Selanjutnya akses fungsi `serviceUser()` untuk memperoleh objek rpc client user.
```go
user := serviceUser()
-fmt.Println("\n", "===========> user test")
+fmt.Printf("\n %s> user test\n", strings.Repeat("=", 10))
// register user1
user.Register(context.Background(), &user1)
@@ -444,7 +453,7 @@ Sekarang coba panggil method `.List()`. Jalankan ulang aplikasi client untuk mel
```go
// show all registered users
-res1, err := user.List(context.Background(), new(empty.Empty))
+res1, err := user.List(context.Background(), new(emptypb.Empty))
if err != nil {
log.Fatal(err.Error())
}
@@ -465,18 +474,18 @@ Tambahkan beberapa statement untuk memanggil method yang ada di `service-garage`
```go
garage := serviceGarage()
- fmt.Println("\n", "===========> garage test A")
+ fmt.Printf("\n %s> garage test A\n", strings.Repeat("=", 10))
// add garage1 to user1
garage.Add(context.Background(), &model.GarageAndUserId{
- UserId: user1.Id,
- Garage: &garage1,
+ UserId: user1.Id,
+ Garage: &garage1,
})
// add garage2 to user1
garage.Add(context.Background(), &model.GarageAndUserId{
- UserId: user1.Id,
- Garage: &garage2,
+ UserId: user1.Id,
+ Garage: &garage2,
})
```
@@ -486,7 +495,7 @@ Tambahkan beberapa statement untuk memanggil method yang ada di `service-garage`
// show all garages of user1
res2, err := garage.List(context.Background(), &model.GarageUserId{UserId: user1.Id})
if err != nil {
- log.Fatal(err.Error())
+ log.Fatal(err.Error())
}
res2String, _ := json.Marshal(res2.List)
log.Println(string(res2String))
@@ -495,7 +504,7 @@ Tambahkan beberapa statement untuk memanggil method yang ada di `service-garage`
- Menambahkan garage untuk user `user2`:
```go
- fmt.Println("\n", "===========> garage test B")
+ fmt.Printf("\n %s> garage test B\n", strings.Repeat("=", 10))
// add garage3 to user2
garage.Add(context.Background(), &model.GarageAndUserId{
@@ -510,7 +519,7 @@ Tambahkan beberapa statement untuk memanggil method yang ada di `service-garage`
// show all garages of user2
res3, err := garage.List(context.Background(), &model.GarageUserId{UserId: user2.Id})
if err != nil {
- log.Fatal(err.Error())
+ log.Fatal(err.Error())
}
res3String, _ := json.Marshal(res3.List)
log.Println(string(res3String))
From 6debb33aa815cf0e9cb544ccfceeaf88c8d2c7d0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Fri, 22 Dec 2023 16:47:27 +0700
Subject: [PATCH 150/522] New translations c-golang-protobuf-implementation.md
(English)
---
.../C-golang-protobuf-implementation.md | 114 +++++++++---------
1 file changed, 55 insertions(+), 59 deletions(-)
diff --git a/en/content-en/C-golang-protobuf-implementation.md b/en/content-en/C-golang-protobuf-implementation.md
index 4641baf23..ce744f236 100644
--- a/en/content-en/C-golang-protobuf-implementation.md
+++ b/en/content-en/C-golang-protobuf-implementation.md
@@ -37,14 +37,12 @@ Silakan merujuk ke http://google.github.io/proto-lens/installing-protoc.html unt
Selain `protoc`, masih ada satu lagi yang perlu di install, yaitu protobuf runtime untuk Go (karena di sini kita menggunakan bahasa Go). Cara instalasinya cukup mudah:
```bash
-go get -u github.com/golang/protobuf/protoc-gen-go
+go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
```
-Wajib mengeksekusi command di atas di luar Go Modules, agar dependency tersebut binary nya tersimpan dalam $GOPATH/bin. Jika tidak, maka bisa muncul masalah (see https://stackoverflow.com/a/30097929/1467988).
-
> Protobuf runtime tersedia untuk banyak bahasa selain Go, selengkapnya silakan cek https://github.com/protocolbuffers/protobuf.
-## C.30.3. Pembuatan File `.Proto`
+## C.30.3. Pembuatan File `.proto`
Siapkan satu buah folder dengan skema seperti berikut.
@@ -70,9 +68,10 @@ OK, mari kita masuk ke bagian tulis-menulis kode. Buka file `user.proto`, tulis
```protobuf
syntax = "proto3";
-
package model;
+option go_package = "./model";
+
enum UserGender {
UNDEFINED = 0;
MALE = 1;
@@ -84,7 +83,9 @@ Bahasa yang digunakan dalam file proto sangat minimalis, dan cukup mudah untuk d
Statement `syntax = "proto3";`, artinya adalah versi proto yang digunakan adalah `proto3`. Ada juga versi `proto2`, namun kita tidak menggunakannya.
-Statement `package model;`, digunakan untuk menge-set nama package dari file yang nantinya di-generate. File `user.proto` akan di-compile, menghasilkan file `user.pb.go`. File Go tersebut package-nya adalah sesuai dengan yang sudah didefinisikan di statement, pada contoh ini `model`.
+Statement `option go_package = "./model";`, artinya adalah file model yang di-generate nantinya akan diletakkan di dalam folder `model` dengan nama GO package `model`.
+
+Statement `package model;`, digunakan untuk menge-set nama package dari file proto. Untuk mencegah terjadinya konflik jika terdapat model dengan nama yang sama.
Statement `enum UserGender` digunakan untuk pendefinisian enum. Tulis nama-nama enum beserta value di dalam kurung kurawal. Keyword `UserGender` sendiri nantinya menjadi tipe enum. Value enum di protobuf hanya bisa menggunakan tipe data numerik int.
@@ -131,14 +132,23 @@ Kode protobuf di atas menghasilkan kode Go berikut.
```go
type User struct {
- Id string
- Name string
- Password string
- Gender UserGender
+ // Ini adalah field implementasi protobuf
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
+ Gender UserGender `protobuf:"varint,4,opt,name=gender,proto3,enum=model.UserGender" json:"gender,omitempty"`
}
type UserList struct {
- List []*User
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ List []*User `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"`
}
```
@@ -148,9 +158,10 @@ Sekarang beralih ke file ke-dua, `garage.proto`. Silakan tulis kode berikut.
```protobuf
syntax = "proto3";
-
package model;
+option go_package = "./model";
+
message GarageCoordinate {
float latitude = 1;
float longitude = 2;
@@ -187,25 +198,32 @@ Kembali ke topik, dua message di atas akan menghasilkan kode Go berikut.
```go
type GarageList struct {
- List []*Garage
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ List []*Garage `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"`
}
type GarageListByUser struct {
- List map[string]*GarageList
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ List map[string]*GarageList `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
```
-## C.30.4. Kompilasi File `.Proto`
+## C.30.4. Kompilasi File `.proto`
File `.proto` sudah siap, sekarang saatnya untuk meng-compile file-file tersebut agar menjadi `.go`. Kompilasi dilakukan lewat command `protoc`. Agar output berupa file Go, maka gunakan flag `--go_out`. Lebih jelasnya silakan ikut command berikut.
```bash
-cd chapter-c29/model
+protoc --go_out . model/*.proto
-PATH=$PATH:$GOPATH/bin/ protoc --go_out=. *.proto
+tree model
-tree .
-.
+model
├── garage.pb.go
├── garage.proto
├── user.pb.go
@@ -224,7 +242,6 @@ Sekarang kita akan belajar tentang pengoperasian file proto yang sudah di-genera
package main
import (
- "bytes"
"fmt"
"os"
@@ -304,7 +321,7 @@ Print salah satu objek yang sudah dibuat di atas.
fmt.Printf("# ==== Original\n %#v \n", user1)
// =========== as string
-fmt.Printf("# ==== As String\n %v \n", user1.String())
+fmt.Printf("# ==== As String\n %s \n", user1.String())
```
Jalankan aplikasi untuk melihat hasilnya.
@@ -321,23 +338,21 @@ Tambahkan kode berikut:
```go
// =========== as json string
-var buf bytes.Buffer
-err1 := (&jsonpb.Marshaler{}).Marshal(&buf, garageList)
+jsonb, err1 := protojson.Marshal(garageList)
if err1 != nil {
- fmt.Println(err1.Error())
- os.Exit(0)
+ fmt.Println(err1.Error())
+ os.Exit(0)
}
-jsonString := buf.String()
-fmt.Printf("# ==== As JSON String\n %v \n", jsonString)
+fmt.Printf("# ==== As JSON String\n %s \n", string(jsonb))
```
-Di atas kita membuat banyak objek lewat generated struct. Objek tersebut bisa dikonversi ke bentuk JSON string, caranya dengan memanfaatkan package [github.com/golang/protobuf/jsonpb](https://github.com/golang/protobuf/). Lakukan `go get` pada package jika belum punya, dan jangan lupa untuk meng-importnya pada file yang sedang dibuat.
+Di atas kita membuat banyak objek lewat generated struct. Objek tersebut bisa dikonversi ke bentuk JSON string, caranya dengan memanfaatkan package [google.golang.org/protobuf/encoding/protojson](google.golang.org/protobuf/encoding/protojson). Lakukan `go get` pada package jika belum punya, dan jangan lupa untuk meng-importnya pada file yang sedang dibuat.
```bash
-go get -u github.com/golang/protobuf@v1.3.2
+go get -u google.golang.org/protobuf
```
-Kembali ke pembahasan, buat objek pointer baru dari struct `jsonpb.Marshaler`, lalu akses method `.Marshal()`. Sisipkan objek bertipe `io.Writer` penampung hasil konversi sebagai parameter pertama (pada contoh di atas kita gunakan `bytes.Buffer`). Lalu sisipkan juga objek proto yang ingin dikonversi sebagai parameter kedua.
+Kembali ke pembahasan, untuk konversi ke json string, gunakan method `.Marshal()` pada package `protojson`. Parameter adalah objek proto pointer, dan hasilnya adalah json byte.
Jalankan aplikasi, cek hasilnya.
@@ -347,38 +362,19 @@ Selain method `.Marshal()`, konversi ke json string bisa dilakukan lewat method
#### • Konversi json string ke objek proto
-Proses unmarshal dari json string ke objek proto, bisa dilakukan lewat dua cara:
-
- - Membuat objek pointer dari struct `jsonpb.Unmarshaler`, lalu mengakses method `.Unmarshal()`. Parameter pertama diisi dengan objek `io.Reader` yang isinya json string, dan parameter kedua adalah receiver.
-
- ```go
- buf2 := strings.NewReader(jsonString)
- protoObject := new(model.GarageList)
-
- err2 := (&jsonpb.Unmarshaler{}).Unmarshal(buf2, protoObject)
- if err2 != nil {
- fmt.Println(err2.Error())
- os.Exit(0)
- }
+Proses unmarshal dari json string ke objek proto, dapat menggunakan `protojson.Unmarshal`, dengan parameter pertama disisipi data json byte dan parameter kedua disisipi objek proto pointer.
- fmt.Printf("# ==== As String\n %v \n", protoObject.String())
- ```
-
- - Menggunakan `jsonpb.UnmarshalString`, dengan parameter pertama disisipi data json string.
-
- ```go
- protoObject := new(model.GarageList)
-
- err2 := jsonpb.UnmarshalString(jsonString, protoObject)
- if err2 != nil {
- fmt.Println(err2.Error())
- os.Exit(0)
- }
+```go
+protoObject := new(model.GarageList)
+err2 := protojson.Unmarshal(jsonb, protoObject)
- fmt.Printf("# ==== As String\n %v \n", protoObject.String())
- ```
+if err2 != nil {
+ fmt.Println(err2.Error())
+ os.Exit(0)
+}
-Silakan pilih cara yang cocok sesuai kebutuhan. Lalu jalankan aplikasi dan lihat hasilnya.
+fmt.Printf("# ==== As String\n %s \n", protoObject.String())
+```

From 9d6f4342562b63c175344cd4382f28de6691709b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Fri, 22 Dec 2023 16:47:28 +0700
Subject: [PATCH 151/522] New translations contributing.md (English)
---
en/content-en/CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/CONTRIBUTING.md b/en/content-en/CONTRIBUTING.md
index 7a2eb0008..29e86cc45 100644
--- a/en/content-en/CONTRIBUTING.md
+++ b/en/content-en/CONTRIBUTING.md
@@ -26,7 +26,7 @@ Berikut merupakan *hall of fame* kontributor yang sudah berbaik hati menyisihkan
1. [Acep Saepudin](https://github.com/acepsaepudin)
1. [Adev Saputra](https://github.com/adev22)
-1. [Afifurrohman](afifurrohman-id)
+1. [Afifurrohman](https://github.com/afifurrohman-id)
1. [Agus Budiono](https://github.com/dyon048)
1. [Ahmad Syafiq Aqil Wafi](https://github.com/Syafiqjos)
1. [Akul Nurislamimanudin](https://github.com/akulnurislam)
From 0c81a14ad82f5d1ac26cd573538aa72dac6dad3b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:42:55 +0700
Subject: [PATCH 152/522] New translations c-golang-grpc-protobuf.md (English)
---
en/content-en/C-golang-grpc-protobuf.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-grpc-protobuf.md b/en/content-en/C-golang-grpc-protobuf.md
index 1cd181fad..6b40f3c2d 100644
--- a/en/content-en/C-golang-grpc-protobuf.md
+++ b/en/content-en/C-golang-grpc-protobuf.md
@@ -547,4 +547,4 @@ OK, jika anda membaca sampai baris ini, berarti anda telah berhasil sabar dalam
---
-
+
From 2c3d2ea2c908b7ded56b0bb31c82925c73f9fad7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:42:56 +0700
Subject: [PATCH 153/522] New translations c-golang-protobuf-implementation.md
(English)
---
en/content-en/C-golang-protobuf-implementation.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-protobuf-implementation.md b/en/content-en/C-golang-protobuf-implementation.md
index ce744f236..937b7c23f 100644
--- a/en/content-en/C-golang-protobuf-implementation.md
+++ b/en/content-en/C-golang-protobuf-implementation.md
@@ -395,4 +395,4 @@ Pada chapter selanjutnya kita akan belajar tentang penerapan gRPC dan protobuf.
---
-
+
From 9da44ae812a6866a689b96adf3cf27f6806187ce Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:42:57 +0700
Subject: [PATCH 154/522] New translations 1-berkenalan-dengan-golang.md
(English)
---
en/content-en/1-berkenalan-dengan-golang.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/en/content-en/1-berkenalan-dengan-golang.md b/en/content-en/1-berkenalan-dengan-golang.md
index e416a159d..d69e8f6d7 100644
--- a/en/content-en/1-berkenalan-dengan-golang.md
+++ b/en/content-en/1-berkenalan-dengan-golang.md
@@ -26,4 +26,5 @@ Pada buku ini (terutama semua serial chapter A) kita akan belajar tentang dasar
---
-
+
+
From e7cb4fd7642e4675d904aaa392b29da6cecda48d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:42:58 +0700
Subject: [PATCH 155/522] New translations 2-instalasi-golang.md (English)
---
en/content-en/2-instalasi-golang.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/2-instalasi-golang.md b/en/content-en/2-instalasi-golang.md
index 88e8ee6c3..36332a3c4 100644
--- a/en/content-en/2-instalasi-golang.md
+++ b/en/content-en/2-instalasi-golang.md
@@ -103,4 +103,4 @@ Jika pembaca tertarik untuk mencoba versi development Go, ingin mencoba fitur ya
---
-
+
From 052ee5c26fd5ecb9a3199919b1c6aeee18100b31 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:00 +0700
Subject: [PATCH 156/522] New translations a-array.md (English)
---
en/content-en/A-array.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-array.md b/en/content-en/A-array.md
index 0bc7abfd1..10af138d1 100644
--- a/en/content-en/A-array.md
+++ b/en/content-en/A-array.md
@@ -203,4 +203,4 @@ Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diingi
---
-
+
From 91ce0771431500db6c559f30661a699550f0543b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:01 +0700
Subject: [PATCH 157/522] New translations a-buffered-channel.md (English)
---
en/content-en/A-buffered-channel.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-buffered-channel.md b/en/content-en/A-buffered-channel.md
index cdac3e3dd..fdbdc95a8 100644
--- a/en/content-en/A-buffered-channel.md
+++ b/en/content-en/A-buffered-channel.md
@@ -74,4 +74,4 @@ Lebih detailnya mengenai fungsi `time.Sleep()` dan `time.Second` dibahas pada ch
---
-
+
From 2fb7e658309ab19f8cb3485a2bc726d51b05638c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:02 +0700
Subject: [PATCH 158/522] New translations a-channel-range-close.md (English)
---
en/content-en/A-channel-range-close.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-channel-range-close.md b/en/content-en/A-channel-range-close.md
index 1387beb16..e63aa9b4b 100644
--- a/en/content-en/A-channel-range-close.md
+++ b/en/content-en/A-channel-range-close.md
@@ -78,4 +78,4 @@ Dan sebaliknya pada fungsi `printMessage(ch <-chan string)`, channel `ch` hanya
---
-
+
From ce2ea0f3499e821d1d2c256c3a9ccad64d77d380 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:03 +0700
Subject: [PATCH 159/522] New translations a-channel-select.md (English)
---
en/content-en/A-channel-select.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-channel-select.md b/en/content-en/A-channel-select.md
index 4ea4c87ad..3f45edaf0 100644
--- a/en/content-en/A-channel-select.md
+++ b/en/content-en/A-channel-select.md
@@ -87,4 +87,4 @@ Cukup mudah bukan?
---
-
+
From 81cbf96b550a132262ee7181631dbf77957ab432 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:04 +0700
Subject: [PATCH 160/522] New translations a-channel-timeout.md (English)
---
en/content-en/A-channel-timeout.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-channel-timeout.md b/en/content-en/A-channel-timeout.md
index 4ee5d50bc..23241f6af 100644
--- a/en/content-en/A-channel-timeout.md
+++ b/en/content-en/A-channel-timeout.md
@@ -74,4 +74,4 @@ Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika ti
---
-
+
From da0d8165f5e253cd5853f66d7fab799b74bbc4b3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:04 +0700
Subject: [PATCH 161/522] New translations a-channel.md (English)
---
en/content-en/A-channel.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-channel.md b/en/content-en/A-channel.md
index d63fd9c2b..1a65b1e28 100644
--- a/en/content-en/A-channel.md
+++ b/en/content-en/A-channel.md
@@ -165,4 +165,4 @@ fmt.Println(message)
---
-
+
From eb40f2e7a72cb7099405c5bd2a02aa41c373bdaa Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:05 +0700
Subject: [PATCH 162/522] New translations a-client-http-request-simple.md
(English)
---
en/content-en/A-client-http-request-simple.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-client-http-request-simple.md b/en/content-en/A-client-http-request-simple.md
index f2c86f211..00189b2fe 100644
--- a/en/content-en/A-client-http-request-simple.md
+++ b/en/content-en/A-client-http-request-simple.md
@@ -179,4 +179,4 @@ Kita telah mempelajari bagaimana cara membuat http request sederhana untuk kirim
---
-
+
From 7a8335b981e06896b6c548c7d749a5ca5b5d4846 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:06 +0700
Subject: [PATCH 163/522] New translations a-command-line-args-flag.md
(English)
---
en/content-en/A-command-line-args-flag.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-command-line-args-flag.md b/en/content-en/A-command-line-args-flag.md
index 50a18528b..4ba0648d5 100644
--- a/en/content-en/A-command-line-args-flag.md
+++ b/en/content-en/A-command-line-args-flag.md
@@ -138,4 +138,4 @@ Kegunaan dari parameter terakhir method-method flag adalah untuk memunculkan hin
---
-
+
From d60b7267065d25bcab151d7661044e81c3592cd5 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:07 +0700
Subject: [PATCH 164/522] New translations a-concurrency-pipeline.md (English)
---
en/content-en/A-concurrency-pipeline.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-concurrency-pipeline.md b/en/content-en/A-concurrency-pipeline.md
index 368958e85..13470a522 100644
--- a/en/content-en/A-concurrency-pipeline.md
+++ b/en/content-en/A-concurrency-pipeline.md
@@ -543,4 +543,4 @@ Ok sekian untuk chapter panjang ini.
---
-
+
From 8e5180558c8c9997cf1ac29f8495e1d0702dde2e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:08 +0700
Subject: [PATCH 165/522] New translations a-data-type-conversion.md (English)
---
en/content-en/A-data-type-conversion.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-data-type-conversion.md b/en/content-en/A-data-type-conversion.md
index aaf9ee53e..bfbd3b880 100644
--- a/en/content-en/A-data-type-conversion.md
+++ b/en/content-en/A-data-type-conversion.md
@@ -251,4 +251,4 @@ Kombinasi `switch` - `case` bisa dimanfaatkan untuk deteksi tipe konkret data ya
---
-
+
From 32f69e9e0ef53d20af3443b4ddc971fd830a7261 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:09 +0700
Subject: [PATCH 166/522] New translations a-defer-exit.md (English)
---
en/content-en/A-defer-exit.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-defer-exit.md b/en/content-en/A-defer-exit.md
index 4182dfe32..a039df9d5 100644
--- a/en/content-en/A-defer-exit.md
+++ b/en/content-en/A-defer-exit.md
@@ -137,4 +137,4 @@ Meskipun `defer fmt.Println("halo")` ditempatkan sebelum `os.Exit()`, statement
---
-
+
From 5321dc8e9faccf7a68e2e7c623ef8bdea49d390a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:10 +0700
Subject: [PATCH 167/522] New translations a-encoding-base64.md (English)
---
en/content-en/A-encoding-base64.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-encoding-base64.md b/en/content-en/A-encoding-base64.md
index 5accb3b3a..08a092cd0 100644
--- a/en/content-en/A-encoding-base64.md
+++ b/en/content-en/A-encoding-base64.md
@@ -89,4 +89,4 @@ fmt.Println(decodedString)
---
-
+
From ce0ae98886dbfcb3f9aae04b0eab32794002a015 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:11 +0700
Subject: [PATCH 168/522] New translations a-error-panic-recover.md (English)
---
en/content-en/A-error-panic-recover.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-error-panic-recover.md b/en/content-en/A-error-panic-recover.md
index f8340b042..147ff9b7b 100644
--- a/en/content-en/A-error-panic-recover.md
+++ b/en/content-en/A-error-panic-recover.md
@@ -218,4 +218,4 @@ Pada kode di atas, bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk r
---
-
+
From de6f55176e872c369ed85c31a4021b8d3d7ad29d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:12 +0700
Subject: [PATCH 169/522] New translations a-exec.md (English)
---
en/content-en/A-exec.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-exec.md b/en/content-en/A-exec.md
index 48d9d5b60..d472f8544 100644
--- a/en/content-en/A-exec.md
+++ b/en/content-en/A-exec.md
@@ -59,4 +59,4 @@ Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi denga
---
-
+
From f02972cc26dbe4048c622349a274277d571f72b4 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:13 +0700
Subject: [PATCH 170/522] New translations a-file.md (English)
---
en/content-en/A-file.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-file.md b/en/content-en/A-file.md
index 70093aefc..630b1d958 100644
--- a/en/content-en/A-file.md
+++ b/en/content-en/A-file.md
@@ -160,4 +160,4 @@ func main() {
---
-
+
From 2ae93b80374b4e81c4ad8654fdc83548c9f146d3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:14 +0700
Subject: [PATCH 171/522] New translations a-fungsi-closure.md (English)
---
en/content-en/A-fungsi-closure.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-fungsi-closure.md b/en/content-en/A-fungsi-closure.md
index 86c9d0c2e..6130d674f 100644
--- a/en/content-en/A-fungsi-closure.md
+++ b/en/content-en/A-fungsi-closure.md
@@ -176,4 +176,4 @@ Output program:
---
-
+
From d927d41c0e2af98a6e2b4d3af98d984e327440fc Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:15 +0700
Subject: [PATCH 172/522] New translations a-fungsi-multiple-return.md
(English)
---
en/content-en/A-fungsi-multiple-return.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-fungsi-multiple-return.md b/en/content-en/A-fungsi-multiple-return.md
index 131ff118a..796983568 100644
--- a/en/content-en/A-fungsi-multiple-return.md
+++ b/en/content-en/A-fungsi-multiple-return.md
@@ -105,4 +105,4 @@ Fungsi `math.Pow()` digunakan untuk memangkat nilai. `math.Pow(2, 3)` berarti 2
---
-
+
From dda8a95be2cf8b0d7f73337ccbfa784b46e3a615 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:16 +0700
Subject: [PATCH 173/522] New translations a-fungsi-sebagai-parameter.md
(English)
---
en/content-en/A-fungsi-sebagai-parameter.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-fungsi-sebagai-parameter.md b/en/content-en/A-fungsi-sebagai-parameter.md
index 90c9fa18b..8eacd0a12 100644
--- a/en/content-en/A-fungsi-sebagai-parameter.md
+++ b/en/content-en/A-fungsi-sebagai-parameter.md
@@ -106,4 +106,4 @@ Variabel `result` bernilai `true` karena string `"ang"` merupakan bagian dari st
---
-
+
From b1b61accc60fca9fa22eb366da67a256759e3b8e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:17 +0700
Subject: [PATCH 174/522] New translations a-fungsi-variadic.md (English)
---
en/content-en/A-fungsi-variadic.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-fungsi-variadic.md b/en/content-en/A-fungsi-variadic.md
index eaf48b82b..75e54f00a 100644
--- a/en/content-en/A-fungsi-variadic.md
+++ b/en/content-en/A-fungsi-variadic.md
@@ -153,4 +153,4 @@ Output program:
---
-
+
From e9c16003141b39beb188c845d27cbbc47def1ddd Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:18 +0700
Subject: [PATCH 175/522] New translations a-fungsi.md (English)
---
en/content-en/A-fungsi.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-fungsi.md b/en/content-en/A-fungsi.md
index 6879cfb08..eec5f2edb 100644
--- a/en/content-en/A-fungsi.md
+++ b/en/content-en/A-fungsi.md
@@ -176,4 +176,4 @@ Di dalamnya terdapat proses validasi nilai variabel pembagi, jika nilainya adala
---
-
+
From 52568fbb3b98708eb41af01d6c29affe7138590f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:19 +0700
Subject: [PATCH 176/522] New translations a-go-command.md (English)
---
en/content-en/A-go-command.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-go-command.md b/en/content-en/A-go-command.md
index 051c70ecc..237ba3599 100644
--- a/en/content-en/A-go-command.md
+++ b/en/content-en/A-go-command.md
@@ -106,4 +106,4 @@ Command ini digunakan untuk vendoring. Lebih detailnya akan dibahas di akhir ser
---
-
+
From 7edd5fafa50d828c5f32fc2af1b45e615bab2e8d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:20 +0700
Subject: [PATCH 177/522] New translations a-go-vendoring.md (English)
---
en/content-en/A-go-vendoring.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-go-vendoring.md b/en/content-en/A-go-vendoring.md
index ec4230926..c700a0900 100644
--- a/en/content-en/A-go-vendoring.md
+++ b/en/content-en/A-go-vendoring.md
@@ -77,4 +77,4 @@ Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan sa
---
-
+
From 9f95e8d9203beef6be41ff2be7fdf27f26f47b70 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:21 +0700
Subject: [PATCH 178/522] New translations a-golang-generics.md (English)
---
en/content-en/A-golang-generics.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-golang-generics.md b/en/content-en/A-golang-generics.md
index 320f19d5f..a25e239e0 100644
--- a/en/content-en/A-golang-generics.md
+++ b/en/content-en/A-golang-generics.md
@@ -280,4 +280,4 @@ Ok, sekian pembahasan mengenai generics. Jika ada update perihal generic API aka
---
-
+
From 642f89290162811a773450c46489b685e524d43e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:22 +0700
Subject: [PATCH 179/522] New translations a-gopath-dan-workspace.md (English)
---
en/content-en/A-gopath-dan-workspace.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-gopath-dan-workspace.md b/en/content-en/A-gopath-dan-workspace.md
index e4c9808c4..311eabbb9 100644
--- a/en/content-en/A-gopath-dan-workspace.md
+++ b/en/content-en/A-gopath-dan-workspace.md
@@ -41,4 +41,4 @@ Struktur di atas merupakan struktur standar workspace Go. Jadi pastikan penamaan
---
-
+
From 68d3c785746dd3f951da246449a0768ec766de68 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:22 +0700
Subject: [PATCH 180/522] New translations a-goroutine.md (English)
---
en/content-en/A-goroutine.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-goroutine.md b/en/content-en/A-goroutine.md
index 5b0941937..653650da0 100644
--- a/en/content-en/A-goroutine.md
+++ b/en/content-en/A-goroutine.md
@@ -93,4 +93,4 @@ Bisa dilihat pada kode di atas, untuk menampung inputan text `trafalgar d law`,
---
-
+
From adac47f649b599600c512246aa281fec0c5bda84 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:23 +0700
Subject: [PATCH 181/522] New translations a-hash-sha1.md (English)
---
en/content-en/A-hash-sha1.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-hash-sha1.md b/en/content-en/A-hash-sha1.md
index 19db57e11..c6128c378 100644
--- a/en/content-en/A-hash-sha1.md
+++ b/en/content-en/A-hash-sha1.md
@@ -104,4 +104,4 @@ Metode ini sering dipakai untuk enkripsi password user. Salt dan data hasil hash
---
-
+
From 336a435aea58d1f9edd9090f11aada3d1bffd4fc Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:24 +0700
Subject: [PATCH 182/522] New translations a-hello-world.md (English)
---
en/content-en/A-hello-world.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-hello-world.md b/en/content-en/A-hello-world.md
index 9bfde32ba..7718ab007 100644
--- a/en/content-en/A-hello-world.md
+++ b/en/content-en/A-hello-world.md
@@ -131,4 +131,4 @@ Contoh statement di atas akan menghasilkan output: **Hello world! how are you**.
---
-
+
From c471afd96f575fe787ba1cb38d443040f9aae512 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:25 +0700
Subject: [PATCH 183/522] New translations a-instalasi-editor.md (English)
---
en/content-en/A-instalasi-editor.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-instalasi-editor.md b/en/content-en/A-instalasi-editor.md
index 89aebec69..c18c770d0 100644
--- a/en/content-en/A-instalasi-editor.md
+++ b/en/content-en/A-instalasi-editor.md
@@ -47,4 +47,4 @@ indent_size = 8
---
-
+
From e24f3f1cedc9e1fa9ecf6b6b4b5ff0da0ee0e1c2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:26 +0700
Subject: [PATCH 184/522] New translations a-interface-kosong.md (English)
---
en/content-en/A-interface-kosong.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-interface-kosong.md b/en/content-en/A-interface-kosong.md
index 519cbce00..c182977c2 100644
--- a/en/content-en/A-interface-kosong.md
+++ b/en/content-en/A-interface-kosong.md
@@ -154,4 +154,4 @@ for _, each := range fruits {
---
-
+
From a5ab3bb48b7d758a7241ac50398f47521fe72ad3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:27 +0700
Subject: [PATCH 185/522] New translations a-interface.md (English)
---
en/content-en/A-interface.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-interface.md b/en/content-en/A-interface.md
index b98b17d53..31631a719 100644
--- a/en/content-en/A-interface.md
+++ b/en/content-en/A-interface.md
@@ -183,4 +183,4 @@ Pada chapter [A.23. Pointer](/A-pointer.html) dijelaskan bahwa method pointer bi
---
-
+
From 4f2bc074e55e9115e9784cf315e2dee950a6e762 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:28 +0700
Subject: [PATCH 186/522] New translations a-json.md (English)
---
en/content-en/A-json.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-json.md b/en/content-en/A-json.md
index 73bbcd034..72ce09b14 100644
--- a/en/content-en/A-json.md
+++ b/en/content-en/A-json.md
@@ -138,4 +138,4 @@ Output:
---
-
+
From 48a92ba7a64c8e716f295e641f92629b379dd7c0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:29 +0700
Subject: [PATCH 187/522] New translations a-komentar.md (English)
---
en/content-en/A-komentar.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-komentar.md b/en/content-en/A-komentar.md
index 31718e5bf..ba737d4ba 100644
--- a/en/content-en/A-komentar.md
+++ b/en/content-en/A-komentar.md
@@ -53,4 +53,4 @@ Sifat komentar ini sama seperti komentar inline, yaitu sama-sama diabaikan oleh
---
-
+
From d5a49753092c7fbdb1dd4d44610062f8f517c9b9 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:29 +0700
Subject: [PATCH 188/522] New translations a-konstanta.md (English)
---
en/content-en/A-konstanta.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-konstanta.md b/en/content-en/A-konstanta.md
index 5cd147327..52c3a60e5 100644
--- a/en/content-en/A-konstanta.md
+++ b/en/content-en/A-konstanta.md
@@ -108,4 +108,4 @@ const three, four string = "tiga", "empat"
---
-
+
From 271e555b55e3a211d62a7dcac25585314f63d9d2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:30 +0700
Subject: [PATCH 189/522] New translations a-map.md (English)
---
en/content-en/A-map.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-map.md b/en/content-en/A-map.md
index 7d1e06cd2..efa4293b8 100644
--- a/en/content-en/A-map.md
+++ b/en/content-en/A-map.md
@@ -179,4 +179,4 @@ var data = []map[string]string{
---
-
+
From 5b5108a715177f64a6bb631579d92cc0e2bbed29 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:31 +0700
Subject: [PATCH 190/522] New translations a-method.md (English)
---
en/content-en/A-method.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-method.md b/en/content-en/A-method.md
index 32afeb085..491676ade 100644
--- a/en/content-en/A-method.md
+++ b/en/content-en/A-method.md
@@ -156,4 +156,4 @@ Lebih detailnya akan dibahas pada chapter selanjutnya.
---
-
+
From 844d5ce8b81c01ef6671e4d31e4a1fceb7a0f62c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:32 +0700
Subject: [PATCH 191/522] New translations a-mongodb.md (English)
---
en/content-en/A-mongodb.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-mongodb.md b/en/content-en/A-mongodb.md
index fd7af9fcc..4a69376de 100644
--- a/en/content-en/A-mongodb.md
+++ b/en/content-en/A-mongodb.md
@@ -333,4 +333,4 @@ if len(result) > 0 {
---
-
+
From a9ec8b2b76fd8b67a32ce6815221c33341ca0279 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:33 +0700
Subject: [PATCH 192/522] New translations a-mutex.md (English)
---
en/content-en/A-mutex.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-mutex.md b/en/content-en/A-mutex.md
index 910ce03a0..aeaab6a74 100644
--- a/en/content-en/A-mutex.md
+++ b/en/content-en/A-mutex.md
@@ -178,4 +178,4 @@ func main() {
---
-
+
From e005bb2c0b94c5498d34419a68fd6729d8c74110 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:34 +0700
Subject: [PATCH 193/522] New translations a-operator.md (English)
---
en/content-en/A-operator.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-operator.md b/en/content-en/A-operator.md
index 4d3675421..42e4334d5 100644
--- a/en/content-en/A-operator.md
+++ b/en/content-en/A-operator.md
@@ -99,4 +99,4 @@ Template `\t` digunakan untuk menambahkan indent tabulasi. Biasa dimanfaatkan un
---
-
+
From 15a6876dc8dd7a622e10eaa3b320fa49f76d5ca3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:35 +0700
Subject: [PATCH 194/522] New translations a-perulangan.md (English)
---
en/content-en/A-perulangan.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-perulangan.md b/en/content-en/A-perulangan.md
index 7a935f0b4..5f7da9225 100644
--- a/en/content-en/A-perulangan.md
+++ b/en/content-en/A-perulangan.md
@@ -163,4 +163,4 @@ Pada `for` bagian dalam, terdapat seleksi kondisi untuk pengecekan nilai `i`. Ke
---
-
+
From acaa56240b435e3d9fac1b49de330a969b2420df Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:36 +0700
Subject: [PATCH 195/522] New translations a-pipeline-context-cancellation.md
(English)
---
en/content-en/A-pipeline-context-cancellation.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-pipeline-context-cancellation.md b/en/content-en/A-pipeline-context-cancellation.md
index 4554282cc..a165b6d56 100644
--- a/en/content-en/A-pipeline-context-cancellation.md
+++ b/en/content-en/A-pipeline-context-cancellation.md
@@ -454,4 +454,4 @@ Perbedannya ada pada penerapan *cancellation*-nya. Pada contoh ini kita tidak me
---
-
+
From e5b4f719292555f6f928c9fcddcbd368a4aed67c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:37 +0700
Subject: [PATCH 196/522] New translations a-pointer.md (English)
---
en/content-en/A-pointer.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-pointer.md b/en/content-en/A-pointer.md
index 30112cefb..53117e3b4 100644
--- a/en/content-en/A-pointer.md
+++ b/en/content-en/A-pointer.md
@@ -107,4 +107,4 @@ Nilai variabel `number` berubah menjadi `10` karena perubahan yang terjadi di da
---
-
+
From 3ebd5197bb286e79f9d1cb236164ea9ba2a97187 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:38 +0700
Subject: [PATCH 197/522] New translations a-properti-public-dan-private.md
(English)
---
en/content-en/A-properti-public-dan-private.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-properti-public-dan-private.md b/en/content-en/A-properti-public-dan-private.md
index 550fe82c6..fd2bb9770 100644
--- a/en/content-en/A-properti-public-dan-private.md
+++ b/en/content-en/A-properti-public-dan-private.md
@@ -336,4 +336,4 @@ Dalam sebuah package diperbolehkan ada banyak fungsi `init()` (urutan eksekusiny
---
-
+
From bc85682719b3fbc584296cfba950350d7661d89f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:39 +0700
Subject: [PATCH 198/522] New translations a-random.md (English)
---
en/content-en/A-random.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-random.md b/en/content-en/A-random.md
index 4e7e884fb..58f2113a0 100644
--- a/en/content-en/A-random.md
+++ b/en/content-en/A-random.md
@@ -123,4 +123,4 @@ Dengan fungsi di atas kita bisa dengan mudah meng-generate string random dengan
---
-
+
From b7b899c2575457949e8ff2ac8c3c88b5797d76f5 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:40 +0700
Subject: [PATCH 199/522] New translations a-reflect.md (English)
---
en/content-en/A-reflect.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-reflect.md b/en/content-en/A-reflect.md
index 5be4db6ec..74ce3a853 100644
--- a/en/content-en/A-reflect.md
+++ b/en/content-en/A-reflect.md
@@ -172,4 +172,4 @@ Jika eksekusi method diikuti pengisian parameter, maka parameternya harus dituli
---
-
+
From 48f2a653f53dad23bd6e6ef93961189b622cb3d7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:40 +0700
Subject: [PATCH 200/522] New translations a-regex.md (English)
---
en/content-en/A-regex.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-regex.md b/en/content-en/A-regex.md
index 4ef767fa6..0bc35c9ea 100644
--- a/en/content-en/A-regex.md
+++ b/en/content-en/A-regex.md
@@ -171,4 +171,4 @@ Pada contoh di atas, ekspresi regexp `[a-b]+` digunakan sebagai kriteria split.
---
-
+
From a2a1b4a86680d1504eebfe5d89842707e09f67ce Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:41 +0700
Subject: [PATCH 201/522] New translations a-seleksi-kondisi.md (English)
---
en/content-en/A-seleksi-kondisi.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-seleksi-kondisi.md b/en/content-en/A-seleksi-kondisi.md
index c8e7988e2..d8b7adeef 100644
--- a/en/content-en/A-seleksi-kondisi.md
+++ b/en/content-en/A-seleksi-kondisi.md
@@ -209,4 +209,4 @@ if point > 7 {
---
-
+
From c121dfd18c0ebedeb24f3b58060f9250ad425597 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:42 +0700
Subject: [PATCH 202/522] New translations
a-setup-go-project-dengan-go-modules.md (English)
---
en/content-en/A-setup-go-project-dengan-go-modules.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-setup-go-project-dengan-go-modules.md b/en/content-en/A-setup-go-project-dengan-go-modules.md
index 83e992a3c..392ce8a5a 100644
--- a/en/content-en/A-setup-go-project-dengan-go-modules.md
+++ b/en/content-en/A-setup-go-project-dengan-go-modules.md
@@ -50,4 +50,4 @@ O iya, sebenarnya selain Go Modules, setup project di Go juga bisa menggunakan `
---
-
+
From b835fe2ff083e9ccf4267d02194d8e4456acac78 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:43 +0700
Subject: [PATCH 203/522] New translations
a-simplified-fan-in-fan-out-pipeline.md (English)
---
en/content-en/A-simplified-fan-in-fan-out-pipeline.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-simplified-fan-in-fan-out-pipeline.md b/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
index bc8042795..c01ada997 100644
--- a/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
+++ b/en/content-en/A-simplified-fan-in-fan-out-pipeline.md
@@ -340,4 +340,4 @@ Nah dari sini semoga cukup jelas ya bedanya kalau dari sisi performa. Inilah pen
---
-
+
From 8091ecd47f5615787ef9599c4dd3705181ae4265 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:44 +0700
Subject: [PATCH 204/522] New translations a-slice.md (English)
---
en/content-en/A-slice.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-slice.md b/en/content-en/A-slice.md
index f913e377f..9b9cf3471 100644
--- a/en/content-en/A-slice.md
+++ b/en/content-en/A-slice.md
@@ -264,4 +264,4 @@ fmt.Println(cap(bFruits)) // cap: 2
---
-
+
From 5c397d4053165474684035939a93b3fc55b1359d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:45 +0700
Subject: [PATCH 205/522] New translations a-sql.md (English)
---
en/content-en/A-sql.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-sql.md b/en/content-en/A-sql.md
index 39c3e20aa..8aa7e66c1 100644
--- a/en/content-en/A-sql.md
+++ b/en/content-en/A-sql.md
@@ -344,4 +344,4 @@ Selengkapya mengenai driver yang tersedia bisa dilihat di [https://github.com/go
---
-
+
From 497bd025b662624676dee6fc85fde45dd1583588 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:46 +0700
Subject: [PATCH 206/522] New translations a-string-format.md (English)
---
en/content-en/A-string-format.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-string-format.md b/en/content-en/A-string-format.md
index 3c75e69d4..2ca787e1d 100644
--- a/en/content-en/A-string-format.md
+++ b/en/content-en/A-string-format.md
@@ -276,4 +276,4 @@ fmt.Printf("%%\n")
---
-
+
From c828e793d08bc8c82f2d23d8509dbaadd08b53ee Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:47 +0700
Subject: [PATCH 207/522] New translations a-strings.md (English)
---
en/content-en/A-strings.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-strings.md b/en/content-en/A-strings.md
index 5eb0e25a9..1ca479fae 100644
--- a/en/content-en/A-strings.md
+++ b/en/content-en/A-strings.md
@@ -164,4 +164,4 @@ fmt.Println(str) // "EAT!"
---
-
+
From 9c1a3988ccdb4fb13417ac721b605380676d5c17 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:48 +0700
Subject: [PATCH 208/522] New translations a-struct.md (English)
---
en/content-en/A-struct.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-struct.md b/en/content-en/A-struct.md
index 06bc64194..1164d6c0b 100644
--- a/en/content-en/A-struct.md
+++ b/en/content-en/A-struct.md
@@ -416,4 +416,4 @@ var num Number = 12
---
-
+
From 12d5b487896483bf163bf1c0974b0be26273507c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:49 +0700
Subject: [PATCH 209/522] New translations a-time-duration.md (English)
---
en/content-en/A-time-duration.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-time-duration.md b/en/content-en/A-time-duration.md
index c74c8fd10..bed781829 100644
--- a/en/content-en/A-time-duration.md
+++ b/en/content-en/A-time-duration.md
@@ -128,4 +128,4 @@ duration := time.Duration(n) * time.Second
---
-
+
From 14d0707766b96a19494ad9af83ba7214095ca450 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:50 +0700
Subject: [PATCH 210/522] New translations a-time-parsing-format.md (English)
---
en/content-en/A-time-parsing-format.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-time-parsing-format.md b/en/content-en/A-time-parsing-format.md
index 795ada37d..8b01323bd 100644
--- a/en/content-en/A-time-parsing-format.md
+++ b/en/content-en/A-time-parsing-format.md
@@ -220,4 +220,4 @@ Kode di atas menghasilkan error karena format tidak sesuai dengan skema data yan
---
-
+
From 154f44793d5f4dd932cef601aa2ca24006f9b3fc Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:51 +0700
Subject: [PATCH 211/522] New translations a-timer-ticker-scheduler.md
(English)
---
en/content-en/A-timer-ticker-scheduler.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-timer-ticker-scheduler.md b/en/content-en/A-timer-ticker-scheduler.md
index 56b6d8985..3f115f4ee 100644
--- a/en/content-en/A-timer-ticker-scheduler.md
+++ b/en/content-en/A-timer-ticker-scheduler.md
@@ -203,4 +203,4 @@ Ketika user tidak menginputkan apa-apa dalam kurun waktu 5 detik, maka akan munc
---
-
+
From 995f33740314979932e4f4875c43590838001126 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:52 +0700
Subject: [PATCH 212/522] New translations a-tipe-data.md (English)
---
en/content-en/A-tipe-data.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-tipe-data.md b/en/content-en/A-tipe-data.md
index cdb9ff5d0..cf303e338 100644
--- a/en/content-en/A-tipe-data.md
+++ b/en/content-en/A-tipe-data.md
@@ -125,4 +125,4 @@ Nantinya kita akan sering bertemu dengan `nil` setelah masuk pada pembahasan-pem
---
-
+
From 5f3272697c0b3340b398515ecf29d868857284ff Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:53 +0700
Subject: [PATCH 213/522] New translations a-unit-test.md (English)
---
en/content-en/A-unit-test.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-unit-test.md b/en/content-en/A-unit-test.md
index 2a9655109..6478c54ad 100644
--- a/en/content-en/A-unit-test.md
+++ b/en/content-en/A-unit-test.md
@@ -195,4 +195,4 @@ Fungsi `assert.Equal()` digunakan untuk uji perbandingan. Parameter ke-2 dibandi
---
-
+
From 88c34ed6d06fbc2eff26dacafdd97fcbc1b6202d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:53 +0700
Subject: [PATCH 214/522] New translations a-url-parsing.md (English)
---
en/content-en/A-url-parsing.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-url-parsing.md b/en/content-en/A-url-parsing.md
index 0e395223e..bade0106e 100644
--- a/en/content-en/A-url-parsing.md
+++ b/en/content-en/A-url-parsing.md
@@ -45,4 +45,4 @@ Selain itu, query yang ada pada url akan otomatis diparsing juga, menjadi bentuk
---
-
+
From c30c74c79724db174745f14eff59e27c8cddefdf Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:54 +0700
Subject: [PATCH 215/522] New translations a-variabel.md (English)
---
en/content-en/A-variabel.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-variabel.md b/en/content-en/A-variabel.md
index cd19206f6..39e017488 100644
--- a/en/content-en/A-variabel.md
+++ b/en/content-en/A-variabel.md
@@ -189,4 +189,4 @@ Dan lagi, mungkin banyak yang akan bingung. Ketika sudah masuk ke pembahasan mas
---
-
+
From 4219426902f13f58bd82670b744d64351ef4754c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:55 +0700
Subject: [PATCH 216/522] New translations a-waitgroup.md (English)
---
en/content-en/A-waitgroup.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-waitgroup.md b/en/content-en/A-waitgroup.md
index 0e218bb6d..25b6c57a0 100644
--- a/en/content-en/A-waitgroup.md
+++ b/en/content-en/A-waitgroup.md
@@ -78,4 +78,4 @@ Kombinasi yang tepat antara `sync.WaitGroup` dan channel sangat penting, keduany
---
-
+
From 9e0afee61bf0a93836015625279b04105913f74b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:56 +0700
Subject: [PATCH 217/522] New translations a-web-server.md (English)
---
en/content-en/A-web-server.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-web-server.md b/en/content-en/A-web-server.md
index 0224b3ebb..f4cc144fc 100644
--- a/en/content-en/A-web-server.md
+++ b/en/content-en/A-web-server.md
@@ -131,4 +131,4 @@ Sampai chapter ini yang kita pelajari adalah dasar-dasar pemrograman Go, dibahas
---
-
+
From daf6aca9c7077268f0d8991fe92adee0ea6693b1 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:57 +0700
Subject: [PATCH 218/522] New translations a-web-service-api.md (English)
---
en/content-en/A-web-service-api.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-web-service-api.md b/en/content-en/A-web-service-api.md
index 4ece8ea3a..c2086a850 100644
--- a/en/content-en/A-web-service-api.md
+++ b/en/content-en/A-web-service-api.md
@@ -146,4 +146,4 @@ Data ID yang ingin dicari melalui endpoint /user, ditulis dengan ?id=B002 yang b
---
-
+
From d6f4f2ea746f174186ec4973fe0a55ac20903402 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:58 +0700
Subject: [PATCH 219/522] New translations b-ajax-json-payload.md (English)
---
en/content-en/B-ajax-json-payload.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-ajax-json-payload.md b/en/content-en/B-ajax-json-payload.md
index 432b0bfe8..4e742c68f 100644
--- a/en/content-en/B-ajax-json-payload.md
+++ b/en/content-en/B-ajax-json-payload.md
@@ -198,4 +198,4 @@ Gunakan fasilitas Developer Tools pada Chrome untuk melihat detail dari request.
---
-
+
From 99384fa609e8f7bf8aab7b0ee5d7c91d05d10162 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:43:59 +0700
Subject: [PATCH 220/522] New translations b-ajax-json-response.md (English)
---
en/content-en/B-ajax-json-response.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-ajax-json-response.md b/en/content-en/B-ajax-json-response.md
index edb929334..79be77ffb 100644
--- a/en/content-en/B-ajax-json-response.md
+++ b/en/content-en/B-ajax-json-response.md
@@ -91,4 +91,4 @@ Kode di atas hasilnya ekuivalen dengan penggunaan `json.Marshal`.
---
-
+
From f5e51c40ac70b3bca46c345e6614f217f433d975 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:01 +0700
Subject: [PATCH 221/522] New translations b-ajax-multi-upload.md (English)
---
en/content-en/B-ajax-multi-upload.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-ajax-multi-upload.md b/en/content-en/B-ajax-multi-upload.md
index eecf2bc4f..c6d1e878e 100644
--- a/en/content-en/B-ajax-multi-upload.md
+++ b/en/content-en/B-ajax-multi-upload.md
@@ -206,4 +206,4 @@ Cek apakah file sudah terupload.
---
-
+
From 44549d6111a1e5a6b034f5e2f147aae657a1649e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:02 +0700
Subject: [PATCH 222/522] New translations b-cookie.md (English)
---
en/content-en/B-cookie.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-cookie.md b/en/content-en/B-cookie.md
index 419456b97..91ef67fa9 100644
--- a/en/content-en/B-cookie.md
+++ b/en/content-en/B-cookie.md
@@ -132,4 +132,4 @@ Objek cookie memiliki beberapa property, beberapa di antaranya:
---
-
+
From a32f0e622d52c8b222d8b228eb551689301ac05e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:03 +0700
Subject: [PATCH 223/522] New translations b-custom-mux-multiplexer.md
(English)
---
en/content-en/B-custom-mux-multiplexer.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-custom-mux-multiplexer.md b/en/content-en/B-custom-mux-multiplexer.md
index dec0f2e46..1482d015e 100644
--- a/en/content-en/B-custom-mux-multiplexer.md
+++ b/en/content-en/B-custom-mux-multiplexer.md
@@ -88,4 +88,4 @@ Jika ada keperluan untuk menambahkan middleware baru lainnya, cukup registrasika
---
-
+
From d42d08c36eda48a8ba39bb2959d9a237e46c4eba Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:04 +0700
Subject: [PATCH 224/522] New translations b-download-file.md (English)
---
en/content-en/B-download-file.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-download-file.md b/en/content-en/B-download-file.md
index c6e19629d..050883091 100644
--- a/en/content-en/B-download-file.md
+++ b/en/content-en/B-download-file.md
@@ -226,4 +226,4 @@ Jalankan program, akses rute `/`. List semua file dalam folder `files` muncul di
---
-
+
From 6aa5ce354c20b867f1bae9e815dc64237525d805 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:05 +0700
Subject: [PATCH 225/522] New translations b-form-upload-file.md (English)
---
en/content-en/B-form-upload-file.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-form-upload-file.md b/en/content-en/B-form-upload-file.md
index c58b8b770..e7a9a96f4 100644
--- a/en/content-en/B-form-upload-file.md
+++ b/en/content-en/B-form-upload-file.md
@@ -178,4 +178,4 @@ Jalankan program, test hasilnya lewat browser.
---
-
+
From e2e24213d4a85ea4d6ab492b457f86e80cffa6fd Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:06 +0700
Subject: [PATCH 226/522] New translations b-form-value.md (English)
---
en/content-en/B-form-value.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-form-value.md b/en/content-en/B-form-value.md
index f3c1d702f..25aeb659d 100644
--- a/en/content-en/B-form-value.md
+++ b/en/content-en/B-form-value.md
@@ -138,4 +138,4 @@ OK, sekarang coba jalankan program yang telah kita buat, dan cek hasilnya.
---
-
+
From 58f1cf0c3107992822d371aa45d887c793ce85b2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:07 +0700
Subject: [PATCH 227/522] New translations b-golang-web-hello-world.md
(English)
---
en/content-en/B-golang-web-hello-world.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-golang-web-hello-world.md b/en/content-en/B-golang-web-hello-world.md
index dc943199f..07c244f54 100644
--- a/en/content-en/B-golang-web-hello-world.md
+++ b/en/content-en/B-golang-web-hello-world.md
@@ -159,4 +159,4 @@ Ada banyak lagi property dari struct `http.Server` ini, yang pastinya akan dibah
---
-
+
From e02b7c65f87d4bd8b861c6b437b99158415195fa Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:08 +0700
Subject: [PATCH 228/522] New translations b-http-basic-auth.md (English)
---
en/content-en/B-http-basic-auth.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-http-basic-auth.md b/en/content-en/B-http-basic-auth.md
index 4b98a17c6..362ae9b0d 100644
--- a/en/content-en/B-http-basic-auth.md
+++ b/en/content-en/B-http-basic-auth.md
@@ -233,4 +233,4 @@ $ curl -X GET --user batman:secret http://localhost:9000/student?id=s001
---
-
+
From 100137e885033c86faeffb5b4d34fd1a57d8615c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:09 +0700
Subject: [PATCH 229/522] New translations b-http-method-basic.md (English)
---
en/content-en/B-http-method-basic.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-http-method-basic.md b/en/content-en/B-http-method-basic.md
index 7f72e3197..463bf4baf 100644
--- a/en/content-en/B-http-method-basic.md
+++ b/en/content-en/B-http-method-basic.md
@@ -60,4 +60,4 @@ Jika method yang digunakan adalah selain POST dan GET, maka sesuai source code d
---
-
+
From 4c7f5b46e3d06e408bee08fb46dd033ed21373ad Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:10 +0700
Subject: [PATCH 230/522] New translations b-middleware-using-http-handler.md
(English)
---
en/content-en/B-middleware-using-http-handler.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-middleware-using-http-handler.md b/en/content-en/B-middleware-using-http-handler.md
index 9e26b345e..18cae9fe2 100644
--- a/en/content-en/B-middleware-using-http-handler.md
+++ b/en/content-en/B-middleware-using-http-handler.md
@@ -178,4 +178,4 @@ Dibanding metode pada chapter sebelumnya, dengan teknik ini kita bisa sangat mud
---
-
+
From ef2614cbfdae2c9a845a2a38445124315acbd3e3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:11 +0700
Subject: [PATCH 231/522] New translations b-render-html-string.md (English)
---
en/content-en/B-render-html-string.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-render-html-string.md b/en/content-en/B-render-html-string.md
index 94bdc99d3..1c2b71c02 100644
--- a/en/content-en/B-render-html-string.md
+++ b/en/content-en/B-render-html-string.md
@@ -64,4 +64,4 @@ Lakukan tes dan lihat hasilnya.
---
-
+
From 950b97d61f623d49af1a7c40204990baa8176d77 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:12 +0700
Subject: [PATCH 232/522] New translations b-render-specific-html-template.md
(English)
---
en/content-en/B-render-specific-html-template.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-render-specific-html-template.md b/en/content-en/B-render-specific-html-template.md
index 00bc4ae98..029485d47 100644
--- a/en/content-en/B-render-specific-html-template.md
+++ b/en/content-en/B-render-specific-html-template.md
@@ -82,4 +82,4 @@ Lakukan tes pada program yang telah kita buat, kurang lebih hasilnya seperti pad
---
-
+
From 5ddb6c05b6fcc99575adf732137a581e4b8a9217 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:13 +0700
Subject: [PATCH 233/522] New translations b-routing-http-handlefunc.md
(English)
---
en/content-en/B-routing-http-handlefunc.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-routing-http-handlefunc.md b/en/content-en/B-routing-http-handlefunc.md
index 1c8b843d5..0774c71ca 100644
--- a/en/content-en/B-routing-http-handlefunc.md
+++ b/en/content-en/B-routing-http-handlefunc.md
@@ -82,4 +82,4 @@ Dalam routing, handler bisa berupa fungsi, closure, ataupun anonymous function;
---
-
+
From aaef089fac7262bd4eb39357b517333c2aae2366 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:14 +0700
Subject: [PATCH 234/522] New translations b-routing-static-assets.md (English)
---
en/content-en/B-routing-static-assets.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-routing-static-assets.md b/en/content-en/B-routing-static-assets.md
index 7739e49c8..1254b354a 100644
--- a/en/content-en/B-routing-static-assets.md
+++ b/en/content-en/B-routing-static-assets.md
@@ -92,4 +92,4 @@ Routing static assets menjadi valid, karena file yang di-request akan cocok deng
---
-
+
From a1c6d97481b98de0e31629339218ec3c8325d673 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:15 +0700
Subject: [PATCH 235/522] New translations
b-server-handler-http-request-cancellation.md (English)
---
en/content-en/B-server-handler-http-request-cancellation.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-server-handler-http-request-cancellation.md b/en/content-en/B-server-handler-http-request-cancellation.md
index 366a11340..8a81c7086 100644
--- a/en/content-en/B-server-handler-http-request-cancellation.md
+++ b/en/content-en/B-server-handler-http-request-cancellation.md
@@ -124,4 +124,4 @@ curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -d '{}'
---
-
+
From d565110050f3eb571e18f3929f3799c1e2d6e9f0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:16 +0700
Subject: [PATCH 236/522] New translations b-simple-configuration.md (English)
---
en/content-en/B-simple-configuration.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-simple-configuration.md b/en/content-en/B-simple-configuration.md
index 530136e9b..5729f59aa 100644
--- a/en/content-en/B-simple-configuration.md
+++ b/en/content-en/B-simple-configuration.md
@@ -239,4 +239,4 @@ Kita akan membahas solusi dari beberapa masalah di atas pada chapter terpisah, y
---
-
+
From ada24abe5d979d91745d82ef8770f5cf8fe491c1 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:17 +0700
Subject: [PATCH 237/522] New translations b-template-actions-variables.md
(English)
---
en/content-en/B-template-actions-variables.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-template-actions-variables.md b/en/content-en/B-template-actions-variables.md
index e69d0b380..1b12e441f 100644
--- a/en/content-en/B-template-actions-variables.md
+++ b/en/content-en/B-template-actions-variables.md
@@ -253,4 +253,4 @@ Untuk seleksi kondisi yang kondisinya adalah bersumber dari variabel bertipe `bo
---
-
+
From a98bda56ceef5e0d2e4857a54e6673c4ccbd73de Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:18 +0700
Subject: [PATCH 238/522] New translations b-template-custom-functions.md
(English)
---
en/content-en/B-template-custom-functions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-template-custom-functions.md b/en/content-en/B-template-custom-functions.md
index d858b003f..8bff97bf0 100644
--- a/en/content-en/B-template-custom-functions.md
+++ b/en/content-en/B-template-custom-functions.md
@@ -114,4 +114,4 @@ Chapter selanjutnya akan membahas lebih detail mengenai penggunaan method `Parse
---
-
+
From 0479a43c4898123adb019b12c770761b3afd92cb Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:18 +0700
Subject: [PATCH 239/522] New translations b-template-functions.md (English)
---
en/content-en/B-template-functions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-template-functions.md b/en/content-en/B-template-functions.md
index f19000f23..397f19b32 100644
--- a/en/content-en/B-template-functions.md
+++ b/en/content-en/B-template-functions.md
@@ -237,4 +237,4 @@ Output:
---
-
+
From b37933ba5a6d37f004ec623a1a17049e790bed76 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:19 +0700
Subject: [PATCH 240/522] New translations b-template-render-html.md (English)
---
en/content-en/B-template-render-html.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-template-render-html.md b/en/content-en/B-template-render-html.md
index f3b9e6929..01314b6f5 100644
--- a/en/content-en/B-template-render-html.md
+++ b/en/content-en/B-template-render-html.md
@@ -145,4 +145,4 @@ Jalankan aplikasi untuk test hasil.
---
-
+
From fbf29ec49eed5d4ec1c8c5c839a4ad68a6686852 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:20 +0700
Subject: [PATCH 241/522] New translations b-template-render-partial-html.md
(English)
---
en/content-en/B-template-render-partial-html.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/B-template-render-partial-html.md b/en/content-en/B-template-render-partial-html.md
index 6bba2d5ba..ab26d061d 100644
--- a/en/content-en/B-template-render-partial-html.md
+++ b/en/content-en/B-template-render-partial-html.md
@@ -235,4 +235,4 @@ Jalankan aplikasi untuk test hasilnya.
---
-
+
From f68f720e0439b712bcdcf2e12da28f8bdbfa748d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:21 +0700
Subject: [PATCH 242/522] New translations c-advanced-configuration-viper.md
(English)
---
en/content-en/C-advanced-configuration-viper.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-advanced-configuration-viper.md b/en/content-en/C-advanced-configuration-viper.md
index 3ecdc7b26..5329ff734 100644
--- a/en/content-en/C-advanced-configuration-viper.md
+++ b/en/content-en/C-advanced-configuration-viper.md
@@ -156,4 +156,4 @@ Penggunaan fasilitas watcher memerlukan tambahan 3rd party library [fsnotify](ht
---
-
+
From 3e255e79834eb793cba8360b32203b258d09659e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:22 +0700
Subject: [PATCH 243/522] New translations c-advanced-middleware-and-logging.md
(English)
---
en/content-en/C-advanced-middleware-and-logging.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-advanced-middleware-and-logging.md b/en/content-en/C-advanced-middleware-and-logging.md
index 2fbd6c6ed..a09013016 100644
--- a/en/content-en/C-advanced-middleware-and-logging.md
+++ b/en/content-en/C-advanced-middleware-and-logging.md
@@ -277,4 +277,4 @@ Satu kata, *cantik*.
---
-
+
From 640e4f6b8dd01ae5c67ae29ab3c9f7ba771f2633 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:23 +0700
Subject: [PATCH 244/522] New translations
c-best-practice-configuration-env-var.md (English)
---
en/content-en/C-best-practice-configuration-env-var.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-best-practice-configuration-env-var.md b/en/content-en/C-best-practice-configuration-env-var.md
index 495d8c9c5..dcc0fcebf 100644
--- a/en/content-en/C-best-practice-configuration-env-var.md
+++ b/en/content-en/C-best-practice-configuration-env-var.md
@@ -163,4 +163,4 @@ Mungkin dari sini temen-temen bisa lanjut ke chapter [C.35. Dockerize Aplikasi G
---
-
+
From 281ee1bb498cb595ba810933ac9df59f3fc98ca1 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:24 +0700
Subject: [PATCH 245/522] New translations c-client-http-request-advanced.md
(English)
---
en/content-en/C-client-http-request-advanced.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-client-http-request-advanced.md b/en/content-en/C-client-http-request-advanced.md
index b6c29f0ae..78119776c 100644
--- a/en/content-en/C-client-http-request-advanced.md
+++ b/en/content-en/C-client-http-request-advanced.md
@@ -195,4 +195,4 @@ Jalankan aplikasi server, buka prompt terminal/CMD baru, lalu jalankan aplikasi
---
-
+
From b581d3336a531d8fd95d82645b7b1c01f18ea4a8 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:25 +0700
Subject: [PATCH 246/522] New translations c-convert-html-to-pdf.md (English)
---
en/content-en/C-convert-html-to-pdf.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-convert-html-to-pdf.md b/en/content-en/C-convert-html-to-pdf.md
index 1472738ed..590c3528e 100644
--- a/en/content-en/C-convert-html-to-pdf.md
+++ b/en/content-en/C-convert-html-to-pdf.md
@@ -178,4 +178,4 @@ Cara ini cocok digunakan untuk konversi data HTML yang isinya muncul pada saat p
---
-
+
From 82c330175d90bc64ac88760f357bdf3c95bfe92e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:26 +0700
Subject: [PATCH 247/522] New translations c-cors-preflight-request.md
(English)
---
en/content-en/C-cors-preflight-request.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-cors-preflight-request.md b/en/content-en/C-cors-preflight-request.md
index b1aa8c0f5..eb9238096 100644
--- a/en/content-en/C-cors-preflight-request.md
+++ b/en/content-en/C-cors-preflight-request.md
@@ -298,4 +298,4 @@ Berikut adalah list konfigurasi yang bisa dimanfaatkan dari library ini.
---
-
+
From 9ef572cbdbcfe10dbb5dc17b34e90d2125ef9054 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:27 +0700
Subject: [PATCH 248/522] New translations c-csrf.md (English)
---
en/content-en/C-csrf.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-csrf.md b/en/content-en/C-csrf.md
index 5ecf26860..a61c2c519 100644
--- a/en/content-en/C-csrf.md
+++ b/en/content-en/C-csrf.md
@@ -226,4 +226,4 @@ Lewat teknik pencegahan ini, bukan berarti serangan CSRF tidak bisa dilakukan, s
---
-
+
From ff4f49c8921483ded5c21074d5af850e99751560 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:28 +0700
Subject: [PATCH 249/522] New translations c-dockerize-golang.md (English)
---
en/content-en/C-dockerize-golang.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-dockerize-golang.md b/en/content-en/C-dockerize-golang.md
index 9d7589909..8f4ae84a8 100644
--- a/en/content-en/C-dockerize-golang.md
+++ b/en/content-en/C-dockerize-golang.md
@@ -326,4 +326,4 @@ docker container stop my-container-hello-world
---
-
+
From 5b157bb4868e4aff2ff95f9e3c9358e2a5a59ffc Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:29 +0700
Subject: [PATCH 250/522] New translations c-echo-routing.md (English)
---
en/content-en/C-echo-routing.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-echo-routing.md b/en/content-en/C-echo-routing.md
index df5e72736..7fe6fce9c 100644
--- a/en/content-en/C-echo-routing.md
+++ b/en/content-en/C-echo-routing.md
@@ -285,4 +285,4 @@ Jalankan aplikasi, lalu coba akses `http://localhost:9000/static/layout.js`.
---
-
+
From 4bae0b4db97dbb9caf4846996cfd9fd8bcf4a491 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:30 +0700
Subject: [PATCH 251/522] New translations c-echo-template-rendering.md
(English)
---
en/content-en/C-echo-template-rendering.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-echo-template-rendering.md b/en/content-en/C-echo-template-rendering.md
index 0f24f9f49..bc6799d05 100644
--- a/en/content-en/C-echo-template-rendering.md
+++ b/en/content-en/C-echo-template-rendering.md
@@ -143,4 +143,4 @@ Proses parsing dan rendering tidak di-handle oleh echo, melainkan menggunakan AP
---
-
+
From 4d9c88b65d31e24390e4d6199a377d419d9a735b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:31 +0700
Subject: [PATCH 252/522] New translations c-flag-parser.md (English)
---
en/content-en/C-flag-parser.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-flag-parser.md b/en/content-en/C-flag-parser.md
index 4d20e5e69..2d1cad439 100644
--- a/en/content-en/C-flag-parser.md
+++ b/en/content-en/C-flag-parser.md
@@ -342,4 +342,4 @@ Cobra merupakan library yang dirancang khusus untuk development aplikasi berbasi
---
-
+
From 780f87f856691de5eaedf9edf385e1ad1e106e32 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:32 +0700
Subject: [PATCH 253/522] New translations c-go-project-layout.md (English)
---
en/content-en/C-go-project-layout.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-go-project-layout.md b/en/content-en/C-go-project-layout.md
index 73bd4fa3a..511270a62 100644
--- a/en/content-en/C-go-project-layout.md
+++ b/en/content-en/C-go-project-layout.md
@@ -19,4 +19,4 @@ Library FTP client yang kita gunakan adalah [github.com/jlaffaye/ftp](https://gi
---
-
+
From 4bfd8e159641497b8d6e301804284436af54af27 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:33 +0700
Subject: [PATCH 254/522] New translations c-golang-aws-s3.md (English)
---
en/content-en/C-golang-aws-s3.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-aws-s3.md b/en/content-en/C-golang-aws-s3.md
index dc2c37608..f62c6a6c8 100644
--- a/en/content-en/C-golang-aws-s3.md
+++ b/en/content-en/C-golang-aws-s3.md
@@ -443,4 +443,4 @@ Hasilnya:
---
-
+
From 56c1181b883cf201901bc8ef8dfb3b37d5621b39 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:33 +0700
Subject: [PATCH 255/522] New translations c-golang-ftp.md (English)
---
en/content-en/C-golang-ftp.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-ftp.md b/en/content-en/C-golang-ftp.md
index 9607d2d6e..ddbe6db2c 100644
--- a/en/content-en/C-golang-ftp.md
+++ b/en/content-en/C-golang-ftp.md
@@ -299,4 +299,4 @@ Jalankan aplikasi, cek hasilnya. Untuk memvalidasi bahwa file di client dan di s
---
-
+
From fc34df402f97b4df9464ae42bee18e78c0b671ef Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:34 +0700
Subject: [PATCH 256/522] New translations c-golang-jwt.md (English)
---
en/content-en/C-golang-jwt.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-jwt.md b/en/content-en/C-golang-jwt.md
index 7900dda1c..5e56357b4 100644
--- a/en/content-en/C-golang-jwt.md
+++ b/en/content-en/C-golang-jwt.md
@@ -413,4 +413,4 @@ Semua berjalan sesuai harapan. Agar lebih meyakinkan, coba lakukan beberapa test
---
-
+
From 9c0e3c844a7693365451a39be1bc3064db3b161d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:35 +0700
Subject: [PATCH 257/522] New translations c-golang-ldap-authentication.md
(English)
---
en/content-en/C-golang-ldap-authentication.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-ldap-authentication.md b/en/content-en/C-golang-ldap-authentication.md
index dc3c5099a..c8ac682b0 100644
--- a/en/content-en/C-golang-ldap-authentication.md
+++ b/en/content-en/C-golang-ldap-authentication.md
@@ -323,4 +323,4 @@ if err != nil {
---
-
+
From 2a3b8c8f5a017c7068e66e4f0e645ffba3c83cd7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:36 +0700
Subject: [PATCH 258/522] New translations c-golang-redis.md (English)
---
en/content-en/C-golang-redis.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-redis.md b/en/content-en/C-golang-redis.md
index 952e66a4c..b1207b178 100644
--- a/en/content-en/C-golang-redis.md
+++ b/en/content-en/C-golang-redis.md
@@ -169,4 +169,4 @@ Error, ini karena data yang disimpan hanya di retain sesuai `ttl` yaitu 3 detik,
---
-
+
From 626c3db1c66d86344475c279c5fd88ef56ab3d08 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:37 +0700
Subject: [PATCH 259/522] New translations c-golang-ssh-sftp.md (English)
---
en/content-en/C-golang-ssh-sftp.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-ssh-sftp.md b/en/content-en/C-golang-ssh-sftp.md
index d3b988f21..390f1d03d 100644
--- a/en/content-en/C-golang-ssh-sftp.md
+++ b/en/content-en/C-golang-ssh-sftp.md
@@ -270,4 +270,4 @@ Jalankan aplikasi untuk melihat hasilnya.
---
-
+
From 4793851ae813ade71b57ea7ea95f750442997622 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:38 +0700
Subject: [PATCH 260/522] New translations c-golang-sso-saml-sp.md (English)
---
en/content-en/C-golang-sso-saml-sp.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-sso-saml-sp.md b/en/content-en/C-golang-sso-saml-sp.md
index 886916789..90e670d27 100644
--- a/en/content-en/C-golang-sso-saml-sp.md
+++ b/en/content-en/C-golang-sso-saml-sp.md
@@ -317,4 +317,4 @@ Salah satu benefit metode inisialisasi ini: ketika ada banyak aplikasi SP (misal
---
-
+
From b7f436ec87a9176232ad5150e1ccaa185d2963de Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:39 +0700
Subject: [PATCH 261/522] New translations c-golang-web-framework.md (English)
---
en/content-en/C-golang-web-framework.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-golang-web-framework.md b/en/content-en/C-golang-web-framework.md
index 48206b450..67ff215e7 100644
--- a/en/content-en/C-golang-web-framework.md
+++ b/en/content-en/C-golang-web-framework.md
@@ -63,4 +63,4 @@ Ok, saya rasa cukup untuk pembahasan kali ini. Semoga bermanfaat
---
-
+
From 8e07780c727718bbd59ac731075fd5966b187f46 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:40 +0700
Subject: [PATCH 262/522] New translations c-http-error-handling.md (English)
---
en/content-en/C-http-error-handling.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-http-error-handling.md b/en/content-en/C-http-error-handling.md
index c548b3298..c45a2cba6 100644
--- a/en/content-en/C-http-error-handling.md
+++ b/en/content-en/C-http-error-handling.md
@@ -111,4 +111,4 @@ Silakan ubah kode `fmt.Sprintf("%d.html", report.Code)` sesuai format nama halam
---
-
+
From ab70496aa446c566bcf6e8c9b653dbe15b9c9a9b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:41 +0700
Subject: [PATCH 263/522] New translations c-http-gzip-compression.md (English)
---
en/content-en/C-http-gzip-compression.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-http-gzip-compression.md b/en/content-en/C-http-gzip-compression.md
index a34804b99..70cb27dab 100644
--- a/en/content-en/C-http-gzip-compression.md
+++ b/en/content-en/C-http-gzip-compression.md
@@ -147,4 +147,4 @@ e.Logger.Fatal(e.Start(":9000"))
---
-
+
From aede54ca12be3fe61f37fca1a70a5e72a3b32d37 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:42 +0700
Subject: [PATCH 264/522] New translations c-http-request-payload-validation.md
(English)
---
en/content-en/C-http-request-payload-validation.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-http-request-payload-validation.md b/en/content-en/C-http-request-payload-validation.md
index c933d1800..2cfb081bb 100644
--- a/en/content-en/C-http-request-payload-validation.md
+++ b/en/content-en/C-http-request-payload-validation.md
@@ -115,4 +115,4 @@ Nah, pada chapter selanjutnya kita akan belajar cara membuat custom error handle
---
-
+
From cdcc3c8a6b530d8a59d9b45d278ceab9d328a39d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:43 +0700
Subject: [PATCH 265/522] New translations c-http2-server-push.md (English)
---
en/content-en/C-http2-server-push.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-http2-server-push.md b/en/content-en/C-http2-server-push.md
index a2f980d4e..102033af1 100644
--- a/en/content-en/C-http2-server-push.md
+++ b/en/content-en/C-http2-server-push.md
@@ -168,4 +168,4 @@ Selain dari kolom protocol, penanda server push bisa dilihat juga lewat grafik *
---
-
+
From ccd28ab8a8ce09b103827d195bc6a3a3405315e3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:44 +0700
Subject: [PATCH 266/522] New translations c-https-tls.md (English)
---
en/content-en/C-https-tls.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-https-tls.md b/en/content-en/C-https-tls.md
index 88a5207d2..3922c237a 100644
--- a/en/content-en/C-https-tls.md
+++ b/en/content-en/C-https-tls.md
@@ -137,4 +137,4 @@ Warning `NET::ERR_CERT_AUTHORITY_INVALID` muncul ketika mengakses sebuah website
---
-
+
From 1eb1b99426d6c364b613ef3b598a7ed21d38293a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:45 +0700
Subject: [PATCH 267/522] New translations
c-parsing-http-request-payload-echo.md (English)
---
en/content-en/C-parsing-http-request-payload-echo.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-parsing-http-request-payload-echo.md b/en/content-en/C-parsing-http-request-payload-echo.md
index ac9183edc..3a4288166 100644
--- a/en/content-en/C-parsing-http-request-payload-echo.md
+++ b/en/content-en/C-parsing-http-request-payload-echo.md
@@ -121,4 +121,4 @@ curl -X GET http://localhost:9000/user?name=Joe&email=nope@novalagung.com
---
-
+
From 13e0093f1456b57413613327bd315ccbe575da0f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:46 +0700
Subject: [PATCH 268/522] New translations c-project-layout-structure.md
(English)
---
en/content-en/C-project-layout-structure.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-project-layout-structure.md b/en/content-en/C-project-layout-structure.md
index f90cf9aa7..d7307d81c 100644
--- a/en/content-en/C-project-layout-structure.md
+++ b/en/content-en/C-project-layout-structure.md
@@ -119,4 +119,4 @@ Ok, sampai sini saja pembahasan mengenai project layout, selanjutnya silakan men
---
-
+
From a7a7fcc1ce76c0cabf681c613558957362325475 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:47 +0700
Subject: [PATCH 269/522] New translations c-read-write-excel-xlsx-file.md
(English)
---
en/content-en/C-read-write-excel-xlsx-file.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-read-write-excel-xlsx-file.md b/en/content-en/C-read-write-excel-xlsx-file.md
index ff1c9f63d..8d29fa3b9 100644
--- a/en/content-en/C-read-write-excel-xlsx-file.md
+++ b/en/content-en/C-read-write-excel-xlsx-file.md
@@ -198,4 +198,4 @@ Jalankan aplikasi untuk mengecek hasilnya.
---
-
+
From bab339025adeb3b27b6d15564fba418b9cb04966 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:48 +0700
Subject: [PATCH 270/522] New translations c-scraping-parsing-html.md (English)
---
en/content-en/C-scraping-parsing-html.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-scraping-parsing-html.md b/en/content-en/C-scraping-parsing-html.md
index a5b7050c7..c422445da 100644
--- a/en/content-en/C-scraping-parsing-html.md
+++ b/en/content-en/C-scraping-parsing-html.md
@@ -196,4 +196,4 @@ Jalankan aplikasi, lihat hasilnya.
---
-
+
From 17ef29eb932e282b81e37c587ff7187cd80faed4 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:48 +0700
Subject: [PATCH 271/522] New translations
c-secure-insecure-client-http-request.md (English)
---
en/content-en/C-secure-insecure-client-http-request.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-secure-insecure-client-http-request.md b/en/content-en/C-secure-insecure-client-http-request.md
index ccf1da525..ca12dd4f2 100644
--- a/en/content-en/C-secure-insecure-client-http-request.md
+++ b/en/content-en/C-secure-insecure-client-http-request.md
@@ -191,4 +191,4 @@ Tujuan mengapa penulis tambahkan sub chapter **Konfigurasi SSL/TLS Lanjutan** in
---
-
+
From 93e91e7e727fdcd9aecf936ad256392ede7210ff Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:49 +0700
Subject: [PATCH 272/522] New translations c-secure-middleware.md (English)
---
en/content-en/C-secure-middleware.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-secure-middleware.md b/en/content-en/C-secure-middleware.md
index 36ec10786..5a6e30541 100644
--- a/en/content-en/C-secure-middleware.md
+++ b/en/content-en/C-secure-middleware.md
@@ -154,4 +154,4 @@ Lebih mendetailnya silakan langsung cek halaman official library secure di https
---
-
+
From f99541b25c6d4c1a3804bf0dc1fa5859016ac88b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:50 +0700
Subject: [PATCH 273/522] New translations c-securecookie.md (English)
---
en/content-en/C-securecookie.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-securecookie.md b/en/content-en/C-securecookie.md
index de0e27aa5..ed5074adb 100644
--- a/en/content-en/C-securecookie.md
+++ b/en/content-en/C-securecookie.md
@@ -159,4 +159,4 @@ http.SetCookie(c.Response(), cookie)
---
-
+
From 47715de9218d36538a6c35964de0cb94ff3187c0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:51 +0700
Subject: [PATCH 274/522] New translations c-send-email.md (English)
---
en/content-en/C-send-email.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-send-email.md b/en/content-en/C-send-email.md
index 50195f667..cf2aa8416 100644
--- a/en/content-en/C-send-email.md
+++ b/en/content-en/C-send-email.md
@@ -200,4 +200,4 @@ Daaaaannnnn ... cukup itu saja penyesuaiannya agar bisa kirim email via konfigur
---
-
+
From 6c235b1a9447d4a12111aaa4f80e51d2280d5651 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:52 +0700
Subject: [PATCH 275/522] New translations c-session.md (English)
---
en/content-en/C-session.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-session.md b/en/content-en/C-session.md
index 8cdc26415..0b755843d 100644
--- a/en/content-en/C-session.md
+++ b/en/content-en/C-session.md
@@ -316,4 +316,4 @@ Tujuan dari kode yang kita tulis kurang lebih sebagai berikut.
---
-
+
From c99634a5efdb968ddd55a0819d3af020a05040c5 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:53 +0700
Subject: [PATCH 276/522] New translations c-singleflight.md (English)
---
en/content-en/C-singleflight.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-singleflight.md b/en/content-en/C-singleflight.md
index 260fc53e6..9a5b4aa76 100644
--- a/en/content-en/C-singleflight.md
+++ b/en/content-en/C-singleflight.md
@@ -241,4 +241,4 @@ Cukup berguna bukan? Dengan adahnya singleflight API ini, beban backend akan sed
---
-
+
From 6b5c563b0b15a93c7e2e1c55242ea9e9909b3629 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:54 +0700
Subject: [PATCH 277/522] New translations c-write-pdf-file.md (English)
---
en/content-en/C-write-pdf-file.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-write-pdf-file.md b/en/content-en/C-write-pdf-file.md
index c553083cb..fa6e17ed8 100644
--- a/en/content-en/C-write-pdf-file.md
+++ b/en/content-en/C-write-pdf-file.md
@@ -93,4 +93,4 @@ Coba jalankan aplikasi untuk melihat hasilnya. Buka generated file `file.pdf`, i
---
-
+
From a32573259785de1007671aabb536fe3d4fd6b3a6 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:55 +0700
Subject: [PATCH 278/522] New translations c-xml-parser.md (English)
---
en/content-en/C-xml-parser.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/C-xml-parser.md b/en/content-en/C-xml-parser.md
index 21c8df66b..e9b54902c 100644
--- a/en/content-en/C-xml-parser.md
+++ b/en/content-en/C-xml-parser.md
@@ -280,4 +280,4 @@ Jalankan aplikasi, lihat hasilnya.
---
-
+
From f46ea250801ae9f8016af3080b835b5ba735e5bc Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:56 +0700
Subject: [PATCH 279/522] New translations d-golang-web-socket-chatting-app.md
(English)
---
en/content-en/D-golang-web-socket-chatting-app.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/D-golang-web-socket-chatting-app.md b/en/content-en/D-golang-web-socket-chatting-app.md
index a7fe99801..c234dd629 100644
--- a/en/content-en/D-golang-web-socket-chatting-app.md
+++ b/en/content-en/D-golang-web-socket-chatting-app.md
@@ -420,4 +420,4 @@ Ketika salah satu user leave, pesan **User XXX: disconnected** akan di-broadcast
---
-
+
From c772bd3122ec98fbb87b4b4dbb0d45dcb43a0d5b Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:57 +0700
Subject: [PATCH 280/522] New translations d-google-api-search.md (English)
---
en/content-en/D-google-api-search.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/D-google-api-search.md b/en/content-en/D-google-api-search.md
index 0ea6ff67a..9a278d1f9 100644
--- a/en/content-en/D-google-api-search.md
+++ b/en/content-en/D-google-api-search.md
@@ -331,4 +331,4 @@ Error di atas muncul karena, host `localhost` belum didaftarkan pada API console
---
-
+
From f1ac9f87146f1985471a76db784bece43ee2acb6 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 25 Dec 2023 16:44:58 +0700
Subject: [PATCH 281/522] New translations
d-insert-1mil-csv-record-into-db-in-a-minute.md (English)
---
en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md b/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md
index 9e4ef99c3..05d5c0cd7 100644
--- a/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md
+++ b/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md
@@ -348,4 +348,4 @@ Praktek pada chapter ini sifatnya adalah POC, jadi sangat mungkin diperlukan pen
---
-
+
From 35b80b217b3f6f6243b1b0a28a145e8cd894763a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Tue, 5 Mar 2024 15:48:35 +0700
Subject: [PATCH 282/522] New translations
a-setup-go-project-dengan-go-modules.md (English)
---
en/content-en/A-setup-go-project-dengan-go-modules.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/en/content-en/A-setup-go-project-dengan-go-modules.md b/en/content-en/A-setup-go-project-dengan-go-modules.md
index 392ce8a5a..31ece0485 100644
--- a/en/content-en/A-setup-go-project-dengan-go-modules.md
+++ b/en/content-en/A-setup-go-project-dengan-go-modules.md
@@ -36,11 +36,11 @@ go mod init
go mod init project-pertama
```
-Untuk nama project, umumnya adalah disamakan dengan nama direktori, tapi bisa saja sebenarnya menggunakan nama yang lain.
+Untuk nama project, umumnya disamakan dengan nama direktori, tapi bisa saja sebenarnya menggunakan nama yang lain.
> Nama project dan Nama module merupakan istilah yang sama.
-Eksekusi perintah `go mod init` menghasilkan satu buah file baru bernama `go.mod`. File ini digunakan oleh Go toolchain untuk menandai bahwa folder di mana file tersebut berada adalah folder project. Jadi jangan di hapus ya file tersebut.
+Eksekusi perintah `go mod init` menghasilkan satu buah file baru bernama `go.mod`. File ini digunakan oleh Go toolchain untuk menandai bahwa folder di mana file tersebut berada adalah folder project. Jadi jangan dihapus ya file tersebut.
---
From 90980cf1d2690163e2ec6d3796e4d75c1a57ceed Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Tue, 5 Mar 2024 15:48:36 +0700
Subject: [PATCH 283/522] New translations contributing.md (English)
---
en/content-en/CONTRIBUTING.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/en/content-en/CONTRIBUTING.md b/en/content-en/CONTRIBUTING.md
index 29e86cc45..881de87ea 100644
--- a/en/content-en/CONTRIBUTING.md
+++ b/en/content-en/CONTRIBUTING.md
@@ -35,6 +35,7 @@ Berikut merupakan *hall of fame* kontributor yang sudah berbaik hati menyisihkan
1. [Ananda Wiradharma](https://github.com/anandawira)
1. [Andreas Giovani](https://github.com/compatc)
1. [Arian Saputra](https://github.com/Rhyanz46)
+1. [Arsy Opraza Akma](https://github.com/arasopraza)
1. [Burhanudin Yahya](https://github.com/burhanudinyahya)
1. [Dipta Harimbawa](https://github.com/diptarimba)
1. [Dwi Hujianto](https://github.com/dwihujianto)
From 549ac617f511996784c68344971676eeda7b4b19 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 7 Mar 2024 15:10:19 +0700
Subject: [PATCH 284/522] New translations a-tipe-data.md (English)
---
en/content-en/A-tipe-data.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-tipe-data.md b/en/content-en/A-tipe-data.md
index cf303e338..3d57aeffc 100644
--- a/en/content-en/A-tipe-data.md
+++ b/en/content-en/A-tipe-data.md
@@ -90,7 +90,7 @@ Mari belajar "Golang".`
fmt.Println(message)
```
-Ketika dijalankan, output akan muncul sama persisi sesuai nilai variabel `message` di atas. Tanda petik dua akan muncul, baris baru juga muncul, sama persis.
+Ketika dijalankan, output akan muncul sama persis sesuai nilai variabel `message` di atas. Tanda petik dua akan muncul, baris baru juga muncul, sama persis.

From 91caf2e03aa26f8d8ed43f8148ee642001c8d3a2 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Fri, 29 Mar 2024 15:28:54 +0700
Subject: [PATCH 285/522] New translations a-buffered-channel.md (English)
---
en/content-en/A-buffered-channel.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/A-buffered-channel.md b/en/content-en/A-buffered-channel.md
index fdbdc95a8..52a3cd2f8 100644
--- a/en/content-en/A-buffered-channel.md
+++ b/en/content-en/A-buffered-channel.md
@@ -57,7 +57,7 @@ Bisa dilihat output di atas, pada proses pengiriman data ke-4, diikuti dengan pr
Pengiriman data indeks ke 0, 1, 2 dan 3 akan berjalan secara asynchronous, hal ini karena channel ditentukan nilai buffer-nya sebanyak 3 (ingat, jika nilai buffer adalah 3, maka 4 data yang akan di-buffer). Pengiriman selanjutnya (indeks 5) hanya akan terjadi jika ada salah satu data dari ke-empat data yang sebelumnya telah dikirimkan sudah diterima (dengan serah terima data yang bersifat blocking). Setelahnya, pengiriman data kembali dilakukan secara asynchronous (karena sudah ada slot buffer ada yang kosong).
-Karena pengiriman dan penerimaan data via buffered channel terjadi tidak selalu sycnrhonous (tergantung jumlah buffer-nya), maka ada kemungkinan dimana eksekusi program selesai namun tidak semua data diterima via channel `messages`. Karena alasan ini pada bagian akhir ditambahkan statement `time.Sleep(1 * time.Second)` agar ada jeda 1 detik sebelum program selesai.
+Karena pengiriman dan penerimaan data via buffered channel terjadi tidak selalu synchronous (tergantung jumlah buffer-nya), maka ada kemungkinan dimana eksekusi program selesai namun tidak semua data diterima via channel `messages`. Karena alasan ini pada bagian akhir ditambahkan statement `time.Sleep(1 * time.Second)` agar ada jeda 1 detik sebelum program selesai.
#### • Fungsi `time.Sleep()`
From 777682293b746408fd6de6f612175079fad27be5 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Fri, 29 Mar 2024 15:28:55 +0700
Subject: [PATCH 286/522] New translations c-golang-sso-saml-sp.md (English)
---
en/content-en/C-golang-sso-saml-sp.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/en/content-en/C-golang-sso-saml-sp.md b/en/content-en/C-golang-sso-saml-sp.md
index 90e670d27..cffa7dce1 100644
--- a/en/content-en/C-golang-sso-saml-sp.md
+++ b/en/content-en/C-golang-sso-saml-sp.md
@@ -112,7 +112,7 @@ Karena user memiliki security context, maka SP merespon dengan balasan berupa ta
> Selanjutnya, setiap kali ada request target resource, maka point 7 dan 8 akan diulang.
-
+
## C.34.4. SAML Metadata
@@ -126,7 +126,7 @@ Metadata diperlukan secara *mandatory* dalam operasi dan komunikasi antar SP dan
> Metadata berisi informasi penting. Di antaranya adalah entity ID, key pair, protocol endpoints, dan lainnya.
-
+
Ada dua jenis metode pertukaran metadata, **static metadata exchange** dan **dynamic metadata exchange**. Yang kita terapkan pada pembahasan ini adalah yg static.
From 42d13cf019e2166c79693fb88d8b04824752da5f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Fri, 29 Mar 2024 15:28:55 +0700
Subject: [PATCH 287/522] New translations contributing.md (English)
---
en/content-en/CONTRIBUTING.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/en/content-en/CONTRIBUTING.md b/en/content-en/CONTRIBUTING.md
index 881de87ea..28d7df31b 100644
--- a/en/content-en/CONTRIBUTING.md
+++ b/en/content-en/CONTRIBUTING.md
@@ -61,6 +61,7 @@ Berikut merupakan *hall of fame* kontributor yang sudah berbaik hati menyisihkan
1. [MH Rohman Masyhar](https://github.com/rohmanhm)
1. [Muhammad Faris 'Afif](https://github.com/muhfaris)
1. [Muhammad Ridho](https://github.com/reedho)
+1. [Muhammad Zulfan Wahyudin](https://github.com/mzulfanw)
1. [Mulia Nasution](https://github.com/mul14)
1. [nekonako](https://github.com/nekonako)
1. [Nuevo Querto](https://github.com/NuevoQuerto)
From 287acdeaca910ecb63f3c3bfad3f55ebccb4fd19 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Sun, 31 Mar 2024 11:48:46 +0700
Subject: [PATCH 288/522] New translations a-fungsi.md (English)
---
en/content-en/A-fungsi.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/en/content-en/A-fungsi.md b/en/content-en/A-fungsi.md
index eec5f2edb..63e6c65c4 100644
--- a/en/content-en/A-fungsi.md
+++ b/en/content-en/A-fungsi.md
@@ -1,6 +1,6 @@
# A.18. Fungsi
-Fungsi merupakan aspek penting dalam pemrograman. Definisi fungsi sendiri adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (kependekan dari *don't repeat yourself*), tak perlu menuliskan banyak kode yang kegunaannya berkali-kali, cukup sekali saja lalu panggil sesuai kebutuhan.
+Fungsi merupakan aspek penting dalam pemrograman. Definisi fungsi sendiri adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (singkatan dari *don't repeat yourself*), tak perlu menuliskan banyak kode yang kegunaannya berkali-kali, cukup sekali saja lalu panggil sesuai kebutuhan.
Pada chapter ini kita akan belajar tentang penggunaan fungsi di Go.
@@ -33,7 +33,7 @@ func printMessage(message string, arr []string) {
Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`.
-Fungsi tersebut dipanggil dalam `main`, dengan disisipkan 2 buah data sebagai parameter, data pertama adalah string `"hallo"` yang ditampung parameter `message`, dan parameter ke 2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`.
+Fungsi tersebut dipanggil dalam `main`, dengan disisipkan 2 buah data sebagai parameter, data pertama adalah string `"halo"` yang ditampung parameter `message`, dan parameter ke 2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`.
Di dalam `printMessage`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`).
From 87ae7be4bb645d936374dd7ee2adaee260dacaef Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Wed, 3 Apr 2024 21:28:06 +0700
Subject: [PATCH 289/522] New translations 2-instalasi-golang.md (English)
---
en/content-en/2-instalasi-golang.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/2-instalasi-golang.md b/en/content-en/2-instalasi-golang.md
index 36332a3c4..0e4feb93a 100644
--- a/en/content-en/2-instalasi-golang.md
+++ b/en/content-en/2-instalasi-golang.md
@@ -33,7 +33,7 @@ Cara termudah instalasi Go di MacOS adalah menggunakan [Homebrew](http://brew.sh
1. *Install* terlebih dahulu Homebrew (jika belum ada), caranya jalankan perintah berikut di **terminal**.
```bash
- $ ruby -e "$(curl -fsSL http://git.io/pVOl)"
+ $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
2. *Install* Go menggunakan command `brew`.
From 8249d09dc0d66e8006e8e4c307ecb4dc48856039 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 22 Apr 2024 17:07:34 +0700
Subject: [PATCH 290/522] New translations a-perulangan.md (English)
---
en/content-en/A-perulangan.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/en/content-en/A-perulangan.md b/en/content-en/A-perulangan.md
index 5f7da9225..17696f4ee 100644
--- a/en/content-en/A-perulangan.md
+++ b/en/content-en/A-perulangan.md
@@ -81,6 +81,11 @@ for k, v := range kvs {
for range kvs {
fmt.Println("Done")
}
+
+// selain itu, bisa juga dengan cukup menentukan nilai numerik perulangan
+for i := range 5 {
+ fmt.Print(i) // 01234
+}
```
## A.14.5. Penggunaan Keyword `break` & `continue`
From 6c2cbb406899d12221cbea8075ae7f62912e449c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Mon, 22 Apr 2024 17:07:35 +0700
Subject: [PATCH 291/522] New translations readme.md (English)
---
en/content-en/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/en/content-en/README.md b/en/content-en/README.md
index 5478ce8a5..8908f70f5 100644
--- a/en/content-en/README.md
+++ b/en/content-en/README.md
@@ -23,7 +23,7 @@ Ada total sekitar 120 chapter yang dibahas dalam ebook ini. Kumpulan chap
-Versi e-book: **((VERSION))**, dan versi Go **1.20**.
+Versi e-book: **((VERSION))**, dan versi Go **1.22**.
## Download File E-book (pdf, epub, mobi)
From ffe968117ef2cdddf599744d88455ac061b8297d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:25 +0700
Subject: [PATCH 292/522] New translations 2-instalasi-golang.md (English)
---
en/content-en/2-instalasi-golang.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/en/content-en/2-instalasi-golang.md b/en/content-en/2-instalasi-golang.md
index 0e4feb93a..a6d02da7a 100644
--- a/en/content-en/2-instalasi-golang.md
+++ b/en/content-en/2-instalasi-golang.md
@@ -4,13 +4,13 @@ Hal pertama yang perlu dilakukan sebelum bisa menggunakan Go adalah meng-*instal
Di sini penulis mencoba meringkas petunjuk instalasi pada *link* di atas, agar lebih mudah untuk diikuti terutama untuk pembaca yang baru belajar.
-> Go yang digunakan adalah versi **1.20**, direkomendasikan menggunakan versi tersebut.
+> Go yang digunakan adalah versi **1.22**, direkomendasikan menggunakan versi tersebut.
URL untuk mengunduh *installer* Go: https://golang.org/dl/. Silakan langsung unduh dari *link* tersebut lalu lakukan proses instalasi, atau bisa mengikuti petunjuk pada chapter ini.
## A.2.1. Instalasi Go *Stable*
-#### • Instalasi Go di Windows
+#### ◉ Instalasi Go di Windows
1. Download terlebih dahulu *installer*-nya di [https://golang.org/dl/](https://golang.org/dl/). Pilih *installer* untuk sistem operasi Windows sesuai jenis bit yang digunakan.
@@ -26,7 +26,7 @@ URL untuk mengunduh *installer* Go: https://golang.org/dl/. Silakan langsung und
> Sering terjadi, command `go version` tidak bisa dijalankan meskipun instalasi sukses. Solusinya bisa dengan restart CMD (tutup CMD, kemudian buka lagi). Setelah itu coba jalankan ulang command di atas.
-#### • Instalasi Go di MacOS
+#### ◉ Instalasi Go di MacOS
Cara termudah instalasi Go di MacOS adalah menggunakan [Homebrew](http://brew.sh/).
@@ -57,7 +57,7 @@ Cara termudah instalasi Go di MacOS adalah menggunakan [Homebrew](http://brew.sh
5. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil.
-#### • Instalasi Go di Linux
+#### ◉ Instalasi Go di Linux
1. Unduh arsip *installer* dari [https://golang.org/dl/](https://golang.org/dl/), pilih installer untuk Linux yang sesuai dengan jenis bit komputer anda. Proses download bisa dilakukan lewat CLI, menggunakan `wget` atau `curl`.
From 2341e553c1b997fd8f171568363777120327050f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:26 +0700
Subject: [PATCH 293/522] New translations a-array.md (English)
---
en/content-en/A-array.md | 50 +++++++++++++++++++++-------------------
1 file changed, 26 insertions(+), 24 deletions(-)
diff --git a/en/content-en/A-array.md b/en/content-en/A-array.md
index 10af138d1..2d4e75f32 100644
--- a/en/content-en/A-array.md
+++ b/en/content-en/A-array.md
@@ -1,6 +1,8 @@
# A.15. Array
-Array adalah kumpulan data bertipe sama, yang disimpan dalam sebuah variabel. Array memiliki kapasitas yang nilainya ditentukan pada saat pembuatan, menjadikan elemen/data yang disimpan di array tersebut jumlahnya tidak boleh melebihi yang sudah dialokasikan. Default nilai tiap elemen array pada awalnya tergantung dari tipe datanya. Jika `int` maka tiap element zero value-nya adalah `0`, jika `bool` maka `false`, dan seterusnya. Setiap elemen array memiliki indeks berupa angka yang merepresentasikan posisi urutan elemen tersebut. Indeks array dimulai dari 0.
+Array adalah kumpulan data bertipe sama, yang disimpan dalam sebuah variabel. Array memiliki kapasitas yang nilainya ditentukan pada saat pembuatan, menjadikan elemen/data yang disimpan di array tersebut jumlahnya tidak boleh melebihi yang sudah dialokasikan.
+
+Default nilai tiap elemen array pada awalnya tergantung dari tipe datanya. Jika `int` maka tiap element zero value-nya adalah `0`, jika `bool` maka `false`, dan seterusnya. Setiap elemen array memiliki indeks berupa angka yang merepresentasikan posisi urutan elemen tersebut. Indeks array dimulai dari 0.
Contoh penerapan array:
@@ -14,13 +16,13 @@ names[3] = "law"
fmt.Println(names[0], names[1], names[2], names[3])
```
-Variabel `names` dideklarasikan sebagai `array string` dengan alokasi elemen `4` slot. Cara mengisi slot elemen array bisa dilihat di kode di atas, yaitu dengan langsung mengakses elemen menggunakan indeks, lalu mengisinya.
+Variabel `names` dideklarasikan sebagai `array string` dengan alokasi kapasitas elemen adalah `4` slot. Cara mengisi slot elemen array bisa dilihat di kode di atas, yaitu dengan langsung mengakses elemen menggunakan indeks, lalu mengisinya.

## A.15.1. Pengisian Elemen Array yang Melebihi Alokasi Awal
-Pengisian elemen array pada indeks yang tidak sesuai dengan alokasi menghasilkan error. Contoh sederhana, jika array memiliki 4 slot, maka pengisian nilai slot 5 seterusnya adalah tidak valid.
+Pengisian elemen array pada indeks yang tidak sesuai dengan jumlah alokasi menghasilkan error. Contoh: jika array memiliki 4 slot, maka pengisian nilai slot 5 seterusnya adalah tidak valid.
```go
var names [4]string
@@ -31,7 +33,7 @@ names[3] = "law"
names[4] = "ez" // baris kode ini menghasilkan error
```
-Solusi dari masalah di atas adalah dengan menggunakan keyword `append`, yang nantinya pada chapter selanjutnya ([A.16. Slice](/A-slice.html)) akan kita bahas.
+Solusi dari masalah di atas adalah dengan menggunakan keyword `append`, yang pembahasannya ada pada chapter selanjutnya, ([A.16. Slice](/A-slice.html)).
## A.15.2. Inisialisasi Nilai Awal Array
@@ -44,11 +46,11 @@ fmt.Println("Jumlah element \t\t", len(fruits))
fmt.Println("Isi semua element \t", fruits)
```
-Penggunaan fungsi `fmt.Println()` pada data array tanpa mengakses indeks tertentu, akan menghasilkan output dalam bentuk string dari semua array yang ada. Teknik ini biasa digunakan untuk *debugging* data array.
+Penggunaan fungsi `fmt.Println()` pada data array tanpa mengakses indeks tertentu, menghasilkan output dalam bentuk string dari semua array yang ada. Teknik ini umum digunakan untuk keperluan *debugging* data array.

-Fungsi `len()` dipakai untuk menghitung jumlah elemen sebuah array.
+Fungsi `len()` berfungsi untuk menghitung jumlah elemen sebuah array.
## A.15.3. Inisialisasi Nilai Array Dengan Gaya Vertikal
@@ -69,11 +71,11 @@ fruits = [4]string{
}
```
-Khusus untuk deklarasi array dengan cara vertikal, tanda koma wajib dituliskan setelah elemen, termasuk elemen terakhir. Jika tidak, maka akan muncul error.
+Khusus untuk deklarasi array dengan cara vertikal, tanda koma wajib dituliskan setelah setiap elemen (termasuk elemen terakhir), agar tidak memunculkan syntax error.
## A.15.4. Inisialisasi Nilai Awal Array Tanpa Jumlah Elemen
-Deklarasi array yang nilainya diset di awal, boleh tidak dituliskan jumlah lebar array-nya, cukup ganti dengan tanda 3 titik (`...`). Jumlah elemen akan di kalkulasi secara otomatis menyesuaikan data elemen yang diisikan.
+Deklarasi array yang nilainya diset di awal, boleh tidak dituliskan jumlah lebar array-nya, cukup ganti dengan tanda 3 titik (`...`). Metode penulisan ini membuat kapasitas array otomatis dihitung dari jumlah elemen array yang ditulis.
```go
var numbers = [...]int{2, 3, 2, 4, 3}
@@ -82,17 +84,19 @@ fmt.Println("data array \t:", numbers)
fmt.Println("jumlah elemen \t:", len(numbers))
```
-Variabel `numbers` akan secara otomatis memiliki jumlah elemen `5`, karena pada saat deklarasi disiapkan 5 buah elemen.
+Variabel `numbers` secara otomatis kapasitas elemennya adalah `5`.

## A.15.5. Array Multidimensi
-Array multidimensi adalah array yang tiap elemennya juga berupa array (dan bisa seterusnya, tergantung ke dalaman dimensinya).
+Array multidimensi adalah array yang tiap elemennya juga berupa array.
+
+> Level kedalaman array multidimensi adalah tidak terbatas, bisa saja suatu array berisi elemen array yang setiap elemennya juga adalah nilai array, dst.
-Cara deklarasi array multidimensi secara umum sama dengan cara deklarasi array biasa, dengan cara menuliskan data array dimensi selanjutnya sebagai elemen array dimensi sebelumnya.
+Cara deklarasi array multidimensi secara umum sama dengan array biasa, bedanya adalah pada array biasa, setiap elemen berisi satu nilai, sedangkan pada array multidimensi setiap elemen berisi array.
-Khusus untuk array yang merupakan sub dimensi atau elemen, boleh tidak dituliskan jumlah datanya. Contohnya bisa dilihat pada deklarasi variabel `numbers2` di kode berikut.
+Khusus penulisan array yang merupakan subdimensi/elemen, boleh tidak dituliskan jumlah datanya. Contohnya bisa dilihat pada deklarasi variabel `numbers2` di kode berikut.
```go
var numbers1 = [2][3]int{[3]int{3, 2, 3}, [3]int{3, 4, 5}}
@@ -102,13 +106,13 @@ fmt.Println("numbers1", numbers1)
fmt.Println("numbers2", numbers2)
```
-Kedua array di atas memiliki elemen yang sama.
+Kedua array di atas memiliki jumlah dan isi elemen yang sama.

## A.15.6. Perulangan Elemen Array Menggunakan Keyword `for`
-Keyword `for` dan array memiliki hubungan yang sangat erat. Dengan memanfaatkan perulangan menggunakan keyword ini, elemen-elemen dalam array bisa didapat.
+Keyword `for` dan array memiliki hubungan yang sangat erat. Dengan memanfaatkan perulangan/looping menggunakan keyword ini, elemen-elemen dalam array bisa didapat.
Ada beberapa cara yang bisa digunakan untuk me-looping data array, yg pertama adalah dengan memanfaatkan variabel iterasi perulangan untuk mengakses elemen berdasarkan indeks-nya. Contoh:
@@ -126,7 +130,7 @@ Perulangan di atas dijalankan sebanyak jumlah elemen array `fruits` (bisa diketa
## A.15.7. Perulangan Elemen Array Menggunakan Keyword `for` - `range`
-Ada cara yang lebih sederhana me-looping data array, dengan menggunakan keyword `for` - `range`. Contoh pengaplikasiannya bisa dilihat di kode berikut.
+Ada cara lain yang lebih sederhana untuk operasi perulangan array, yaitu menggunakan kombinasi keyword `for` - `range`. Contoh pengaplikasiannya bisa dilihat di kode berikut.
```go
var fruits = [4]string{"apple", "grape", "banana", "melon"}
@@ -138,13 +142,11 @@ for i, fruit := range fruits {
Array `fruits` diambil elemen-nya secara berurutan. Nilai tiap elemen ditampung variabel oleh `fruit` (tanpa huruf s), sedangkan indeks nya ditampung variabel `i`.
-Output program di atas, sama dengan output program sebelumnya, hanya cara yang digunakan berbeda.
+Output program di atas, sama persis dengan output program sebelumnya, hanya saja cara yang diterapkan berbeda.
## A.15.8. Penggunaan Variabel Underscore `_` Dalam `for` - `range`
-Kadang kala ketika *looping* menggunakan `for` - `range`, ada kemungkinan di mana data yang dibutuhkan adalah elemen-nya saja, indeks-nya tidak. Sedangkan kode di atas, `range` mengembalikan 2 data, yaitu indeks dan elemen.
-
-Seperti yang sudah diketahui, bahwa di Go tidak memperbolehkan adanya variabel yang menganggur atau tidak dipakai. Jika dipaksakan, error akan muncul, contohnya seperti kode berikut.
+Terkadang, dalam penerapan *looping* menggunakan `for` - `range`, ada kebutuhan di mana yang dibutuhkan dari perulangan adlah adalah elemen-nya saja, sedangkan indeks-nya tidak, contoh:
```go
var fruits = [4]string{"apple", "grape", "banana", "melon"}
@@ -154,11 +156,11 @@ for i, fruit := range fruits {
}
```
-Hasil dari kode program di atas:
+Hasil dari kode program di atas adalah error, karena Go tidak memperbolehkan adanya variabel yang menganggur atau tidak dipakai.

-Di sinilah salah satu kegunaan variabel pengangguran, atau underscore (`_`). Tampung saja nilai yang tidak ingin digunakan ke underscore.
+Di sinilah salah satu kegunaan dari variabel pengangguran, atau underscore (`_`). Tampung saja nilai yang tidak ingin digunakan ke underscore.
```go
var fruits = [4]string{"apple", "grape", "banana", "melon"}
@@ -172,7 +174,7 @@ Pada kode di atas, yang sebelumnya adalah variabel `i` diganti dengan `_`, karen

-Jika yang dibutuhkan hanya indeks elemen-nya saja, bisa gunakan 1 buah variabel setelah keyword `for`.
+Bagaiamana jika sebaliknya? Misal, yang dibutuhkan hanya indeks-nya saja, nilainya tidak penting. Maka cukup tulis satu variabel saja setelah keyword `for`, yaitu variabel penampung nilai indeks.
```go
for i, _ := range fruits { }
@@ -182,7 +184,7 @@ for i := range fruits { }
## A.15.9. Alokasi Elemen Array Menggunakan Keyword `make`
-Deklarasi sekaligus alokasi data array juga bisa dilakukan lewat keyword `make`.
+Deklarasi sekaligus alokasi kapasitas array juga bisa dilakukan lewat keyword `make`.
```go
var fruits = make([]string, 2)
@@ -192,7 +194,7 @@ fruits[1] = "manggo"
fmt.Println(fruits) // [apple manggo]
```
-Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diinginkan, parameter kedua adalah jumlah elemennya. Pada kode di atas, variabel `fruits` tercetak sebagai array string dengan alokasi 2 slot.
+Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diinginkan, parameter kedua adalah jumlah elemennya. Pada kode di atas, variabel `fruits` tercetak sebagai array string dengan kapasitas alokasi 2 slot.
---
From 59b94ff06c64eb8fa6abe2e1484e532304ea04ce Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:27 +0700
Subject: [PATCH 294/522] New translations a-buffered-channel.md (English)
---
en/content-en/A-buffered-channel.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/en/content-en/A-buffered-channel.md b/en/content-en/A-buffered-channel.md
index 52a3cd2f8..b5e16bf82 100644
--- a/en/content-en/A-buffered-channel.md
+++ b/en/content-en/A-buffered-channel.md
@@ -1,12 +1,12 @@
# A.32. Buffered Channel
-Proses transfer data pada channel secara default dilakukan dengan cara **un-buffered**, atau tidak di-buffer di memori. Ketika terjadi proses kirim data via channel dari sebuah goroutine, maka harus ada goroutine lain yang menerima data dari channel yang sama, dengan proses serah-terima yang bersifat blocking. Maksudnya, baris kode setelah kode pengiriman dan penerimaan data tidak akan di proses sebelum proses serah-terima itu sendiri selesai.
+Proses transfer data pada channel secara default dilakukan dengan metode **un-buffered** atau tidak di-buffer di memori. Ketika terjadi proses kirim data via channel dari sebuah goroutine, maka harus ada goroutine lain yang menerima data dari channel yang sama, dengan proses serah-terima yang bersifat blocking. Maksudnya, baris kode setelah kode pengiriman dan juga penerimaan data tidak akan diproses sebelum proses serah-terima itu sendiri selesai.
Buffered channel sedikit berbeda. Pada channel jenis ini, ditentukan angka jumlah buffer-nya. Angka tersebut menjadi penentu jumlah data yang bisa dikirimkan bersamaan. Selama jumlah data yang dikirim tidak melebihi jumlah buffer, maka pengiriman akan berjalan **asynchronous** (tidak blocking).
Ketika jumlah data yang dikirim sudah melewati batas buffer, maka pengiriman data hanya bisa dilakukan ketika salah satu data yang sudah terkirim adalah sudah diambil dari channel di goroutine penerima, sehingga ada slot channel yang kosong.
-Proses pengiriman data pada buffered channel adalah *asynchronous* ketika jumlah data yang dikirim tidak melebihi batas buffer. Namun pada bagian channel penerimaan data selalu bersifat *synchronous*.
+Proses pengiriman data pada buffered channel adalah *asynchronous* ketika jumlah data yang dikirim tidak melebihi batas buffer. Namun pada bagian channel penerimaan data selalu bersifat *synchronous* atau blocking.

@@ -49,21 +49,21 @@ func main() {
Pada kode di atas, parameter kedua fungsi `make()` adalah representasi jumlah buffer. Perlu diperhatikan bahwa nilai buffered channel dimulai dari `0`. Ketika nilainya adalah **3** berarti jumlah buffer maksimal ada **4**.
-Bisa dilihat terdapat IIFE goroutine yang isinya proses penerimaan data dari channel `messages`, untuk kemudian datanya ditampilkan. Setelah goroutine tersebut dieksekusi, perulangan dijalankan dengan di-masing-masing perulangan dilakukan pengiriman data. Total ada 5 data dikirim lewat channel `messages` secara sekuensial.
+Terdapat juga IIFE goroutine yang isinya proses penerimaan data dari channel `messages`, untuk kemudian datanya ditampilkan. Setelah goroutine tersebut dieksekusi, perulangan dijalankan dengan di-masing-masing perulangan dilakukan pengiriman data. Total ada 5 data dikirim lewat channel `messages` secara sekuensial.

-Bisa dilihat output di atas, pada proses pengiriman data ke-4, diikuti dengan proses penerimaan data; yang kedua proses tersebut berlangsung secara blocking.
+Terlihat di output, proses pengiriman data indeks ke-4 adalah diikuti dengan proses penerimaan data yang proses transfernya sendiri dilakukan *syncrhonous* atau blocking.
Pengiriman data indeks ke 0, 1, 2 dan 3 akan berjalan secara asynchronous, hal ini karena channel ditentukan nilai buffer-nya sebanyak 3 (ingat, jika nilai buffer adalah 3, maka 4 data yang akan di-buffer). Pengiriman selanjutnya (indeks 5) hanya akan terjadi jika ada salah satu data dari ke-empat data yang sebelumnya telah dikirimkan sudah diterima (dengan serah terima data yang bersifat blocking). Setelahnya, pengiriman data kembali dilakukan secara asynchronous (karena sudah ada slot buffer ada yang kosong).
Karena pengiriman dan penerimaan data via buffered channel terjadi tidak selalu synchronous (tergantung jumlah buffer-nya), maka ada kemungkinan dimana eksekusi program selesai namun tidak semua data diterima via channel `messages`. Karena alasan ini pada bagian akhir ditambahkan statement `time.Sleep(1 * time.Second)` agar ada jeda 1 detik sebelum program selesai.
-#### • Fungsi `time.Sleep()`
+#### ◉ Fungsi `time.Sleep()`
Fungsi ini digunakan untuk menambahkan delay sebelum statement berikutnya dieksekusi. Durasi delay ditentukan oleh parameter, misal `1 * time.Second` maka durasi delay adalah 1 detik.
-Lebih detailnya mengenai fungsi `time.Sleep()` dan `time.Second` dibahas pada chapter terpisah, yaitu [Time Duration](https://dasarpemrogramangolang.novalagung.com/A-time-duration.html).
+Lebih detailnya mengenai fungsi `time.Sleep()` dan `time.Second` dibahas pada chapter terpisah, yaitu [Time Duration](/A-time-duration.html).
---
From 7daddc0a8c695abcd47523cb5c65b062506ac953 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:28 +0700
Subject: [PATCH 295/522] New translations a-channel-range-close.md (English)
---
en/content-en/A-channel-range-close.md | 37 +++++++++++++-------------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/en/content-en/A-channel-range-close.md b/en/content-en/A-channel-range-close.md
index e63aa9b4b..1b4527051 100644
--- a/en/content-en/A-channel-range-close.md
+++ b/en/content-en/A-channel-range-close.md
@@ -1,16 +1,17 @@
# A.34. Channel - Range dan Close
-Proses *retrieving* data dari banyak channel bisa lebih mudah dilakukan dengan memanfaatkan kombinasi keyword `for` - `range`.
+Proses penerimaan/*retrieving* data dari banyak channel bisa lebih mudah dilakukan dengan memanfaatkan kombinasi keyword `for` - `range`. Penerapannnya cukup mudah, yaitu dengan menuliskan keyword `for` - `range` pada variabel channel.
-`for` - `range` jika diterapkan pada channel berfungsi untuk handle penerimaan data. Setiap kali ada pengiriman data via channel, maka akan men-trigger perulangan `for` - `range`. Perulangan akan berlangsung terus-menerus seiring pengiriman data ke channel yang dipergunakan. Dan perulangan hanya akan berhenti jika channel yang digunakan tersebut di **close** atau di non-aktifkan. Fungsi `close` digunakan utuk me-non-aktifkan channel.
+Cara kerjanya:
-Channel yang sudah di-close tidak bisa digunakan lagi baik untuk menerima data ataupun untuk mengirim data, itulah mengapa perulangan `for` - `range` juga berhenti.
+- Transaksi data via channel men-trigger perulangan `for` - `range`. Perulangan akan berlangsung seiring terjadinya pengiriman data ke channel yang di-iterasi.
+- Perulangan tersebut hanya akan berhenti jika channel di-**close** atau di non-aktifkan via fungsi `close()`. Channel yang sudah di-close tidak bisa digunakan lagi baik untuk menerima data ataupun untuk mengirim data.
-## A.34.1. Penerapan `for` - `range` - `close` Pada Channel
+## A.34.1. Penerapan `for` - `range` - `close`
-Berikut adalah contoh program menggunakan `for` - `range` untuk menerima data dari channel.
+Berikut adalah contoh program pengaplikasian `for`, `range`, dan `close` untuk penerimaan data dari channel.
-Ok, pertama siapkan fungsi `sendMessage()` yang tugasnya mengirim data via channel. Di dalam fungsi ini dijalankan perulangan sebanyak 20 kali, ditiap perulangannya data dikirim ke channel. Channel di-close setelah semua data selesai dikirim.
+Pertama siapkan fungsi `sendMessage()` yang tugasnya mengirim data via channel. Di dalam fungsi ini dijalankan perulangan sebanyak 20 kali, ditiap perulangannya data dikirim ke channel. Channel di-close setelah semua data selesai dikirim.
```go
func sendMessage(ch chan<- string) {
@@ -21,7 +22,7 @@ func sendMessage(ch chan<- string) {
}
```
-Siapkan juga fungsi `printMessage()` untuk handle penerimaan data. Di dalam fungsi tersebut, channel di-looping menggunakan `for` - `range`. Di tiap looping, data yang diterima dari channel ditampilkan.
+Siapkan juga fungsi `printMessage()` untuk handle penerimaan data. Di dalam fungsi tersebut, channel di-looping menggunakan `for` - `range`. Di setiap iterasi, data yang diterima dari channel ditampilkan.
```go
func printMessage(ch <-chan string) {
@@ -31,7 +32,7 @@ func printMessage(ch <-chan string) {
}
```
-Buat channel baru dalam fungsi `main()`, jalankan `sendMessage()` sebagai goroutine (dengan ini 20 data yang berada dalam fungsi tersebut dikirimkan via goroutine baru). Tak lupa jalankan juga fungsi `printMessage()`.
+Selanjutnya, buat channel baru dalam fungsi `main()`, jalankan `sendMessage()` sebagai goroutine. Dengan ini 20 data yang berada dalam fungsi tersebut dikirimkan via goroutine baru. Tak lupa jalankan juga fungsi `printMessage()`.
```go
func main() {
@@ -43,25 +44,25 @@ func main() {
}
```
-Setelah 20 data sukses dikirim dan diterima, channel `ch` di-non-aktifkan (`close(ch)`). Membuat perulangan data channel dalam `printMessage()` juga akan berhenti.
+Setelah 20 data yang dikirim sukses diterima, channel `ch` di-non-aktifkan dengan adanya statement `close(ch)`. Statement tersebut menghentikan perulangan channel dalam `printMessage()`.

----
+## A.34.2. Penjelasan tambahan
-Berikut adalah penjelasan tambahan mengenai channel.
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
-## A.34.1.1. Channel Direction
+#### ◉ Channel Direction
-Ada yang unik dengan fitur parameter channel yang disediakan oleh Go. Level akses channel bisa ditentukan, apakah hanya sebagai penerima, pengirim, atau penerima sekaligus pengirim. Konsep ini disebut dengan **channel direction**.
+Go mendesain API channel untuk mendukung level akses channel, apakah hanya sebagai penerima, pengirim, atau penerima sekaligus pengirim. Konsep ini disebut dengan **channel direction**.
Cara pemberian level akses adalah dengan menambahkan tanda `<-` sebelum atau setelah keyword `chan`. Untuk lebih jelasnya bisa dilihat di list berikut.
-| Sintaks | Penjelasan |
-|:--------------------- |:---------------------------------------------------------------------- |
-| `ch chan string` | Parameter `ch` bisa digunakan untuk **mengirim** dan **menerima** data |
-| `ch chan<- string` | Parameter `ch` hanya bisa digunakan untuk **mengirim** data |
-| `ch <-chan string` | Parameter `ch` hanya bisa digunakan untuk **menerima** data |
+| Sintaks | Penjelasan |
+|:--------------------- |:------------------------------------------------------- |
+| `ch chan string` | Parameter `ch` untuk **mengirim** dan **menerima** data |
+| `ch chan<- string` | Parameter `ch` hanya untuk **mengirim** data |
+| `ch <-chan string` | Parameter `ch` hanya untuk **menerima** data |
Pada kode di atas bisa dilihat bahwa secara default channel akan memiliki kemampuan untuk mengirim dan menerima data. Untuk mengubah channel tersebut agar hanya bisa mengirim atau menerima saja, dengan memanfaatkan simbol `<-`.
From 927a75e2e77895b2707ab0508691c0f49083b352 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:29 +0700
Subject: [PATCH 296/522] New translations a-channel-select.md (English)
---
en/content-en/A-channel-select.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/en/content-en/A-channel-select.md b/en/content-en/A-channel-select.md
index 3f45edaf0..07b9603bd 100644
--- a/en/content-en/A-channel-select.md
+++ b/en/content-en/A-channel-select.md
@@ -1,16 +1,16 @@
# A.33. Channel - Select
-Disediakannya channel membuat engineer menjadi mudah dalam me-manage goroutine. Namun perlu di-ingat, meskipun lewat channel manajemen goroutine menjadi mudah, fungsi utama channel bukan untuk kontrol, melainkan untuk sharing data antar goroutine.
+Channel membuat manajemen goroutine menjadi sangat mudah di Go. Namun perlu di-ingat, fungsi utama channel adalah bukan untuk kontrol eksekusi goroutine, melainkan untuk sharing data atau komunikasi goroutine.
-> Nantinya pada chapter [A.59. sync.WaitGroup](/A-waitgroup.html) akan dibahas secara komprehensif bagaimana cara optimal mengontrol goroutine.
+> Pada chapter [A.59. sync.WaitGroup](/A-waitgroup.html) akan dibahas secara komprehensif tentang cara yang lebih optimal untuk kontrol eksekusi goroutine.
-Ada kalanya kita butuh tak hanya satu channel saja untuk melakukan komunikasi data antar goroutine. Tergantung jenis kasusnya, sangat mungkin lebih dari satu channel dibutuhkan. Nah, di situlah kegunaan dari `select`. Select ini mempermudah kontrol komunikasi data lewat satu ataupun banyak channel.
+Tergantung jenis kasusnya, ada kalanya kita butuh lebih dari satu channel untuk komunikasi data antar goroutine. Penerimaan data pada banyak goroutine penerapannya masih sama, yaitu dengan menambahkan karakter `<-` pada statement. Selain itu, ada juga cara lain yaitu menggunakan keyword `select`. Keyword ini mempermudah kontrol penerimaan data via satu atau lebih dari satu channel.
Cara penggunaan `select` untuk kontrol channel sama seperti penggunaan `switch` untuk seleksi kondisi.
## A.33.1. Penerapan Keyword `select`
-Program berikut merupakan contoh sederhana penerapan select dalam channel. Dipersiapkan 2 buah goroutine, satu untuk pencarian rata-rata, dan satu untuk nilai tertinggi. Hasil operasi di masing-masing goroutine dikirimkan ke fungsi `main()` via channel (ada dua channel). Di fungsi `main()` sendiri, data tersebut diterima dengan memanfaatkan keyword `select`.
+Program berikut merupakan contoh sederhana penerapan keyword `select`. Di sini disiapkan 2 buah goroutine, satu untuk menghitung rata-rata dari data array numerik, dan satu lagi untuk pencarian nilai tertinggi. Hasil operasi di masing-masing goroutine dikirimkan ke fungsi `main()` via channel (ada dua channel). Di fungsi `main()` sendiri, data tersebut diterima dengan memanfaatkan keyword `select`.
Ok, langsung saja kita praktek. Pertama, siapkan 2 fungsi yang sudah dibahas di atas. Fungsi pertama digunakan untuk mencari rata-rata, dan fungsi kedua untuk penentuan nilai tertinggi dari sebuah slice.
From b8122beff076617e45b924502e591e73a89a410a Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:30 +0700
Subject: [PATCH 297/522] New translations a-channel-timeout.md (English)
---
en/content-en/A-channel-timeout.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/en/content-en/A-channel-timeout.md b/en/content-en/A-channel-timeout.md
index 23241f6af..5177a51d8 100644
--- a/en/content-en/A-channel-timeout.md
+++ b/en/content-en/A-channel-timeout.md
@@ -1,12 +1,12 @@
# A.35. Channel - Timeout
-Teknik channel timeout digunakan untuk mengontrol penerimaan data dari channel mengacu ke kapan waktu diterimanya data, dengan durasi timeout bisa kita tentukan sendiri.
+Teknik channel timeout digunakan untuk kontrol waktu penerimaan data pada channel, berapa lama channel tersebut harus menunggu hingga akhirnya suatu penerimaan data dianggap timeout.
-Ketika tidak ada aktivitas penerimaan data dalam durasi yang sudah ditentukan, maka blok timeout dieksekusi.
+Durasi penerimaan kita tentukan sendiri. Ketika tidak ada aktivitas penerimaan data dalam durasi tersebut, blok timeout dijalankan.
## A.35.1. Penerapan Channel Timeout
-Berikut adalah program sederhana contoh pengaplikasian timeout pada channel. Sebuah goroutine baru dijalankan dengan tugas mengirimkan data setiap interval tertentu, dengan durasi interval-nya sendiri adalah acak/random.
+Berikut adalah program sederhana contoh pengaplikasian timeout pada channel. Sebuah goroutine dijalankan dengan tugas adalah mengirimkan data secara berulang dalam interval tertentu, dengan durasi interval-nya sendiri adalah acak/random.
```go
package main
@@ -45,8 +45,8 @@ func retreiveData(ch <-chan int) {
Ada 2 blok kondisi pada `select` tersebut.
- - Kondisi `case data := <-messages:`, akan terpenuhi ketika ada serah terima data pada channel `messages`.
- - Kondisi `case <-time.After(time.Second * 5):`, akan terpenuhi ketika tidak ada aktivitas penerimaan data dari channel dalam durasi 5 detik. Blok inilah yang kita sebut sebagai blok timeout.
+- Kondisi `case data := <-messages:`, akan terpenuhi ketika ada serah terima data pada channel `messages`.
+- Kondisi `case <-time.After(time.Second * 5):`, akan terpenuhi ketika tidak ada aktivitas penerimaan data dari channel dalam durasi 5 detik. Blok inilah yang kita sebut sebagai blok timeout.
Terakhir, kedua fungsi tersebut dipanggil di `main()`.
@@ -61,7 +61,7 @@ func main() {
}
```
-Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika tidak ada aktivitas penerimaan dari channel dalam durasi 5 detik, perulangan pengecekkan channel diberhentikan.
+Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika dalam durasi 5 detik tidak ada aktivitas penerimaan sama sekali, maka dianggap timeout dan perulangan pengecekkan channel dihentikan.

From b29c66eadd1f6764b86096ebb7d64f4a8b1e3140 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:31 +0700
Subject: [PATCH 298/522] New translations a-channel.md (English)
---
en/content-en/A-channel.md | 39 +++++++++++++++++++++-----------------
1 file changed, 22 insertions(+), 17 deletions(-)
diff --git a/en/content-en/A-channel.md b/en/content-en/A-channel.md
index 1a65b1e28..583ada27a 100644
--- a/en/content-en/A-channel.md
+++ b/en/content-en/A-channel.md
@@ -1,16 +1,18 @@
# A.31. Channel
-**Channel** digunakan untuk menghubungkan goroutine satu dengan goroutine lain. Mekanisme yang dilakukan adalah serah-terima data lewat channel tersebut. Dalam komunikasinya, sebuah channel difungsikan sebagai pengirim di sebuah goroutine, dan juga sebagai penerima di goroutine lainnya. Pengiriman dan penerimaan data pada channel bersifat **blocking** atau **synchronous**.
+**Channel** digunakan untuk menghubungkan goroutine satu dengan goroutine lain dengan mekanisme serah terima data, jadi harus ada data yang dikirim dari goroutine A untuk kemudian diterima di goroutine B.
-
+Peran channel adalah sebagai media perantara bagi pengirim data dan juga penerima data. Jadi channel adalah *thread safe*, aman digunakan di banyak goroutine.
+
+Pengiriman dan penerimaan data pada channel bersifat **blocking** atau **synchronous**. Artinya, statement di-bawah syntax pengiriman dan penerimaan data via channel hanya akan dieksekusi setelah proses serah terima berlangsung dan selesai.
-Pada chapter ini kita akan belajar mengenai pemanfaatan channel.
+
## A.31.1. Penerapan Channel
-Channel merupakan sebuah variabel, dibuat dengan menggunakan kombinasi keyword `make` dan `chan`. Variabel channel memiliki satu tugas, menjadi pengirim, atau penerima data.
+Channel berbentuk variabel, dibuat dengan menggunakan kombinasi keyword `make` dan `chan`.
-Program berikut adalah contoh implementasi channel. 3 buah goroutine dieksekusi, di masing-masing goroutine terdapat proses pengiriman data lewat channel. Data tersebut akan diterima 3 kali di goroutine utama `main`.
+Program berikut adalah contoh implementasi channel. 3 buah goroutine dieksekusi, di masing-masing goroutine terdapat proses pengiriman data lewat channel. Kesemua data tersebut nantinya diterima oleh di goroutine utama yaitu proses yang dijalankan di dalam blok fungsi `main()`.
```go
package main
@@ -43,7 +45,7 @@ func main() {
}
```
-Pada kode di atas, variabel `messages` dideklarasikan bertipe channel `string`. Cara pembuatan channel yaitu dengan menuliskan keyword `make` dengan isi keyword `chan` diikuti dengan tipe data channel yang diinginkan.
+Pada kode di atas, variabel `messages` dideklarasikan bertipe channel `string`. Contoh cara pembuatan channel bisa dilihat di situ, yaitu dengan memanggil fungsi `make()` dengan isi adalah keyword `chan` diikuti dengan tipe data channel yang diinginkan.
```go
var messages = make(chan string)
@@ -75,21 +77,24 @@ var message1 = <-messages
fmt.Println(message1)
```
-Penerimaan channel bersifat blocking. Artinya statement `var message1 = <-messages` hingga setelahnya tidak akan dieksekusi sebelum ada data yang dikirim lewat channel.
+Penerimaan channel bersifat blocking. Artinya:
-Ke semua data yang dikirim dari tiga goroutine berbeda tersebut datanya akan diterima secara berurutan oleh `message1`, `message2`, `message3`; untuk kemudian ditampilkan.
+- Statement `var message1 = <-messages` hingga setelahnya tidak akan dieksekusi sebelum ada data yang dikirim lewat channel.
+- Berlaku juga dengan statement `messages <- data`. Statement dibawahnya tidak akan dieksekusi hingga data yang dikirim ke channel `messages` benar-benar diterima oleh penerima, yaitu variabel `message1`.
+
+Ke semua data yang dikirim dari tiga goroutine berbeda tersebut nantinya diterima oleh `message1`, `message2`, `message3`; untuk kemudian ditampilkan.

Dari screenshot output di atas bisa dilihat bahwa text yang dikembalikan oleh `sayHelloTo` tidak selalu berurutan, meskipun penerimaan datanya adalah berurutan. Hal ini dikarenakan, pengiriman data adalah dari 3 goroutine yang berbeda, yang kita tidak tau mana yang prosesnya selesai lebih dulu. Goroutine yang dieksekusi lebih awal belum tentu selesai lebih awal, yang jelas proses yang selesai lebih awal datanya akan diterima lebih awal.
-Karena pengiriman dan penerimaan data lewat channel bersifat **blocking**, tidak perlu memanfaatkan sifat blocking dari fungsi sejenis `fmt.Scanln()` atau lainnya, untuk mengantisipasi goroutine utama `main` selesai sebelum ketiga goroutine di atas selesai.
+Karena pengiriman dan penerimaan data lewat channel bersifat **blocking**, tidak perlu memanfaatkan sifat blocking dari fungsi seperti `fmt.Scanln()` (atau lainnya) untuk mengantisipasi goroutine utama (yaitu `main`) selesai sebelum ketiga goroutine di atas selesai.
## A.31.2. Channel Sebagai Tipe Data Parameter
-Variabel channel bisa di-pass ke fungsi lain sebagai parameter. Cukup tambahkan keyword `chan` pada deklarasi parameter agar operasi pass channel variabel bisa dilakukan.
+Variabel channel bisa di-pass ke fungsi lain via parameter. Cukup tambahkan keyword `chan` pada deklarasi parameter agar operasi pass channel variabel bisa dilakukan.
-Siapkan fungsi `printMessage` dengan parameter adalah channel. Lalu ambil data yang dikirimkan lewat channel tersebut untuk ditampilkan.
+Siapkan fungsi `printMessage()` dengan parameter adalah channel. Lalu ambil data yang dikirimkan lewat channel tersebut untuk ditampilkan.
```go
func printMessage(what chan string) {
@@ -97,7 +102,7 @@ func printMessage(what chan string) {
}
```
-Setelah itu ubah implementasi di fungsi `main`.
+Setelah itu ubah implementasi di fungsi `main()`.
```go
func main() {
@@ -120,17 +125,17 @@ func main() {
Output program di atas sama dengan program sebelumnya.
-Parameter `what` fungsi `printMessage` bertipe channel `string`, bisa dilihat dari kode `chan string` pada cara deklarasinya. Operasi serah-terima data akan bisa dilakukan pada variabel tersebut, dan akan berdampak juga pada variabel `messages` di fungsi `main`.
+Parameter `what` pada fungsi `printMessage()` bertipe channel `string`, bisa dilihat dari kode `chan string` pada cara deklarasinya. Operasi serah-terima data akan bisa dilakukan pada variabel tersebut, dan akan berdampak juga pada variabel `messages` di fungsi `main()`.
Passing data bertipe channel lewat parameter sifatnya **pass by reference**, yang di transferkan adalah pointer datanya, bukan nilai datanya.

----
+## A.32.3. Penjelasan tambahan
-Berikut merupakan penjelasan tambahan untuk kode di atas.
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
-#### • Iterasi Data Slice/Array Langsung Pada Saat Inisialisasi
+#### ◉ Iterasi Data Slice/Array Langsung Pada Saat Inisialisasi
Data slice yang baru di inisialisasi bisa langsung di-iterasi, caranya mudah dengan menuliskannya langsung setelah keyword `range`.
@@ -140,7 +145,7 @@ for _, each := range []string{"wick", "hunt", "bourne"} {
}
```
-#### • Eksekusi Goroutine Pada IIFE
+#### ◉ Eksekusi Goroutine Pada IIFE
Eksekusi goroutine tidak harus pada fungsi atau closure yang sudah terdefinisi. Sebuah IIFE juga bisa dijalankan sebagai goroutine baru. Caranya dengan langsung menambahkan keyword `go` pada waktu deklarasi-eksekusi IIFE-nya.
From 8ba4bb869100887aa9d8e0652a4147a0bae14e1e Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:32 +0700
Subject: [PATCH 299/522] New translations a-client-http-request-simple.md
(English)
---
en/content-en/A-client-http-request-simple.md | 36 +++++++++----------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/en/content-en/A-client-http-request-simple.md b/en/content-en/A-client-http-request-simple.md
index 00189b2fe..79cda92b9 100644
--- a/en/content-en/A-client-http-request-simple.md
+++ b/en/content-en/A-client-http-request-simple.md
@@ -1,6 +1,6 @@
# A.55. Simple Client HTTP Request
-Pada chapter sebelumnya telah dibahas bagaimana membuat Web Service API yang mem-provide data JSON, pada chapter ini kita akan belajar mengenai cara untuk mengkonsumsi data tersebut.
+Pada chapter sebelumnya telah dibahas bagaimana cara membuat Web Service API yang response data-nya berbentuk JSON. Pada chapter ini kita akan belajar mengenai cara untuk mengkonsumsi data tersebut.
Pastikan anda sudah mempraktekkan apa-apa yang ada pada chapter sebelumnya ([A.54. Web Service API Server](/A-web-service-api.html)), karena web service yang telah dibuat di situ juga dipergunakan pada chapter ini.
@@ -8,9 +8,9 @@ Pastikan anda sudah mempraktekkan apa-apa yang ada pada chapter sebelumnya ([A.5
## A.55.1. Penggunaan HTTP Request
-Package `net/http`, selain berisikan tools untuk keperluan pembuatan web, juga berisikan fungsi-fungsi untuk melakukan http request. Salah satunya adalah `http.NewRequest()` yang akan kita bahas di sini.
+Package `net/http`, selain berisikan tools untuk keperluan pembuatan web, juga berisikan fungsi-fungsi untuk melakukan http request. Salah satunya adalah `http.NewRequest()` yang akan kita bahas di sini. Untuk menggunakannya pastikan import package-nya terlebih dahulu.
-Sebelumnya, import package yang dibutuhkan. Dan siapkan struct `student` yang nantinya akan dipakai sebagai tipe data reponse dari web API. Struk tersebut skema nya sama dengan yang ada pada chapter ([A.54. Web Service API Server](/A-web-service-api.html)).
+Kemudian siapkan struct `student` yang nantinya akan dipakai sebagai tipe data reponse dari web API. Struk tersebut skema nya sama dengan yang ada pada chapter ([A.54. Web Service API Server](/A-web-service-api.html)).
```go
package main
@@ -60,19 +60,19 @@ Statement `&http.Client{}` menghasilkan instance `http.Client`. Objek ini nantin
Fungsi `http.NewRequest()` digunakan untuk membuat request baru. Fungsi tersebut memiliki 3 parameter yang wajib diisi.
- 1. Parameter pertama, berisikan tipe request **POST** atau **GET** atau lainnya
- 2. Parameter kedua, adalah URL tujuan request
- 3. Parameter ketiga, form data request (jika ada)
+1. Parameter pertama, berisikan tipe request **POST** atau **GET** atau lainnya
+2. Parameter kedua, adalah URL tujuan request
+3. Parameter ketiga, form data request (jika ada)
-Fungsi tersebut menghasilkan instance bertipe `http.Request`. Objek tersebut nantinya disisipkan pada saat eksekusi request.
+Fungsi tersebut menghasilkan instance bertipe `http.Request` yang nantinya digunakan saat eksekusi request.
-Cara eksekusi request sendiri adalah dengan memanggil method `Do()` pada instance `http.Client` yang sudah dibuat, dengan parameter adalah instance request-nya. Contohnya seperti pada `client.Do(request)`.
+Cara eksekusi request sendiri adalah dengan memanggil method `Do()` pada variabel `client` yang sudah dibuat. Fungsi `Do()` dipanggil dengan disisipkan argument fungsi yaitu object `request`. Penulisannya: `client.Do(request)`.
-Method tersebut mengembalikan instance bertipe `http.Response`, yang di dalamnya berisikan informasi yang dikembalikan dari web API.
+Method tersebut mengembalikan instance bertipe `http.Response` yang di contoh ditampung oleh variabel `response`. Dari data response tersebut kita bisa mengakses informasi yang berhubungan dengan HTTP response, termasuk response body.
-Data response bisa diambil lewat property `Body` dalam bentuk string. Gunakan JSON Decoder untuk mengkonversinya menjadi bentuk JSON. Contohnya bisa dilihat di kode di atas, `json.NewDecoder(response.Body).Decode(&data)`. Setelah itu barulah kita bisa menampilkannya.
+Data response body tersedia via property `Body` dalam tipe `[]byte`. Gunakan JSON Decoder untuk mengkonversinya menjadi bentuk JSON. Contohnya bisa dilihat di kode di atas, `json.NewDecoder(response.Body).Decode(&data)`.
-Perlu diketahui, data response perlu di-**close** setelah tidak dipakai. Caranya seperti pada kode `defer response.Body.Close()`.
+Perlu diketahui, data response perlu di-**close** setelah tidak dipakai. Caranya dengan memanggil method `Close()` milik property `Body` yang dalam penerapannya umumnya di-defer. Contohnya: `defer response.Body.Close()`.
Selanjutnya, eksekusi fungsi `fetchUsers()` dalam fungsi `main()`.
@@ -90,20 +90,20 @@ func main() {
}
```
-Ok, terakhir sebelum memulai testing, pastikan telah run aplikasi pada chapter sebelumya ([A.54. Web Service API Server](/A-web-service-api.html)). Kemudian start prompt cmd/terminal baru dan jalankan program yang barusan kita buat pada chapter ini.
+Ok, terakhir sebelum memulai testing, pastikan telah run aplikasi pada chapter sebelumya ([A.54. Web Service API Server](/A-web-service-api.html)). Setelah itu start prompt cmd/terminal baru dan jalankan program yang telah dibuat di chapter ini.

## A.55.2. HTTP Request Dengan Form Data
-Untuk menyisipkan data pada sebuah request, ada beberapa hal yang perlu ditambahkan. Yang pertama, import beberapa package lagi, `bytes` dan `net/url`.
+Untuk menyisipkan data pada sebuah request, ada beberapa hal yang perlu ditambahkan. Pertama, import package `bytes` dan `net/url`.
```go
import "bytes"
import "net/url"
```
-Buat fungsi baru, isinya request ke [http://localhost:8080/user](http://localhost:8080/user) dengan data yang disisipkan adalah `ID`.
+Kemudian buat fungsi baru, isinya request ke [http://localhost:8080/user](http://localhost:8080/user) dengan data yang disisipkan adalah `ID`.
```go
func fetchUser(ID string) (student, error) {
@@ -136,13 +136,13 @@ func fetchUser(ID string) (student, error) {
}
```
-Isi fungsi di atas bisa dilihat memiliki beberapa kemiripan dengan fungsi `fetchUsers()` sebelumnya.
+Isi fungsi `fetchUser()` memiliki beberapa kemiripan dengan fungsi `fetchUsers()` sebelumnya.
Statement `url.Values{}` akan menghasilkan objek yang nantinya digunakan sebagai form data request. Pada objek tersebut perlu di set data apa saja yang ingin dikirimkan menggunakan fungsi `Set()` seperti pada `param.Set("id", ID)`.
-Statement `bytes.NewBufferString(param.Encode())` maksudnya, objek form data di-encode lalu diubah menjadi bentuk `bytes.Buffer`, yang nantinya disisipkan pada parameter ketiga pemanggilan fungsi `http.NewRequest()`.
+Statement `bytes.NewBufferString(param.Encode())` melakukan proses encoding pada data param untuk kemudian diubah menjadi bentuk `bytes.Buffer`. Nantinya data buffer tersebut disisipkan pada parameter ketiga pemanggilan fungsi `http.NewRequest()`.
-Karena data yang akan dikirim di-encode, maka pada header perlu di set tipe konten request-nya. Kode `request.Header.Set("Content-Type", "application/x-www-form-urlencoded")` artinya tipe konten request di set sebagai `application/x-www-form-urlencoded`.
+Karena data yang akan dikirim adalah *encoded*, maka pada header perlu dituliskan juga tipe encoding-nya. Kode `request.Header.Set("Content-Type", "application/x-www-form-urlencoded")` menandai bahwa HTTP request berisi body yang ter-encode sesuai spesifikasi `application/x-www-form-urlencoded`.
> Pada konteks HTML, HTTP Request yang di trigger dari tag `
` secara default tipe konten-nya sudah di set `application/x-www-form-urlencoded`. Lebih detailnya bisa merujuk ke spesifikasi HTML form [http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1)
@@ -168,7 +168,7 @@ Untuk keperluan testing, kita hardcode `ID` nilainya `"E001"`. Jalankan program
## A.55.3. Secure & Insecure HTTP Request
-Kita telah mempelajari bagaimana cara membuat http request sederhana untuk kirim data dan juga ambil data. Nantinya pada chapter [C.27. Secure & Insecure Client HTTP Request](/C-secure-insecure-client-http-request.html) kita akan belajar cara membuat http client request yang lebih *njlimet* untuk kasus yang lebih advance, tapi sabar dulu hehe.
+Sampai sini kita telah belajar bagaimana cara membuat http request sederhana untuk kirim data dan juga ambil data. Nantinya pada chapter [C.27. Secure & Insecure Client HTTP Request](/C-secure-insecure-client-http-request.html) pembelajaran topik HTTP request dilanjutkan kembali, kita akan bahas tentang aspek keamanan/security suatu HTTP request.
---
From 0d225a3d370fc011a51dc9f3d68cd158bf5e55bd Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:33 +0700
Subject: [PATCH 300/522] New translations a-command-line-args-flag.md
(English)
---
en/content-en/A-command-line-args-flag.md | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/en/content-en/A-command-line-args-flag.md b/en/content-en/A-command-line-args-flag.md
index 4ba0648d5..62f7af27d 100644
--- a/en/content-en/A-command-line-args-flag.md
+++ b/en/content-en/A-command-line-args-flag.md
@@ -1,14 +1,12 @@
# A.48. Arguments & Flag
-**Arguments** adalah data opsional yang disisipkan ketika eksekusi program. Sedangkan **flag** merupakan ekstensi dari argument. Dengan flag, penulisan argument menjadi lebih rapi dan terstruktur.
+**Arguments** adalah data argument opsional yang disisipkan ketika eksekusi program. Sedangkan **flag** merupakan ekstensi dari argument. Dengan flag, penulisan argument menjadi lebih rapi dan terstruktur.
-Pada chapter ini kita akan belajar tentang penggunaan arguments dan flag.
+Pada chapter ini kita akan belajar tentang penerapan arguments dan flag.
## A.48.1. Penggunaan Arguments
-Data arguments bisa didapat lewat variabel `os.Args` (package `os` perlu di-import terlebih dahulu). Data tersebut tersimpan dalam bentuk array dengan pemisah adalah tanda spasi.
-
-Berikut merupakan contoh penggunaannya.
+Data arguments bisa didapat lewat variabel `os.Args` (package `os` perlu di-import terlebih dahulu). Data tersebut tersimpan dalam bentuk array. Setiap data argument yang disisipkan saat pemanggilan program, datanya dipecah menggunakan karakter spasi lalu di-map ke bentuk array. Contoh penerapan:
```go
package main
@@ -27,9 +25,7 @@ func main() {
}
```
-Pada saat eksekusi program disisipkan juga argument-nya. Sebagai contoh disisipkan 3 buah data sebagai argumen, yaitu: `banana`, `potato`, dan `ice cream`.
-
-Untuk eksekusinya sendiri bisa menggunakan `go run` ataupun dengan cara build-execute.
+Argument disisipkan saat eksekusi program. Sebagai contoh, kita ingin menyisipkan 3 buah argumen berikut: `banana`, `potato`, dan `ice cream`. Maka penulisan saat pemanggilan program-nya seperti ini:
- Menggunakan `go run`
@@ -44,11 +40,13 @@ Untuk eksekusinya sendiri bisa menggunakan `go run` ataupun dengan cara build-ex
$ ./bab45 banana potato "ice cream"
```
-Variabel `os.Args` mengembalikan tak hanya arguments saja, tapi juga path file executable (jika eksekusi-nya menggunakan `go run` maka path akan merujuk ke folder temporary). Gunakan `os.Args[1:]` untuk mengambil slice arguments-nya saja.
+Output program:

-Bisa dilihat pada kode di atas, bahwa untuk data argumen yang ada karakter spasi nya (), maka harus diapit tanda petik (`"`), agar tidak dideteksi sebagai 2 argumen.
+Bisa dilihat pada kode di atas, bahwa untuk data argumen yang ada karakter spasi-nya () harus dituliskan dengan diapit tanda petik (`"`) agar tidak dideteksi sebagai 2 argumen.
+
+Variabel `os.Args` mengembalikan tak hanya arguments saja, tapi juga path file executable (jika eksekusi-nya menggunakan `go run` maka path akan merujuk ke folder temporary). Maka disini penting untuk hanya mengambil element index ke 1 hingga seterusnya saja via statement `os.Args[1:]`.
## A.48.2. Penggunaan Flag
@@ -85,7 +83,7 @@ fmt.Println(*dataName)
Kode tersebut maksudnya adalah, disiapkan flag bertipe `string`, dengan key adalah `name`, dengan nilai default `"anonymous"`, dan keterangan `"type your name"`. Nilai flag nya sendiri akan disimpan ke dalam variabel `dataName`.
-Nilai balik fungsi `flag.String()` adalah string pointer, jadi perlu di-*dereference* terlebih dahulu agar bisa mendapatkan nilai aslinya (`*dataName`).
+Nilai balik fungsi `flag.String()` adalah string pointer, jadi perlu di-*dereference* terlebih dahulu untuk mengakses nilai aslinya (`*dataName`).

@@ -110,7 +108,7 @@ Sebenarnya ada 2 cara deklarasi flag yang bisa digunakan, dan cara di atas merup
Cara kedua mirip dengan cara pertama, perbedannya adalah kalau di cara pertama nilai pointer flag dikembalikan lalu ditampung variabel. Sedangkan pada cara kedua, nilainya diambil lewat parameter pointer.
-Agar lebih jelas perhatikan contoh berikut.
+Agar lebih jelas perhatikan contoh berikut:
```go
// cara ke-1
From 5d16c465932902301d453bb332ca2f75d64f9495 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:35 +0700
Subject: [PATCH 301/522] New translations a-concurrency-pipeline.md (English)
---
en/content-en/A-concurrency-pipeline.md | 52 +++++++++++++------------
1 file changed, 27 insertions(+), 25 deletions(-)
diff --git a/en/content-en/A-concurrency-pipeline.md b/en/content-en/A-concurrency-pipeline.md
index 13470a522..b8282dcaf 100644
--- a/en/content-en/A-concurrency-pipeline.md
+++ b/en/content-en/A-concurrency-pipeline.md
@@ -1,14 +1,14 @@
# A.62. Concurrency Pattern: Pipeline
-Kita sudah membahas beberapa kali tentang *concurrency* atau konkurensi di Go programming. Pada chapter ini kita akan belajar salah satu best practice konkurensi dalam Go, yaitu *pipeline*, yang merupakan satu di antara banyak *concurrency pattern* yang ada di Go.
+Kita sudah membahas beberapa kali tentang topik *concurrency* atau konkurensi di Go programming. Pada chapter ini kita akan belajar salah satu best practice konkurensi dalam Go, yaitu teknik *pipeline*, yang merupakan satu di antara banyak *concurrency pattern* yang ada di Go.
-Go memiliki beberapa API untuk keperluan konkurensi, di antara *goroutine* dan *channel*. Dengan memanfaatkan APIs yang ada kita bisa membentuk sebuah *streaming data pipeline* yang benefitnya adalah efisiensi penggunaan I/O dan efisiensi penggunaan banyak CPU.
+Go memiliki beberapa API untuk keperluan konkurensi, dua diantaranya adalah *goroutine* dan *channel*. Dengan memanfaatkan APIs yang ada kita bisa membentuk sebuah *streaming data pipeline* yang benefitnya adalah efisiensi penggunaan I/O dan efisiensi penggunaan banyak CPU.
## A.62.1. Konsep *Pipeline*
Definisi *pipeline* yang paling mudah versi penulis adalah **beberapa/banyak proses yang berjalan secara konkuren yang masing-masing proses merupakan bagian dari serangkaian tahapan proses yang berhubungan satu sama lain**.
-Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang di mana database yang di backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut:
+Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang di mana database server yang perlu di-backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut:
1. Kita perlu data *list* dari semua database yang harus di-backup, beserta alamat akses dan kredensial-nya.
2. Kita jalankan proses backup, bisa secara sekuensial (setelah `db1` selesai, lanjut `db2`, lanjut `db3`, dst), atau secara paralel (proses backup `db1`, `db2`, `db3`, dan lainnya dijalankan secara bersamaan).
@@ -18,7 +18,7 @@ Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database
* B. File-file hasil dump kemudian di-*archive* ke bentuk `.zip` atau `.tar.gz` (misalnya).
* C. File archive di-kirim ke server backup, sebagai contoh AWS S3.
-Kalau diperhatikan pada kasus di atas, mungkin akan lebih bagus dari segi performansi kalau proses backup banyak database tersebut dilakukan secara parallel. Dan untuk ini penulis setuju.
+Kalau diperhatikan pada kasus di atas, mungkin akan lebih bagus dari segi performansi kalau proses backup banyak database tersebut dilakukan secara parallel.
Dan akan lebih bagus lagi, jika di masing-masing proses backup database tersebut, proses A, B, dan C dijalankan secara konkuren. Dengan menjadikan ketiga proses tersebut (A, B, C) sebagai proses konkuren, maka I/O akan lebih efisien. Nantinya antara proses A, B, dan C eksekusinya akan tetap berurutan (karena memang harus berjalan secara urut. Tidak boleh kalau misal B lebih dulu dieksekusi kemudian A); akan tetapi, ketika goroutine yang bertanggung jawab untuk eksekusi proses A selesai, kita bisa lanjut dengan eksekusi proses B (yang memang *next stage*-nya proses A) plus eksekusi proses A lainnya (database lain) secara paralel. Jadi goroutine yang handle A ini ga sampai menganggur.
@@ -47,14 +47,14 @@ Untuk mempermudah memahami tabel di atas silakan ikuti penjelasan beruntun berik
Pada contoh ini kita asumsikan pipeline A adalah hanya satu goroutine, pipeline B juga satu goroutine, demikian juga pipeline C. Tapi sebenarnya dalam implementasi *real world* bisa saja ada banyak goroutine untuk masing-masing pipeline (banyak goroutine untuk pipeline A, banyak goroutine untuk pipeline B, banyak goroutine untuk pipeline C).
-Semoga cukup jelas ya. Gpp kalau bingung, nanti kita sambil praktek juga jadi bisa saja temen-temen mulai benar-benar pahamnya setelah praktek.
+Semoga cukup jelas ya. Tapi jika masih bingung, juga tidak apa. Kita sambil praktek juga, dan bisa saja pembaca mulai benar-benar pahamnya saat praktek.
+
+> Penulis sarankan untuk benar-benar memahami setiap bagian praktek ini, karena topik ini merupakan pembahasan yang cukup berat untuk pemula, tapi masih dalam klasifikasi fundamental kalau di Go programming. Bingung tidak apa, nanti bisa di-ulang-ulang, yang penting tidak sekadar *copy-paste*.
## A.62.2. Skenario Praktek
Ok, penjabaran teori sepanjang sungai `nil` tidak akan banyak membawa penjelasan yang real kalau tidak diiringi dengan praktek. So, mari kita mulai praktek.
-Penulis sarankan untuk benar-benar memahami setiap bagian praktek ini, karena topik ini merupakan pembahasan yang cukup berat untuk pemula, tapi masih dalam klasifikasi fundamental kalau di Go programming. Bingung tidak apa, nanti bisa di-ulang-ulang, yang penting tidak sekadar *copy-paste*.
-
Untuk skenario praktek kita tidak menggunakan analogi backup database di atas ya, karena untuk setup environment-nya butuh banyak *effort*. Skenario praktek yang kita pakai adalah mencari [md5 sum](https://en.wikipedia.org/wiki/Md5sum) dari banyak file, kemudian menggunakan hash dari content-nya sebagai nama file. Jadi file yang lama akan di-rename dengan nama baru yaitu hash dari konten file tersebut.
Agar skenario ini bisa kita eksekusi, kita perlu siapkan dulu sebuah program untuk *generate dummy files*.
@@ -63,7 +63,7 @@ Agar skenario ini bisa kita eksekusi, kita perlu siapkan dulu sebuah program unt
Buat project baru dengan nama bebas loss gak reweellll beserta satu buah file bernama `1-dummy-file-generator.go`.
-Dalam file tersebut import beberapa hal dan definisikan, yaitu:
+Dalam file tersebut import dan definisikan beberapa hal, diantaranya:
1. Konstanta `totalFile` yang isinya jumlah file yang ingin di-generate.
1. Variabel `contentLength` yang isinya panjang karakter random yang merupakan isi dari masing-masing *generated* file.
@@ -87,7 +87,7 @@ const contentLength = 5000
var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.59-pipeline-temp")
```
-Kemudian siapkan fungsi `main()` yang isinya statement pemanggilan fungsi `generate()`. Selain itu juga ada beberapa statement untuk keperluan *benchmark* performa dari sisi *execution time*.
+Kemudian siapkan fungsi `main()` yang isinya statement pemanggilan fungsi `generate()`, dan beberapa hal lainnya untuk keperluan *benchmark* performa dari sisi *execution time*.
```go
func main() {
@@ -117,7 +117,7 @@ func randomString(length int) string {
}
```
-Lalu siapkan fungsi `generateFiles()`-nya. isinya kurang lebih adalah generate banyak file sejumlah `totalFile`. Lalu di tiap-tiap file di-isi dengan *random string* dengan lebar sepanjang `contentLength`. Untuk nama file-nya sendiri, formatnya adalah `file-.txt`.
+Siapkan fungsi `generateFiles()`-nya, isinya kurang lebih adalah generate banyak file sejumlah `totalFile`. Lalu di tiap-tiap file di-isi dengan *random string* dengan lebar sepanjang `contentLength`. Untuk nama file-nya sendiri, formatnya adalah `file-.txt`.
```go
func generateFiles() {
@@ -151,9 +151,9 @@ Bisa dilihat sebanyak 3000 dummy file di-generate pada folder temporary os, di s
## A.62.4. Program 2: Baca Semua Files, Cari MD5 Hash-nya, Lalu Gunakan Hash Untuk Rename File
-Sesuai judul sub bagian, kita akan buat satu file program lagi, yang isinya kurang lebih adalah melakukan pembacaan terhadap semua dummy file yang sudah di-generate untuk kemudian dicari *hash*-nya, lalu menggunakan value hash tersebut sebagai nama file baru masing-masing file.
+Sesuai judul sub bagian, kita akan buat satu file program lagi, yang isinya adalah melakukan operasi baca terhadap semua dummy file yang sudah di-generate, untuk kemudian dicari *hash*-nya lalu menggunakan nilai hash tersebut sebagai nama untuk file-file baru yang akan dibuat.
-Pada bagian ini kita belum masuk ke aspek konkurensi-nya ya. Sabar dulu. Saya akan coba sampaikan dengan penjabaran yang bisa diterima oleh banyak pembaca termasuk yang masih junior.
+Pada bagian ini kita belum masuk ke aspek konkurensi-nya ya. Sabar dulu. Saya akan coba sampaikan dengan penjabaran yang bisa diterima oleh banyak pembaca (termasuk yang masih awam banget).
Siapkan file `2-find-md5-sum-of-file-then-rename-it.go`, import beberapa *packages* dan siapkan definisi variabel `tempPath`.
@@ -233,7 +233,7 @@ func proceed() {
}
```
-Cukup panjang isi fungsi ini, tetapi isinya cukup *straightforward* kok.
+Cukup panjang isi fungsi ini, tetapi isinya cukup *straight forward* kok.
* Pertama kita siapkan `counterTotal` sebagai counter jumlah file yang ditemukan dalam `$TEMP/chapter-A.59-pipeline-temp`. Idealnya jumlahnya adalah sama dengan isi variabel `totalFile` pada program pertama, kecuali ada error.
* Kedua, kita siapkan `counterRenamed` sebagai counter jumlah file yang berhasil di-rename. Untuk ini juga idealnya sama dengan nilai pada `counterTotal`, kecuali ada error
@@ -250,7 +250,7 @@ Selesai dalam waktu **1,17 detik**, lumayan untuk eksekusi proses sekuensial.
Ok, aplikasi sudah siap. Selanjutnya kita akan refactor aplikasi tersebut ke bentuk konkuren menggunakan metode *pipeline*.
-## A.62.5. Program 3: Lakukan Proses Secara Concurrent Menggunakan Pipeline
+## A.62.5. Program 3: Lakukan Proses Secara Concurrent Menggunakan Teknik Pipeline
Pada bagian ini kita akan re-write ulang program 2, isinya masih sama persis kalau dilihat dari perspektif bisnis logic, tapi metode yang kita terapkan dari sisi engineering berbeda. Di sini kita akan terapkan *pipeline*. Bisnis logic akan dipecah menjadi 3 dan seluruhnya dieksekusi secara konkuren, yaitu:
@@ -258,9 +258,9 @@ Pada bagian ini kita akan re-write ulang program 2, isinya masih sama persis kal
* Proses perhitungan md5 hash sum
* Proses rename file
-Kenapa kita pecah, karena ketiga proses tersebut bisa dijalankan bersama secara konkuren, dalam artian misalnya ketika file1 sudah selesai dibaca, perhitungan md5 sum nya bisa dijalankan secara bersama dengan pembacaan file2. Begitu juga untuk proses rename-nya, misalnya, proses rename file24 bisa dijalnkan secara konkuren bersamaan dengan proses hitung md5 sum file22 dan bersamaan dengan proses baca file28.
+Kenapa kita pecah, karena ketiga proses tersebut bisa dijalankan bersama secara konkuren, dalam artian misalnya ketika `file1` sudah selesai dibaca, perhitungan md5sum-nya bisa dijalankan secara bersama dengan pembacaan `file2`. Begitu juga untuk proses rename-nya, misalnya, proses rename `file24` bisa dijalnkan secara konkuren bersamaan dengan proses hitung md5sum `file22` dan bersamaan dengan proses baca `file28`.
-#### • Basis Kode Program
+#### ◉ Basis Kode Program
Mungkin agar lebih terlihat perbandingannya nanti di akhir, kita siapkan file terpisah saja untuk program ini. Siapkan file baru bernama `3-find-md5-sum-of-file-then-rename-it-concurrently.go`.
@@ -291,7 +291,7 @@ type FileInfo struct {
Kurang lebih sama seperti sebelumnya, hanya saja ada beberapa packages lain yg di-import dan ada struct `FileInfo`. Struct ini digunakan sebagai metadata tiap file. Karena nantinya proses read file, md5sum, dan rename file akan dipecah menjadi 3 goroutine berbeda, maka perlu ada metadata untuk mempermudah tracking file, agar nanti ketika dapat md5 sum nya tidak salah simpan, dan ketika rename tidak salah file.
-#### • Pipeline 1: Baca File
+#### ◉ Pipeline 1: Baca File
Siapkan fungsi main, lalu panggil fungsi `readFiles()`.
@@ -349,17 +349,17 @@ func readFiles() <-chan FileInfo {
}
```
-Bisa dilihat isi fungsi `readFiles()`. Di fungsi tersebut ada sebuah channel bernama `chanOut` tipenya channel `FileInfo`, yang variabel channel ini langsung dijadikan nilai balik dari fungsi `readFiles()`.
+Bisa dilihat isi fungsi `readFiles()`. Di fungsi tersebut ada sebuah channel bernama `chanOut` tipenya channel `FileInfo`, variabel channel ini dijadikan nilai balik dari fungsi `readFiles()`.
Di dalam fungsi `readFiles()` juga ada proses lain yang berjalan secara *asynchronous* dan *concurrent* yaitu goroutine yang isinya pembacaan file. Dalam blok kode baca file, informasi `path` dan konten file dibungkus dalam objek baru dengan tipe `FileInfo` kemudian dikirim ke channel `chanOut`.
-Karena proses utama dalam fungsi `readFiles` berada dalam goroutine, maka di `main()`, ketika statement `chanFileContent := readFiles()` selesai dieksekusi, bukan berarti proses pembacaan file selesai, malah mungkin baru saja dimulai. Ini karena proses baca file dijalankan dalam goroutine di dalam fungsi `readFiles()` tersebut.
+Karena proses utama dalam fungsi `readFiles()` berada dalam goroutine, maka di `main()`, ketika statement `chanFileContent := readFiles()` selesai dieksekusi, bukan berarti proses pembacaan file selesai, malah mungkin baru saja dimulai. Ini karena proses baca file dijalankan dalam goroutine di dalam fungsi `readFiles()` tersebut.
Mengenai channel `chanOut` sendiri, akan di-close ketika dipastikan **semua file sudah dikirim datanya ke channel tersebut** (silakan lihat statement `close(chanOut)` di akhir goroutine).
Ok lanjut, karena di sini ada channel yang digunakan sebagai media pengiriman data (`FileInfo`), maka juga harus ada penerima data channel-nya dong. Yups.
-#### • Pipeline 2: MD5 Hash Konten File
+#### ◉ Pipeline 2: MD5 Hash Konten File
Tepat di bawah pipeline 1, tambahkan pemanggilan fungsi `getSum()` sebanyak 3x, bisa lebih banyak sih sebenarnya, bebas. Kemudian jadikan nilai balik pemanggilan fungsi tersebut sebagai variadic argument pemanggilan fungsi `mergeChanFileInfo()`.
@@ -390,7 +390,7 @@ Jadi TL;DR nya:
* Fungsi Fan-out digunakan untuk pembuatan worker, untuk distribusi job, yang proses distribusinya sendiri akan berhenti ketika channel inputan di-close.
* Fungsi Fan-in digunakan untuk *multiplexing* atau menggabung banyak worker ke satu channel saja, yang di mana channel baru ini juga otomatis di-close ketika channel input adalah closed.
-Lanjut buat fungsi `getSum()`-nya.
+Sekarang lanjut buat fungsi `getSum()`.
```go
func getSum(chanIn <-chan FileInfo) <-chan FileInfo {
@@ -447,7 +447,7 @@ Secara garis besar, pada fungsi ini terjadi beberapa proses:
* Channel `chanOut` ini dijadikan sebagai nilai balik fungsi.
* Di situ kita gunakan `sync.WaitGroup` untuk kontrol goroutine. Kita akan tunggu hingga semua channel input adalah closed, setelah itu barulah kita close channel `chanOut` ini.
-#### • Pipeline 3: Rename file
+#### ◉ Pipeline 3: Rename file
Tambahkan statement pipeline ketiga, yaitu pemanggilan fungsi Fan-out `rename()`, lalu panggil fungsi Fan-in `mergeChanFileInfo()` untuk multiplex channel kembalian fungsi `rename()`.
@@ -493,7 +493,7 @@ Bisa dilihat di atas kita rename file asli yang informasi path-nya ada di `FileI
Setelah semua file berhasil di-rename, maka channel `chanOut` di-close.
-#### • Pipeline 4 / Output
+#### ◉ Pipeline 4 / Output
Serangkaian proses yang sudah kita setup punya ketergantungan tinggi satu sama lain, dan eksekusinya harus berurutan meskipun *concurrently*. Ini secara langsung juga mempermudah kita dalam mengolah output hasil pipeline. Kita cukup fokus ke channel hasil Fan-in yang paling terakhir, yaitu channel `chanRename`.
@@ -528,9 +528,11 @@ Bisa dilihat bedanya, untuk rename 3000 file menggunakan cara sekuensial membutu
## A.62.6. Kesimpulan
-Pipeline concurrency pattern sangat bagus untuk diterapkan pada kasus yang proses-nya bisa di-klasifikasi menjadi sub-proses kecil-kecil yang secara I/O tidak saling tunggu (tapi secara flow harus berurutan).
+Pipeline concurrency pattern sangat bagus untuk diterapkan pada case yang proses-nya bisa diklasifikasi menjadi sub-proses kecil-kecil yang secara I/O tidak saling tunggu (tapi secara flow harus berurutan).
+
+Untuk banyak kasus, metode pipeline ini sangat tepat guna. Kita bisa dengan mudah mengontrol penggunaan resource seperti **CPU** dengan cara menentukan angka ideal jumlah worker untuk masing-masing pipeline, tapi untuk bagian ini butuh *test and try* juga, karena tidak selalu banyak worker itu menghasilkan proses yang lebih cepat, dan misalpun bisa, perlu dicek juga konsumsi resource-nya berlebihan atau tidak. Bisa jadi karena terlalu banyak worker malah lebih lambat karena ada constraint I/O.
-Untuk banyak kasus, metode pipeline ini sangat tepat guna. Kita bisa dengan mudah mengontrol penggunaan resource seperti **CPU** dengan cara menentukan angka ideal jumlah worker untuk masing-masing pipeline, tapi untuk bagian ini butuh *test and try*, karena tidak selalu banyak worker itu menghasilkan proses yang lebih cepat. Bisa jadi karena terlalu banyak worker malah lebih lambat. Jadi silakan lakukan testing saja, sesuaikan dengan spesifikasi CPU laptop/komputer/server yang digunakan.
+Intinya butuh banyak percobaan dan testing, sesuaikan dengan spesifikasi hardware laptop/komputer/server yang digunakan.
Ok sekian untuk chapter panjang ini.
From 81e54ea9d0dd670784eaabf36cee4e20c46f46fd Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:36 +0700
Subject: [PATCH 302/522] New translations a-data-type-conversion.md (English)
---
en/content-en/A-data-type-conversion.md | 30 +++++++++++++------------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/en/content-en/A-data-type-conversion.md b/en/content-en/A-data-type-conversion.md
index bfbd3b880..d02675939 100644
--- a/en/content-en/A-data-type-conversion.md
+++ b/en/content-en/A-data-type-conversion.md
@@ -1,12 +1,12 @@
# A.43. Konversi Antar Tipe Data
-Pada chapter sebelum-sebelumnyanya kita sudah mengaplikasikan beberapa cara konversi data, contohnya seperti konversi `string` ↔ `int` menggunakan `strconv`, dan `time.Time` ↔ `string`. Pada chapter ini kita akan belajar lebih banyak.
+Di beberapa chapter sebelum ini kita telah menerapkan beberapa cara konversi data, contohnya seperti konversi `string` ↔ `int` menggunakan `strconv`, dan `time.Time` ↔ `string`. Pada chapter ini kita akan mempelajarinya lebih detail.
## A.43.1. Konversi Menggunakan `strconv`
Package `strconv` berisi banyak fungsi yang sangat membantu kita untuk melakukan konversi. Berikut merupakan beberapa fungsi yang dalam package tersebut.
-#### • Fungsi `strconv.Atoi()`
+#### ◉ Fungsi `strconv.Atoi()`
Fungsi ini digunakan untuk konversi data dari tipe `string` ke `int`. `strconv.Atoi()` menghasilkan 2 buah nilai kembalian, yaitu hasil konversi dan `error` (jika konversi sukses, maka `error` berisi `nil`).
@@ -26,7 +26,7 @@ func main() {
}
```
-#### • Fungsi `strconv.Itoa()`
+#### ◉ Fungsi `strconv.Itoa()`
Merupakan kebalikan dari `strconv.Atoi`, berguna untuk konversi `int` ke `string`.
@@ -37,7 +37,7 @@ var str = strconv.Itoa(num)
fmt.Println(str) // "124"
```
-#### • Fungsi `strconv.ParseInt()`
+#### ◉ Fungsi `strconv.ParseInt()`
Digunakan untuk konversi `string` berbentuk numerik dengan basis tertentu ke tipe numerik non-desimal dengan lebar data bisa ditentukan.
@@ -63,7 +63,7 @@ if err == nil {
}
```
-#### • Fungsi `strconv.FormatInt()`
+#### ◉ Fungsi `strconv.FormatInt()`
Berguna untuk konversi data numerik `int64` ke `string` dengan basis numerik bisa ditentukan sendiri.
@@ -74,7 +74,7 @@ var str = strconv.FormatInt(num, 8)
fmt.Println(str) // 30
```
-#### • Fungsi `strconv.ParseFloat()`
+#### ◉ Fungsi `strconv.ParseFloat()`
Digunakan untuk konversi `string` ke numerik desimal dengan lebar data bisa ditentukan.
@@ -89,7 +89,7 @@ if err == nil {
Pada contoh di atas, string `"24.12"` dikonversi ke float dengan lebar tipe data `float32`. Hasil konversi `strconv.ParseFloat` adalah sesuai dengan standar [IEEE Standard for Floating-Point Arithmetic](https://en.wikipedia.org/wiki/IEEE_floating_point).
-#### • Fungsi `strconv.FormatFloat()`
+#### ◉ Fungsi `strconv.FormatFloat()`
Berguna untuk konversi data bertipe `float64` ke `string` dengan format eksponen, lebar digit desimal, dan lebar tipe data bisa ditentukan.
@@ -113,7 +113,7 @@ Ada beberapa format eksponen yang bisa digunakan. Detailnya bisa dilihat di tabe
| `g` | Akan menggunakan format eksponen `e` untuk eksponen besar dan `f` untuk selainnya |
| `G` | Akan menggunakan format eksponen `E` untuk eksponen besar dan `f` untuk selainnya |
-#### • Fungsi `strconv.ParseBool()`
+#### ◉ Fungsi `strconv.ParseBool()`
Digunakan untuk konversi `string` ke `bool`.
@@ -126,7 +126,7 @@ if err == nil {
}
```
-#### • Fungsi `strconv.FormatBool()`
+#### ◉ Fungsi `strconv.FormatBool()`
Digunakan untuk konversi `bool` ke `string`.
@@ -139,12 +139,14 @@ fmt.Println(str) // true
## A.43.2. Konversi Data Menggunakan Teknik Casting
-Keyword tipe data bisa digunakan untuk casting, atau konversi antar tipe data. Cara penggunaannya adalah dengan menuliskan tipe data tujuan casting sebagai fungsi, lalu menyisipkan data yang akan dikonversi sebagai parameter fungsi tersebut.
+Cara penerapannya adalah dengan menggunakan keyword tipe data sebagai nama fungsi, kemudiaan argument pemanggilannya diisi dengan data yang ingin dikonversi tipenya.
```go
+// konversi nilai 24 bertipe int ke float64
var a float64 = float64(24)
fmt.Println(a) // 24
+// konversi nilai 24.00 bertipe float32 ke int32
var b int32 = int32(24.00)
fmt.Println(b) // 24
```
@@ -175,7 +177,7 @@ fmt.Printf("%s \n", s)
// halo
```
-Pada contoh di-atas, beberapa kode byte dituliskan dalam bentuk slice, ditampung variabel `byte1`. Lalu, nilai variabel tersebut di-cast ke `string`, untuk kemudian ditampilkan.
+Di contoh ke-2 di-atas, beberapa kode byte dituliskan dalam bentuk slice, ditampung variabel `byte1`. Lalu, nilai variabel tersebut di-cast ke `string`, untuk kemudian ditampilkan.
Selain itu, tiap karakter string juga bisa di-casting ke bentuk `int`, hasilnya adalah sama yaitu data byte dalam bentuk numerik basis desimal, dengan ketentuan literal string yang digunakan adalah tanda petik satu (').
@@ -189,9 +191,9 @@ var d string = string(104)
fmt.Println(d) // h
```
-## A.43.4. Type Assertions Pada Interface Kosong (`interface{}`)
+## A.43.4. Type Assertions Pada Tipe `any` atau Interface Kosong (`interface{}`)
-**Type assertions** merupakan teknik untuk mengambil tipe data konkret dari data yang terbungkus dalam `interface{}`. Jadi bisa disimpulkan bahwa teknik type assertions hanya bisa dilakukan pada data bertipe `interface{}`. Lebih jelasnya silakan cek contoh berikut.
+**Type assertions** merupakan teknik untuk mengambil tipe data konkret dari data yang terbungkus dalam `interface{}` atau `any`. Lebih jelasnya silakan cek contoh berikut.
Variabel `data` disiapkan bertipe `map[string]interface{}`, map tersebut berisikan beberapa item dengan tipe data value-nya berbeda satu sama lain, sementara tipe data untuk key-nya sama yaitu `string`.
@@ -215,7 +217,7 @@ Statement `data["nama"].(string)` maksudnya adalah, nilai `data["nama"]` yang be
Pada kode di atas, tidak akan terjadi panic error, karena semua operasi type assertion adalah dilakukan menggunakan tipe data yang sudah sesuai dengan tipe data nilai aslinya. Seperti `data["nama"]` yang merupakan `string` pasti bisa di-asertasi ke tipe `string`.
-Coba lakukan asertasi ke tipe yang tidak sesuai dengan tipe nilai aslinya, seperti `data["nama"].(int)`, pasti akan men-trigger panic error.
+Coba lakukan asertasi ke tipe yang tidak sesuai dengan tipe nilai aslinya, seperti `data["nama"].(int)`, hasilnya adalah panic error.
Nah, dari penjelasan di atas, terlihat bahwa kita harus tau terlebih dahulu apa tipe data asli dari data yang tersimpan dalam interface. Jika misal tidak tau, maka bisa gunakan teknik di bawah ini untuk pengecekan sukses tidaknya proses asertasi.
From 5f8f18c466c14454bee90e989e9c50bf3a7793e0 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:37 +0700
Subject: [PATCH 303/522] New translations a-defer-exit.md (English)
---
en/content-en/A-defer-exit.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/en/content-en/A-defer-exit.md b/en/content-en/A-defer-exit.md
index a039df9d5..e3cc1ad34 100644
--- a/en/content-en/A-defer-exit.md
+++ b/en/content-en/A-defer-exit.md
@@ -4,7 +4,7 @@
## A.36.1. Penerapan keyword `defer`
-Seperti yang sudah dijelaskan secara singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode **dalam skope blok fungsi**. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan.
+Seperti yang sudah dijelaskan singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode **dalam skope blok fungsi**. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan.
Defer bisa ditempatkan di mana saja, awal maupun akhir blok. Tetapi tidak mempengaruhi kapan waktu dieksekusinya, akan selalu dieksekusi di akhir.
@@ -19,7 +19,7 @@ func main() {
}
```
-Output:
+Output program:

@@ -45,7 +45,7 @@ func orderSomeFood(menu string) {
}
```
-Output:
+Output program:

@@ -68,7 +68,7 @@ func main() {
}
```
-Output:
+Output program:
```
halo 1
@@ -95,7 +95,7 @@ func main() {
}
```
-Output:
+Output program:
```
halo 1
From 858df59ad3b3af930f4f45177d4a91129e7691f7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:38 +0700
Subject: [PATCH 304/522] New translations a-encoding-base64.md (English)
---
en/content-en/A-encoding-base64.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/en/content-en/A-encoding-base64.md b/en/content-en/A-encoding-base64.md
index 08a092cd0..5d4bbfd1d 100644
--- a/en/content-en/A-encoding-base64.md
+++ b/en/content-en/A-encoding-base64.md
@@ -1,8 +1,8 @@
# A.46. Encode - Decode Base64
-Go memiliki package `encoding/base64`, berisikan fungsi-fungsi untuk kebutuhan **encode** dan **decode** data ke base64 dan sebaliknya. Data yang akan di-encode harus bertipe `[]byte`, perlu dilakukan casting untuk data-data yang belum sesuai tipenya.
+Go menyediakan package `encoding/base64`, berisikan fungsi-fungsi untuk kebutuhan **encode** dan **decode** data ke bentuk base64 dan sebaliknya. Data yang akan di-encode harus bertipe `[]byte`, maka perlu dilakukan casting untuk data-data yang tipenya belum `[]byte`.
-Ada beberapa cara yang bisa digunakan untuk encode dan decode data, dan pada chapter ini kita akan mempelajarinya.
+Proses encoding dan decoding bisa dilakukan via beberapa cara yang pada chapter ini kita akan pelajari.
## A.46.1. Penerapan Fungsi `EncodeToString()` & `DecodeString()`
@@ -59,7 +59,7 @@ Fungsi `base64.StdEncoding.EncodedLen(len(data))` menghasilkan informasi lebar v
Fungsi `base64.StdEncoding.DecodedLen()` memiliki kegunaan sama dengan `EncodedLen()`, hanya saja digunakan untuk keperluan decoding.
-Dibanding 2 fungsi sebelumnya, fungsi `Encode()` dan `Decode()` memiliki beberapa perbedaan. Selain lebar data penampung encode/decode harus dicari terlebih dahulu, terdapat perbedaan lainnya, yaitu pada fungsi ini hasil encode/decode tidak didapat dari nilai kembalian, melainkan dari parameter. Variabel yang digunakan untuk menampung hasil, disisipkan pada parameter fungsi tersebut.
+Dibanding 2 fungsi sebelumnya, fungsi `Encode()` dan `Decode()` ini memiliki beberapa perbedaan. Selain lebar data penampung encode/decode harus dicari terlebih dahulu, terdapat perbedaan lainnya, yaitu pada fungsi ini hasil encode/decode tidak didapat dari nilai kembalian, melainkan dari parameter. Variabel yang digunakan untuk menampung hasil, disisipkan pada parameter fungsi tersebut.
Pada pemanggilan fungsi encode/decode, variabel `encoded` dan `decoded` tidak disisipkan nilai pointer-nya, cukup di-pass dengan cara biasa, tipe datanya sudah dalam bentuk `[]byte`.
From aac5a662da7d762f8df292fb78adb8c8657544e5 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:39 +0700
Subject: [PATCH 305/522] New translations a-error-panic-recover.md (English)
---
en/content-en/A-error-panic-recover.md | 30 ++++++++++++++++----------
1 file changed, 19 insertions(+), 11 deletions(-)
diff --git a/en/content-en/A-error-panic-recover.md b/en/content-en/A-error-panic-recover.md
index 147ff9b7b..2f1b76cb2 100644
--- a/en/content-en/A-error-panic-recover.md
+++ b/en/content-en/A-error-panic-recover.md
@@ -1,14 +1,18 @@
# A.37. Error, Panic, dan Recover
-Error merupakan topik yang sangat penting dalam pemrograman Go. Di bagian ini kita akan belajar mengenai pemanfaatan error dan cara membuat custom error sendiri. Selain itu, kita juga akan belajar tentang penggunaan **panic** untuk memunculkan panic error, dan **recover** untuk mengatasinya.
+Error merupakan topik yang sangat penting dalam pemrograman Go, salah satu alasannya karena Go tidak mengadopsi konsep exception.
+
+Pada chapter ini kita akan belajar tentang pemanfaatan error dan cara membuat custom error. Selain itu, kita juga akan belajar tentang penggunaan **panic** untuk memunculkan panic error, dan **recover** untuk mengatasinya.
## A.37.1. Pemanfaatan Error
-`error` merupakan sebuah tipe. Error memiliki 1 buah property berupa method `Error()`, method ini mengembalikan detail pesan error dalam string. Error termasuk tipe yang isinya bisa `nil`.
+Di go, `error` merupakan sebuah tipe data. Error memiliki beberapa property yang salah satunya adalah method `Error()`. Method ini mengembalikan detail pesan error dalam string. Error termasuk tipe yang isinya bisa `nil`.
+
+Pada praktik pemrograman Go, pembaca akan menemui banyak sekali fungsi yang mengembalikan nilai balik lebih dari satu, yang biasanya salah satunya adalah bertipe `error`.
-Di Go, banyak sekali fungsi yang mengembalikan nilai balik lebih dari satu. Biasanya, salah satu kembalian adalah bertipe `error`. Contohnya seperti pada fungsi `strconv.Atoi()`. Fungsi tersebut digunakan untuk konversi data string menjadi numerik. Fungsi ini mengembalikan 2 nilai balik. Nilai balik pertama adalah hasil konversi, dan nilai balik kedua adalah `error`. Ketika konversi berjalan mulus, nilai balik kedua akan bernilai `nil`. Sedangkan ketika konversi gagal, penyebabnya bisa langsung diketahui dari error yang dikembalikan.
+Contohnya seperti pada fungsi `strconv.Atoi()`. Fungsi tersebut digunakan untuk konversi data string menjadi numerik. Fungsi ini mengembalikan 2 nilai balik. Nilai balik pertama adalah hasil konversi, dan nilai balik kedua adalah `error`. Ketika konversi berjalan mulus, nilai balik kedua akan bernilai `nil`. Sedangkan ketika konversi gagal, penyebabnya bisa langsung diketahui dari error yang dikembalikan.
-Di bawah ini merupakan contoh program sederhana untuk deteksi inputan dari user, apakah numerik atau bukan. Dari sini kita akan belajar mengenai pemanfaatan error.
+Di bawah ini merupakan contoh program sederhana untuk deteksi inputan dari user, apakah numerik atau bukan. Pada kode tersebut kita akan belajar mengenai pemanfaatan error.
```go
package main
@@ -93,17 +97,21 @@ Fungsi `validate()` mengembalikan 2 data. Data pertama adalah nilai `bool` yang
Fungsi `strings.TrimSpace()` digunakan untuk menghilangkan karakter spasi sebelum dan sesudah string. Ini dibutuhkan karena user bisa saja menginputkan spasi lalu enter.
-Ketika inputan tidak valid, maka error baru dibuat dengan memanfaatkan fungsi `errors.New()`. Selain itu objek error juga bisa dibuat lewat fungsi `fmt.Errorf()`.
+Ketika inputan tidak valid, maka error baru dibuat dengan memanfaatkan fungsi `errors.New()`.

+Selain menggunakan `errors.New()`, objek error bisa dibuat via fungsi `fmt.Errorf()`. Pengaplikasiannya mirip, perbedaannya fungsi `fmt.Errorf()` mendukung format string.
+
## A.37.3. Penggunaan `panic`
-Panic digunakan untuk menampilkan *stack trace* error sekaligus menghentikan flow goroutine (karena `main()` juga merupakan goroutine, maka behaviour yang sama juga berlaku). Setelah ada panic, proses akan terhenti, apapun setelah tidak di-eksekusi kecuali proses yang sudah di-defer sebelumnya (akan muncul sebelum panic error).
+Panic digunakan untuk menampilkan *stack trace* error sekaligus menghentikan flow goroutine. Setelah ada panic, proses akan terhenti, apapun setelah tidak di-eksekusi kecuali proses yang sudah di-defer sebelumnya (akan muncul sebelum panic error).
+
+> Perlu diingat bahwa `main()` juga merupakan goroutine, maka behaviour yang sama adalah berlaku.
-Panic menampilkan pesan error di console, sama seperti `fmt.Println()`. Informasi error yang ditampilkan adalah stack trace error, jadi sangat mendetail dan heboh.
+Panic menampilkan pesan error di console, sama seperti `fmt.Println()`. Informasi error yang ditampilkan adalah stack trace error, isinya sangat detail dan heboh.
-Kembali ke koding, pada program yang telah kita buat tadi, ubah `fmt.Println()` yang berada di dalam blok kondisi `else` pada fungsi main menjadi `panic()`, lalu tambahkan `fmt.Println()` setelahnya.
+Kembali ke praktek, pada program yang telah kita buat tadi, ubah `fmt.Println()` yang berada di dalam blok kondisi `else` pada fungsi main menjadi `panic()`, lalu tambahkan `fmt.Println()` setelahnya.
```go
func main() {
@@ -126,7 +134,7 @@ Jalankan program lalu langsung tekan enter, maka panic error muncul dan baris ko
## A.37.4. Penggunaan `recover`
-Recover berguna untuk meng-handle panic error. Pada saat panic error muncul, recover men-take-over goroutine yang sedang panic (pesan panic tidak akan muncul).
+Recover berguna untuk meng-handle panic error. Pada saat panic error muncul, recover men-take-over goroutine yang sedang panic dan efek sampingnya pesan panic tidak muncul dan eksekusi program adalah tidak error.
Ok, mari kita modifikasi sedikit fungsi di-atas untuk mempraktekkan bagaimana cara penggunaan recover. Tambahkan fungsi `catch()`, dalam fungsi ini terdapat statement `recover()` yang dia akan mengembalikan pesan panic error yang seharusnya muncul.
@@ -157,7 +165,7 @@ func main() {
}
```
-Output:
+Output program:

@@ -207,7 +215,7 @@ func main() {
}
```
-Pada kode di atas, bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk recover panic dan juga ada kode untuk men-trigger panic error secara paksa. Ketika panic error terjadi, maka idealnya perulangan terhenti, tetapi pada contoh di atas tidak, dikarenakan operasi dalam perulangan sudah di bungkus dalam IIFE dan seperti yang kita tau sifat panic error adalah menghentikan proses secara paksa dalam scope blok fungsi.
+Bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk recover panic dan juga ada kode untuk men-trigger panic error secara paksa. Ketika panic error terjadi, maka idealnya perulangan terhenti, tetapi pada contoh di atas tidak, dikarenakan operasi dalam perulangan sudah di bungkus dalam IIFE dan seperti yang kita tau sifat panic error adalah menghentikan proses secara paksa dalam scope blok fungsi.
---
From 1b28bacd162b09c9763f1e89b25a07a50260e5e3 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:40 +0700
Subject: [PATCH 306/522] New translations a-exec.md (English)
---
en/content-en/A-exec.md | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/en/content-en/A-exec.md b/en/content-en/A-exec.md
index d472f8544..7c05f887e 100644
--- a/en/content-en/A-exec.md
+++ b/en/content-en/A-exec.md
@@ -1,12 +1,12 @@
# A.49. Exec
-**Exec** digunakan untuk eksekusi perintah command line lewat kode program. Command yang bisa dieksekusi adalah semua command yang bisa dieksekusi di terminal (atau CMD untuk pengguna Windows).
+**Exec** digunakan untuk eksekusi perintah command line lewat kode program. Command yang bisa dieksekusi adalah semua command yang bisa dieksekusi di command line sesuai sistem operasinya (Linux-distros, Windows, MacOS, dan lainnya).
## A.49.1. Penggunaan Exec
-Go menyediakan package `exec` berisikan banyak fungsi untuk keperluan eksekusi perintah CLI.
+Go menyediakan package `exec` isinya banyak sekali API atau fungsi untuk keperluan eksekusi perintah command line.
-Cara untuk eksekusi command cukup mudah, yaitu dengan menuliskan command dalam bentuk string, diikuti arguments-nya (jika ada) sebagai parameter variadic pada fungsi `exec.Command()`.
+Cara eksekusi command adalah menggunakan fungsi `exec.Command()` dengan argument pemanggilan fungsi diisi command CLI yang diinginkan. Contoh:
```go
package main
@@ -26,15 +26,17 @@ func main() {
}
```
-Fungsi `exec.Command()` digunakan untuk menjalankan command. Fungsi tersebut bisa langsung di-chain dengan method `Output()`, jika ingin mendapatkan outputnya. Output yang dihasilkan berbentuk `[]byte`, gunakan cast ke string untuk mengambil bentuk string-nya.
+Fungsi `exec.Command()` menjalankan command yang dituliskan pada argument pemanggilan fungsi.
+
+Untuk mendapatkan outputnya, chain saja langsung dengan method `Output()`. Output yang dihasilkan berbentuk `[]byte`, maka pastikan cast ke string terlebih dahulu untuk membaca isi outputnya.

## A.49.2. Rekomendasi Penggunaan Exec
-Kadang kala, pada saat eksekusi command yang sudah jelas-jelas ada (seperti `ls`, `dir`, atau lainnya) kita menemui error yang mengatakan command not found. Hal itu terjadi karena executable dari command-command tersebut tidak ada. Seperti di windows tidak ada `dir.exe` dan lainnya. Di OS non-windows-pun juga demikian.
+Ada kalanya saat eksekusi command yang sudah jelas-jelas ada (seperti `ls`, `dir`, atau lainnya), error muncul menginformasikan bahwa command tidak ditemukan (command not found). Hal ini biasanya terjadi karena executable dari command-command tersebut tidak ada. Seperti di windows tidak ada `cmd` atau `cmd.exe`, di Linux tidak ditentukan apakah memakai `bash` atau `shell`, dan lainnya
-Untuk mengatasi masalah ini, tambahkan `bash -c` pada linux/nix command atau `cmd /C` untuk windows.
+Untuk mengatasi masalah ini, tambahkan `bash -c` pada sistem operasi berbasi Linux, MacOS, Unix, atau `cmd /C` untuk OS Windows.
```go
if runtime.GOOS == "windows" {
@@ -44,11 +46,11 @@ if runtime.GOOS == "windows" {
}
```
-Statement `runtime.GOOS` mengembalikan informasi sistem operasi dalam string.
+Statement `runtime.GOOS` penggunaannya mengembalikan informasi sistem operasi dalam bentuk string. Manfaatkan seleksi kondisi untuk memastikan command yang ingin dieksekusi sudah sesuai dengan OS atau belum.
## A.49.3. Method Exec Lainnya
-Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi dengan OS/CLI yang bisa dipergunakan. Detailnya silakan langsung merujuk ke dokumentasi [https://golang.org/pkg/os/exec/](https://golang.org/pkg/os/exec/)
+Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi dengan OS/CLI yang bisa dipergunakan. Lebih detailnya silakan langsung melihat dokumentasi package tersebut di [https://golang.org/pkg/os/exec/](https://golang.org/pkg/os/exec/)
---
From b94b3a315bb88587bd3725f42d4620c7b09c83ec Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:40 +0700
Subject: [PATCH 307/522] New translations a-file.md (English)
---
en/content-en/A-file.md | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/en/content-en/A-file.md b/en/content-en/A-file.md
index 630b1d958..3e9ede81f 100644
--- a/en/content-en/A-file.md
+++ b/en/content-en/A-file.md
@@ -1,12 +1,12 @@
# A.50. File
-Ada beberapa cara yang bisa digunakan untuk operasi file di Go. Pada chapter ini kita akan mempelajari teknik yang paling dasar, yaitu dengan memanfaatkan `os.File`.
+Pada chapter ini kita akan belajar beberapa teknik operasi file yang paling dasar.
## A.50.1. Membuat File Baru
-Pembuatan file di Go sangatlah mudah, cukup dengan memanggil fungsi `os.Create()` lalu memasukkan path file yang ingin dibuat sebagai parameter. Jika ternyata file yang akan dibuat sudah ada, maka akan ditimpa. Bisa memanfaatkan `os.IsNotExist()` untuk mendeteksi apakah file sudah dibuat atau belum.
+Pembuatan file di Go sangat mudah, dilakukan dengan memanfaatkan fungsi `os.Create()` disertai path file sebagai argument pemanggilan fungsi. Jika ternyata file yang akan dibuat sudah ada duluan, maka operasi `os.Create()` akan menimpa file yang sudah ada dengan file baru. Untuk menghindari penimpaan file, gunakan fungsi `os.IsNotExist()` untuk mendeteksi apakah file yang ingin dibuat sudah ada atau belum.
-Berikut merupakan contoh pembuatan file.
+Contoh program operasi pembuatan file:
```go
package main
@@ -43,17 +43,17 @@ func main() {
}
```
-Fungsi `os.Stat()` mengembalikan 2 data, yaitu informasi tetang path yang dicari, dan error (jika ada). Masukkan error kembalian fungsi tersebut sebagai parameter fungsi `os.IsNotExist()`, untuk mendeteksi apakah file yang akan dibuat sudah ada. Jika belum ada, maka fungsi tersebut akan mengembalikan nilai `true`.
+Fungsi `os.Stat()` mengembalikan 2 data, yaitu informasi tetang path yang dicari, dan error (jika ada). Masukkan error kembalian fungsi tersebut sebagai argument pemanggilan fungsi `os.IsNotExist()`, untuk mengetahui apakah file yang akan dibuat sudah ada. Jika rupanya file belum ada ada, maka fungsi tersebut akan mengembalikan nilai `true`.
-Fungsi `os.Create()` digunakan untuk membuat file pada path tertentu. Fungsi ini mengembalikan objek `*os.File` dari file yang bersangkutan. File yang baru terbuat statusnya adalah otomatis **open**, maka dari itu perlu untuk di-**close** menggunakan method `file.Close()` setelah file tidak digunakan lagi.
+Fungsi `os.Create()` ini mengembalikan objek bertipe `*os.File`. File yang baru dibuat, statusnya adalah otomatis **open**. Setelah operasi file selesai, file harus di-**close** menggunakan method `file.Close()`.
-Membiarkan file terbuka ketika sudah tak lagi digunakan bukan hal yang baik, karena efeknya ke memory dan akses ke file itu sendiri, file akan di-lock sehingga tidak bisa digunakan oleh proses lain selama status file masih open atau belum di-close.
+Membiarkan file terbuka ketika sudah tak lagi digunakan adalah tidak baik, karena ada efek ke memory dan akses ke file itu sendiri, file menjadi terkunci/locked, membuatnya tidak bisa diakses oleh proses lain selama status file statusnya masih **open** dan belum di-close.

## A.50.2. Mengedit Isi File
-Untuk mengedit file, yang perlu dilakukan pertama adalah membuka file dengan level akses **write**. Setelah mendapatkan objek file-nya, gunakan method `WriteString()` untuk pengisian data. Terakhir panggil method `Sync()` untuk menyimpan perubahan.
+Untuk mengedit file, yang pertama perlu dilakukan adalah membuka file dengan level akses **write**. Setelah mendapatkan objek file-nya, gunakan method `WriteString()` untuk penulisan data. Di akhir, panggil method `Sync()` untuk menyimpan perubahan.
```go
func writeFile() {
@@ -80,13 +80,13 @@ func main() {
}
```
-Pada program di atas, file dibuka dengan level akses **read** dan **write** dengan kode permission **0664**. Setelah itu, beberapa string diisikan ke dalam file tersebut menggunakan `WriteString()`. Di akhir, semua perubahan terhadap file akan disimpan dengan dipanggilnya `Sync()`.
+Pada program di atas, file dibuka dengan level akses **read** dan **write** dengan kode permission **0664**. Setelah itu, beberapa string diisikan ke dalam file tersebut menggunakan `WriteString()`. Di akhir, semua perubahan terhadap file menjadi tersimpan dengan adanya pemanggilan method `Sync()`.

## A.50.3. Membaca Isi File
-File yang ingin dibaca harus dibuka terlebih dahulu menggunakan fungsi `os.OpenFile()` dengan level akses minimal adalah **read**. Setelah itu, gunakan method `Read()` dengan parameter adalah variabel, yang di mana hasil proses baca akan disimpan ke variabel tersebut.
+File yang ingin dibaca harus dibuka terlebih dahulu menggunakan fungsi `os.OpenFile()` dengan level akses minimal adalah **read**. Dari object file kembalian fungsi tersebut, gunakan method `Read()` dengan disertai argument berupa variabel yang akan menampung data hasil operasi baca.
```go
// tambahkan di bagian import package io
@@ -120,21 +120,21 @@ func main() {
}
```
-Pada kode di atas `os.OpenFile()` digunakan untuk membuka file. Fungsi tersebut memiliki beberapa parameter.
+Fungsi `os.OpenFile()` dalam pemanggilannya memerlukan beberapa argument parameter untuk di-isi:
1. Parameter pertama adalah path file yang akan dibuka.
2. Parameter kedua adalah level akses. `os.O_RDONLY` maksudnya adalah **read only**.
3. Parameter ketiga adalah permission file-nya.
-Variabel `text` disiapkan bertipe slice `[]byte` dengan alokasi elemen 1024. Variabel tersebut bertugas menampung data hasil statement `file.Read()`. Proses pembacaan file akan dilakukan terus menerus, berurutan dari baris pertama hingga akhir.
+Variabel `text` disiapkan bertipe slice `[]byte` dengan alokasi elemen `1024`. Variabel tersebut bertugas menampung data hasil statement `file.Read()`. Proses pembacaan file dilakukan terus menerus, berurutan dari baris pertama hingga akhir.
-Error yang muncul ketika eksekusi `file.Read()` akan di-filter, ketika error tersebut adalah selain `io.EOF` maka proses baca file akan berlanjut. Error `io.EOF` sendiri menandakan bahwa file yang sedang dibaca adalah baris terakhir isi atau **end of file**.
+Error yang muncul ketika eksekusi `file.Read()` akan di-filter, ketika error adalah selain `io.EOF` maka proses baca file akan berlanjut. Error `io.EOF` sendiri menandakan bahwa file yang sedang dibaca adalah baris terakhir isi atau **end of file**.

## A.50.4. Menghapus File
-Cara menghapus file sangatlah mudah, cukup panggil fungsi `os.Remove()`, masukan path file yang ingin dihapus sebagai parameter.
+Operasi menghapus file dilakukan via fungsi `os.Remove()`. Panggil fungsi tersebut, kemudian isi path dari file yang ingin dihapus sebagai argument fungsi.
```go
func deleteFile() {
From b955b47f64b1a0a2b4bf561f8289caad97fd2cb7 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:41 +0700
Subject: [PATCH 308/522] New translations a-fungsi-closure.md (English)
---
en/content-en/A-fungsi-closure.md | 42 +++++++++++++++++--------------
1 file changed, 23 insertions(+), 19 deletions(-)
diff --git a/en/content-en/A-fungsi-closure.md b/en/content-en/A-fungsi-closure.md
index 6130d674f..6660eaa6e 100644
--- a/en/content-en/A-fungsi-closure.md
+++ b/en/content-en/A-fungsi-closure.md
@@ -1,12 +1,12 @@
# A.21. Fungsi Closure
-Definisi **Closure** adalah sebuah fungsi yang bisa disimpan dalam variabel. Dengan menerapkan konsep tersebut, kita bisa membuat fungsi di dalam fungsi, atau bahkan membuat fungsi yang mengembalikan fungsi.
-
-Closure merupakan *anonymous function* atau fungsi tanpa nama. Biasa dimanfaatkan untuk membungkus suatu proses yang hanya dipakai sekali atau dipakai pada blok tertentu saja.
+Definisi **Closure** adalah suatu *anonymous function* (atau fungsi tanpa nama) yang disimpan dalam variabel. Dengan adanya closure, kita bisa mendesain beberapa hal diantaranya seperti: membuat fungsi di dalam fungsi, atau bahkan membuat fungsi yang mengembalikan fungsi. Closure biasa dimanfaatkan untuk membungkus suatu proses yang hanya dijalankan sekali saja atau hanya dipakai pada blok tertentu saja.
## A.21.1. Closure Disimpan Sebagai Variabel
-Sebuah fungsi tanpa nama bisa disimpan dalam variabel. Variabel yang menyimpan closure memiliki sifat seperti fungsi yang disimpannya. Di bawah ini adalah contoh program sederhana untuk mencari nilai terendah dan tertinggi dari suatu array. Logika pencarian dibungkus dalam closure yang ditampung oleh variabel `getMinMax`.
+Sebuah fungsi tanpa nama bisa disimpan dalam variabel. Variabel closure memiliki sifat seperti fungsi yang disimpannya.
+
+Di bawah ini adalah contoh program sederhana yang menerapkan closure untuk pencarian nilai terendah dan tertinggi dari data array. Logika pencarian dibungkus dalam closure yang ditampung oleh variabel `getMinMax`.
```go
package main
@@ -35,8 +35,7 @@ func main() {
}
```
-
-Bisa dilihat pada kode di atas bagaimana sebuah closure dibuat dan dipanggil. Sedikit berbeda memang dibanding pembuatan fungsi biasa. Fungsi ditulis tanpa nama, lalu ditampung dalam variabel.
+Bisa dilihat pada kode di atas bagaimana cara deklarasi closure dan cara pemanggilannya. Sedikit berbeda memang dibanding pembuatan fungsi biasa, pada closure fungsi ditulis tanpa memiliki nama lalu ditampung ke variabel.
```go
var getMinMax = func(n []int) (int, int) {
@@ -44,7 +43,7 @@ var getMinMax = func(n []int) (int, int) {
}
```
-Cara pemanggilannya, dengan menuliskan nama variabel tersebut sebagai fungsi, seperti pemanggilan fungsi biasa.
+Cara pemanggilan closure adalah dengan memperlakukan variabel closure seperti fungsi, dituliskan seperti pemanggilan fungsi.
```go
var min, max = getMinMax(numbers)
@@ -54,25 +53,25 @@ Output program:

----
+## A.21.2. Penjelasan tambahan
-Berikut adalah penjelasan tambahan mengenai kode di atas
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
-## A.21.1.1. Penggunaan Template String `%v`
+#### ◉ Penggunaan Template String `%v`
-Template `%v` digunakan untuk menampilkan segala jenis data. Bisa array, int, float, bool, dan lainnya.
+Template `%v` digunakan untuk menampilkan data tanpa melihat tipe datanya. Jadi bisa digunakan untuk menampilkan data array, int, float, bool, dan lainnya. Bisa dilihat di contoh statement, data bertipe array dan numerik ditampilkan menggunakan `%v`.
```go
fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max)
```
-Bisa dilihat pada statement di atas, data bertipe array dan numerik ditampilkan menggunakan `%v`. Template ini biasa dimanfaatkan untuk menampilkan sebuah data yang tipe nya bisa dinamis atau belum diketahui. Sangat tepat jika digunakan pada data bertipe `interface{}` yang nantinya akan di bahas pada chapter [A.27. Interface](/A-interface.html).
+Template `%v` ini biasa dimanfaatkan untuk menampilkan sebuah data yang tipe nya bisa dinamis atau belum diketahui. Biasa digunakan untuk keperluan debugging, misalnya untuk menampilkan data bertipe `any` atau `interface{}`.
----
+> Pembahasan mengenai tipe data `any` atau `interface{}` ada di chapter [A.27. Interface](/A-interface.html)
-## A.21.2. Immediately-Invoked Function Expression (IIFE)
+#### ◉ Immediately-Invoked Function Expression (IIFE)
-Closure jenis ini dieksekusi langsung pada saat deklarasinya. Biasa digunakan untuk membungkus proses yang hanya dilakukan sekali, bisa mengembalikan nilai, bisa juga tidak.
+Closure jenis IIFE ini eksekusinya adalah langsung saat deklarasi. Teknik ini biasa diterapkan untuk membungkus proses yang hanya dilakukan sekali. IIFE bisa memiliki nilai balik atau bisa juga tidak.
Di bawah ini merupakan contoh sederhana penerapan metode IIFE untuk filtering data array.
@@ -104,7 +103,7 @@ Output program:

-Ciri khas IIFE adalah adanya kurung parameter tepat setelah deklarasi closure berakhir. Jika ada parameter, bisa juga dituliskan dalam kurung parameternya.
+Ciri khas dari penulisan IIFE adalah adanya tanda kurung parameter yang ditulis di akhir deklarasi closure. Jika IIFE memiliki parameter, maka argument-nya juga ditulis. Contoh:
```go
var newNumbers = func(min int) []int {
@@ -112,12 +111,12 @@ var newNumbers = func(min int) []int {
}(3)
```
-Pada contoh di atas IIFE menghasilkan nilai balik yang kemudian ditampung `newNumber`. Perlu diperhatikan bahwa yang ditampung adalah **nilai kembaliannya** bukan body fungsi atau **closure**.
+Di contoh sederhana di atas, IIFE menghasilkan nilai balik yang ditampung variabel `newNumber`. Perlu diperhatikan bahwa yang ditampung adalah **nilai kembaliannya** bukan body fungsi atau **closure**-nya.
> Closure bisa juga dengan gaya manifest typing, caranya dengan menuliskan skema closure-nya sebagai tipe data. Contoh: var closure (func (string, int, []string) int) closure = func (a string, b int, c []string) int { // .. }
## A.21.3. Closure Sebagai Nilai Kembalian
-Salah satu keunikan closure lainnya adalah bisa dijadikan sebagai nilai balik fungsi, cukup aneh memang, tapi pada suatu kondisi teknik ini sangat membantu. Di bawah ini disediakan sebuah fungsi bernama `findMax()`, fungsi ini salah satu nilai kembaliannya berupa closure.
+Salah satu keunikan lain dari closure adalah: closure bisa dijadikan sebagai nilai balik fungsi. Cukup aneh, tapi pada kondisi tertentu teknik ini sangat berguna. Sebagai contoh, di bawah ini dideklarasikan sebuah fungsi bernama `findMax()` yang salah satu nilai kembaliannya adalah berupa closure.
```go
package main
@@ -146,7 +145,12 @@ return len(res), func() []int {
```
> Fungsi tanpa nama yang akan dikembalikan boleh disimpan pada variabel terlebih dahulu. Contohnya: var getNumbers = func() []int { return res } return len(res), getNumbers
-Sedikit tentang fungsi `findMax()`, fungsi ini digunakan untuk mencari banyaknya angka-angka yang nilainya di bawah atau sama dengan angka tertentu. Nilai kembalian pertama adalah jumlah angkanya. Nilai kembalian kedua berupa closure yang mengembalikan angka-angka yang dicari. Berikut merupakan contoh implementasi fungsi tersebut.
+Tentang fungsi `findMax()` sendiri, fungsi ini dibuat untuk mempermudah pencarian angka-angka yang nilainya di bawah atau sama dengan angka tertentu. Fungsi ini mengembalikan dua buah nilai balik:
+
+- Nilai balik pertama adalah jumlah angkanya.
+- Nilai balik kedua berupa closure yang mengembalikan angka-angka yang dicari.
+
+Berikut merupakan contoh implementasi fungsi tersebut:
```go
func main() {
From a43efb8efd90631766ad625ae90d9490c5be867d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:43 +0700
Subject: [PATCH 309/522] New translations a-fungsi-multiple-return.md
(English)
---
en/content-en/A-fungsi-multiple-return.md | 26 +++++++++++------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/en/content-en/A-fungsi-multiple-return.md b/en/content-en/A-fungsi-multiple-return.md
index 796983568..2ac5893f0 100644
--- a/en/content-en/A-fungsi-multiple-return.md
+++ b/en/content-en/A-fungsi-multiple-return.md
@@ -1,12 +1,10 @@
# A.19. Fungsi Multiple Return
-Umumnya fungsi hanya memiliki satu buah nilai balik saja. Jika ada kebutuhan di mana data yang dikembalikan harus banyak, biasanya digunakanlah tipe seperti `map`, slice, atau `struct` sebagai nilai balik.
+Di Go, suatu fungsi bisa saja mengembalikan nilai belik lebih dari 1 buah. Teknik ini bisa menjadi alternatif selain menggunakan tipe data kolektif seperti `map`, slice, atau `struct` sebagai nilai balik. Pada chapter ini kita akan belajar penerapannya.
-Go menyediakan kapabilitas bagi programmer untuk membuat fungsi memiliki banyak nilai balik. Pada chapter ini akan dibahas bagaimana penerapannya.
+## A.19.1. Penerapan Fungsi Multiple Return
-## A.19.1 Penerapan Fungsi Multiple Return
-
-Cara membuat fungsi yang memiliki banyak nilai balik tidaklah sulit. Tinggal tulis saja pada saat deklarasi fungsi semua tipe data nilai yang dikembalikan, dan pada keyword `return` tulis semua data yang ingin dikembalikan. Contoh bisa dilihat pada berikut.
+Cara membuat fungsi agar memiliki banyak nilai balik tidaklah sulit, caranya pada saat deklarasi fungsi, tulis semua tipe data nilai balik yang ingin dikembalikan. Kemudian dalam body fungsi, pada penggunaan keyword `return`, tulis semua data yang ingin dikembalikan. Contoh:
```go
package main
@@ -25,7 +23,7 @@ func calculate(d float64) (float64, float64) {
}
```
-Fungsi `calculate()` di atas menerima satu buah parameter (`diameter`) yang digunakan dalam proses perhitungan. Di dalam fungsi tersebut ada 2 hal yang dihitung, yaitu nilai **luas** dan **keliling**. Kedua nilai tersebut kemudian dijadikan sebagai return value fungsi.
+Fungsi `calculate()` di atas memiliki satu buah parameter yaitu `d` (diameter). Di dalam fungsi terdapat operasi perhitungan nilai **luas** dan **keliling** dari nilai `d`. Kedua hasilnya kemudian dijadikan sebagai return value.
Cara pendefinisian banyak nilai balik bisa dilihat pada kode di atas, langsung tulis tipe data semua nilai balik dipisah tanda koma, lalu ditambahkan kurung di antaranya.
@@ -39,7 +37,7 @@ Tak lupa di bagian penulisan keyword `return` harus dituliskan juga semua data y
return area, circumference
```
-Implementasi dari fungsi `calculate()` di atas, bisa dilihat pada kode berikut.
+Sekarang, coba panggil fungsi `calculate()` yang sudah dibuat untuk mencari nilai luas dan keliling dari suatu diameter.
```go
func main() {
@@ -55,13 +53,13 @@ Output program:

-Karena fungsi tersebut memiliki banyak nilai balik, maka pada pemanggilannya harus disiapkan juga banyak variabel untuk menampung nilai kembalian yang ada (sesuai jumlah nilai balik fungsi).
+Fungsi `calculate()` memiliki banyak nilai balik, maka dalam pemanggilannya harus disiapkan juga sejumlah variabel untuk menampung nilai balik fungsi (sesuai dengan jumlah nilai balik yang dideklarasikan).
```go
var area, circumference = calculate(diameter)
```
-## A.19.2 Fungsi Dengan Predefined Return Value
+## A.19.2. Fungsi Dengan Predefined Return Value
Keunikan lainnya yang jarang ditemui di bahasa lain adalah, di Go variabel yang digunakan sebagai nilai balik bisa didefinisikan di awal.
@@ -84,15 +82,15 @@ Fungsi dideklarasikan memiliki 2 buah tipe data, dan variabel yang nantinya dija
Karena variabel nilai balik sudah ditentukan di awal, untuk mengembalikan nilai cukup dengan memanggil `return` tanpa perlu diikuti variabel apapun. Nilai terakhir `area` dan `circumference` sebelum pemanggilan keyword `return` adalah hasil dari fungsi di atas.
----
+## A.19.3. Penjelasan tambahan
-Ada beberapa hal baru dari kode di atas yang perlu dibahas, seperti `math.Pow()` dan `math.Pi`. Berikut adalah penjelasannya.
+Ada beberapa hal baru dari kode di atas yang perlu dibahas, diantaranya `math.Pow()` dan `math.Pi`.
-#### • Penggunaan Fungsi `math.Pow()`
+#### ◉ Penggunaan Fungsi `math.Pow()`
-Fungsi `math.Pow()` digunakan untuk memangkat nilai. `math.Pow(2, 3)` berarti 2 pangkat 3, hasilnya 8. Fungsi ini berada dalam package `math`.
+Fungsi `math.Pow()` digunakan untuk operasi pangkat nilai. `math.Pow(2, 3)` berarti 2 pangkat 3, hasilnya 8. Fungsi ini berada dalam package `math`.
-#### • Penggunaan Konstanta `math.Pi`
+#### ◉ Penggunaan Konstanta `math.Pi`
`math.Pi` adalah konstanta bawaan `package math` yang merepresentasikan **Pi** atau **22/7**.
From 6f6873ac98beccecc0f036962888c62789214c00 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:43 +0700
Subject: [PATCH 310/522] New translations a-fungsi-sebagai-parameter.md
(English)
---
en/content-en/A-fungsi-sebagai-parameter.md | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/en/content-en/A-fungsi-sebagai-parameter.md b/en/content-en/A-fungsi-sebagai-parameter.md
index 8eacd0a12..0e27fde4d 100644
--- a/en/content-en/A-fungsi-sebagai-parameter.md
+++ b/en/content-en/A-fungsi-sebagai-parameter.md
@@ -1,8 +1,8 @@
# A.22. Fungsi Sebagai parameter
-Setelah pada chapter sebelumnya kita belajar mengenai fungsi yang mengembalikan nilai balik berupa fungsi, kali ini topiknya tidak kalah unik, yaitu fungsi yang digunakan sebagai parameter.
+Pada chapter sebelumnya kita telah belajar tentang fungsi yang mengembalikan nilai balik berupa fungsi. Kali ini topiknya tidak kalah unik, yaitu tentang fungsi yang memiliki parameter sebuah fungsi.
-Di Go, fungsi bisa dijadikan sebagai tipe data variabel. Dari situ sangat memungkinkan untuk menjadikannya sebagai parameter juga.
+Di Go, fungsi bisa dijadikan sebagai tipe data variabel, maka sangat memungkinkan untuk menjadikannya sebagai parameter.
## A.22.1. Penerapan Fungsi Sebagai Parameter
@@ -50,7 +50,7 @@ func main() {
}
```
-Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di atas. Berikut merupakan penjelasannya.
+Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di atas. Berikut adalah penjelasannya:
1. Data array (yang didapat dari parameter pertama) akan di-looping.
2. Di tiap perulangannya, closure `callback` dipanggil, dengan disisipkan data tiap elemen perulangan sebagai parameter.
@@ -62,15 +62,17 @@ Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di
Pada `dataContainsO`, parameter kedua fungsi `filter()` berisikan statement untuk deteksi apakah terdapat substring `"o"` di dalam nilai variabel `each` (yang merupakan data tiap elemen), jika iya, maka kondisi filter bernilai `true`, dan sebaliknya.
-pada contoh ke-2 (`dataLength5`), closure `callback` berisikan statement untuk deteksi jumlah karakter tiap elemen. Jika ada elemen yang jumlah karakternya adalah 5, berarti elemen tersebut lolos filter.
+Pada contoh ke-2 (`dataLength5`), closure `callback` berisikan statement untuk deteksi jumlah karakter tiap elemen. Jika ada elemen yang jumlah karakternya adalah 5, berarti elemen tersebut lolos filter.
-Memang butuh usaha ekstra untuk memahami pemanfaatan closure sebagai parameter fungsi. Tapi setelah paham, penerapan teknik ini pada kondisi yang tepat akan sangat membantu proses pembuatan aplikasi.
+Memang butuh usaha ekstra untuk memahami pemanfaatan closure sebagai parameter fungsi. Tapi setelah paham, penerapan teknik ini pada kondisi yang tepat akan sangat berguna.
## A.22.2. Alias Skema Closure
-Kita sudah mempelajari bahwa closure bisa dimanfaatkan sebagai tipe parameter, contohnya seperti pada fungsi `filter()`. Pada fungsi tersebut kebetulan skema tipe parameter closure-nya tidak terlalu panjang, hanya ada satu buah parameter dan satu buah nilai balik.
+Kita sudah mempelajari bahwa closure bisa dimanfaatkan sebagai tipe parameter, contohnya seperti pada fungsi `filter()`. Di fungsi tersebut kebetulan skema tipe parameter closure-nya tidak terlalu panjang, hanya ada satu buah parameter dan satu buah nilai balik.
-Pada fungsi yang skema-nya cukup panjang, akan lebih baik jika menggunakan alias, apalagi ketika ada parameter fungsi lain yang juga menggunakan skema yang sama. Membuat alias fungsi berarti menjadikan skema fungsi tersebut menjadi tipe data baru. Caranya dengan menggunakan keyword `type`. Contoh:
+Untuk fungsi yang skema-nya cukup panjang, akan lebih baik jika menggunakan alias dalam pendefinisiannya, apalagi ketika ada parameter fungsi lain yang juga menggunakan skema yang sama, maka kita tidak perlu menuliskan skema panjang fungsi tersebut berulang-ulang.
+
+Membuat alias fungsi berarti menjadikan skema fungsi tersebut menjadi tipe data baru. Caranya dengan menggunakan keyword `type`. Contoh:
```go
type FilterCallback func(string) bool
@@ -82,11 +84,11 @@ func filter(data []string, callback FilterCallback) []string {
Skema `func(string) bool` diubah menjadi tipe dengan nama `FilterCallback`. Tipe tersebut kemudian digunakan sebagai tipe data parameter `callback`.
----
+## A.22.3. Penjelasan tambahan
Di bawah ini merupakan penjelasan tambahan mengenai fungsi `strings.Contains()`.
-## A.22.2.1. Penggunaan Fungsi `string.Contains()`
+#### ◉ Penggunaan Fungsi `string.Contains()`
Inti dari fungsi ini adalah untuk deteksi apakah sebuah substring adalah bagian dari string, jika iya maka akan bernilai `true`, dan sebaliknya. Contoh penggunaannya:
From 8444710e17f35e9b612aad5a8d3a459191c6744c Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:44 +0700
Subject: [PATCH 311/522] New translations a-fungsi-variadic.md (English)
---
en/content-en/A-fungsi-variadic.md | 38 +++++++++++++++---------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/en/content-en/A-fungsi-variadic.md b/en/content-en/A-fungsi-variadic.md
index 75e54f00a..7263e9926 100644
--- a/en/content-en/A-fungsi-variadic.md
+++ b/en/content-en/A-fungsi-variadic.md
@@ -1,16 +1,16 @@
# A.20. Fungsi Variadic
-Go mengadopsi konsep **variadic function** atau pembuatan fungsi dengan parameter sejenis yang tak terbatas. Maksud **tak terbatas** di sini adalah jumlah parameter yang disisipkan ketika pemanggilan fungsi bisa berapa saja.
+Go mengadopsi konsep **variadic function** atau pembuatan fungsi dengan parameter bisa menampung nilai sejenis yang tidak terbatas jumlahnya.
-Parameter variadic memiliki sifat yang mirip dengan slice. Nilai dari parameter-parameter yang disisipkan bertipe data sama, dan ditampung oleh sebuah variabel saja. Cara pengaksesan tiap datanya juga sama, dengan menggunakan index.
+Parameter variadic memiliki sifat yang mirip dengan slice, yaitu nilai dari parameter-parameter yang disisipkan bertipe data sama, dan kesemuanya cukup ditampung oleh satu variabel saja. Cara pengaksesan tiap nilai juga mirip, yaitu dengan menggunakan index.
Pada chapter ini kita akan belajar mengenai cara penerapan fungsi variadic.
## A.20.1. Penerapan Fungsi Variadic
-Deklarasi parameter variadic sama dengan cara deklarasi variabel biasa, pembedanya adalah pada parameter jenis ini ditambahkan tanda titik tiga kali (`...`) tepat setelah penulisan variabel (sebelum tipe data). Nantinya semua nilai yang disisipkan sebagai parameter akan ditampung oleh variabel tersebut.
+Deklarasi parameter variadic sama dengan cara deklarasi variabel biasa, pembedanya adalah pada parameter jenis ini ditambahkan tanda titik tiga kali (`...`) tepat setelah penulisan variabel, sebelum tipe data. Nantinya semua nilai yang disisipkan sebagai parameter akan ditampung oleh variabel tersebut.
-Berikut merupakan contoh penerepannya.
+Contoh program:
```go
package main
@@ -56,19 +56,21 @@ Nilai tiap parameter bisa diakses seperti cara pengaksesan tiap elemen slice. Pa
for _, number := range numbers {
```
----
+## A.20.2. Penjelasan tambahan
-Berikut merupakan penjelasan tambahan dari kode yang telah kita tulis.
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
-#### • Penggunaan Fungsi `fmt.Sprintf()`
+#### ◉ Penggunaan Fungsi `fmt.Sprintf()`
-Fungsi `fmt.Sprintf()` pada dasarnya sama dengan `fmt.Printf()`, hanya saja fungsi ini tidak menampilkan nilai, melainkan mengembalikan nilainya dalam bentuk string. Pada kasus di atas, nilai kembalian `fmt.Sprintf()` ditampung oleh variabel `msg`.
+Fungsi `fmt.Sprintf()` pada dasarnya sama dengan `fmt.Printf()`, hanya saja fungsi ini tidak menampilkan nilai, melainkan mengembalikan nilainya dalam bentuk string. Pada case di atas, nilai kembalian `fmt.Sprintf()` ditampung oleh variabel `msg`.
Selain `fmt.Sprintf()`, ada juga `fmt.Sprint()` dan `fmt.Sprintln()`.
-#### • Penggunaan Fungsi `float64()`
+#### ◉ Penggunaan Fungsi `float64()`
+
+Sebelumnya sudah dibahas bahwa `float64` merupakan tipe data. Tipe data jika ditulis sebagai fungsi (penandanya ada tanda kurungnya) menandakan bahwa digunakan untuk keperluan **casting**. Casting sendiri adalah teknik untuk konversi tipe sebuah data ke tipe lain. Sebagian besar tipe data dasar yang telah dipelajari pada chapter [A.9. Variabel](/A-variabel.html) bisa di-casting.
-Sebelumnya sudah dibahas bahwa `float64` merupakan tipe data. Tipe data jika ditulis sebagai fungsi (penandanya ada tanda kurungnya) berguna untuk **casting**. Casting sendiri adalah teknik untuk konversi tipe sebuah data ke tipe lain. Sebagian besar tipe data dasar yang telah dipelajari pada chapter [A.9. Variabel](/A-variabel.html) bisa di-cast. Dan cara penerapannya juga sama, cukup panggil sebagai fungsi, lalu masukan data yang ingin dikonversi sebagai parameter.
+Cara penerapan casting: panggil saja tipe data yang diingunkan seperti pemanggilan fungsi, lalu masukan data yang ingin dikonversi sebagai argument pemanggilan fungsi tersebut.
Pada contoh di atas, variabel `total` yang tipenya adalah `int`, dikonversi menjadi `float64`, begitu juga `len(numbers)` yang menghasilkan `int` dikonversi ke `float64`.
@@ -76,11 +78,9 @@ Variabel `avg` perlu dijadikan `float64` karena penghitungan rata-rata lebih ser
Operasi bilangan (perkalian, pembagian, dan lainnya) di Go hanya bisa dilakukan jika tipe datanya sejenis. Maka dari itulah perlu adanya casting ke tipe `float64` pada tiap operand.
----
-
-## A.20.2. Pengisian Parameter Fungsi Variadic Menggunakan Data Slice
+## A.20.3. Pengisian Parameter Fungsi Variadic Menggunakan Data Slice
-Slice bisa digunakan sebagai parameter variadic. Caranya dengan menambahkan tanda titik tiga kali, tepat setelah nama variabel yang dijadikan parameter. Contohnya bisa dilihat pada kode berikut.
+Slice bisa digunakan sebagai argument pada fungsi variadic. Caranya penerapannya: tulis saja nama variabel tapi disertai dengan tanda titik tiga kali, dituliskan tepat setelah nama variabel yang dijadikan parameter. Contohnya bisa dilihat pada kode berikut:
```go
var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3}
@@ -90,9 +90,9 @@ var msg = fmt.Sprintf("Rata-rata : %.2f", avg)
fmt.Println(msg)
```
-Pada kode di atas, variabel `numbers` yang merupakan slice int, disisipkan ke fungsi `calculate()` sebagai parameter variadic (bisa dilihat tanda 3 titik setelah penulisan variabel). Teknik ini sangat berguna ketika sebuah data slice ingin difungsikan sebagai parameter variadic.
+Pada kode di atas, variabel `numbers` bertipe data slice int, disisipkan pada pemanggilan fungsi `calculate()` sebagai argument parameter fungsi variadic (bisa dilihat tanda 3 titik setelah penulisan variabel). Teknik ini sangat berguna pada case dimana sebuah data slice perlu untuk digunakan sebagai argument parameter variadic.
-Perhatikan juga kode berikut ini. Intinya adalah sama, hanya caranya yang berbeda.
+Agar lebih jelas, perhatikan 2 kode berikut. Intinya sama, hanya cara penulisannya yang berbeda.
```go
var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3}
@@ -105,7 +105,7 @@ var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
Pada deklarasi parameter fungsi variadic, tanda 3 titik (`...`) dituliskan sebelum tipe data parameter. Sedangkan pada pemanggilan fungsi dengan menyisipkan parameter array, tanda tersebut dituliskan di belakang variabelnya.
-## A.20.3. Fungsi Dengan Parameter Biasa & Variadic
+## A.20.4. Fungsi Dengan Parameter Biasa & Variadic
Parameter variadic bisa dikombinasikan dengan parameter biasa, dengan syarat parameter variadic-nya harus diposisikan di akhir. Contohnya bisa dilihat pada kode berikut.
@@ -123,7 +123,7 @@ func yourHobbies(name string, hobbies ...string) {
Nilai parameter pertama fungsi `yourHobbies()` akan ditampung oleh `name`, sedangkan nilai parameter kedua dan seterusnya akan ditampung oleh `hobbies` sebagai slice.
-Cara pemanggilannya masih sama seperi pada fungsi biasa.
+Cara pemanggilannya masih sama seperi pada fungsi biasa, contoh:
```go
func main() {
@@ -131,7 +131,7 @@ func main() {
}
```
-Jika parameter kedua dan seterusnya ingin diisi dengan data dari slice, maka gunakan tanda titik tiga kali.
+Jika parameter kedua dan seterusnya ingin diisi dengan data dari slice, maka gunakan tanda titik tiga kali seperti ini:
```go
func main() {
From da24f1fac3372ceef6f9ec5be16fb47456905fee Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:46 +0700
Subject: [PATCH 312/522] New translations a-fungsi.md (English)
---
en/content-en/A-fungsi.md | 51 ++++++++++++++++++++++-----------------
1 file changed, 29 insertions(+), 22 deletions(-)
diff --git a/en/content-en/A-fungsi.md b/en/content-en/A-fungsi.md
index 63e6c65c4..89a43b2dd 100644
--- a/en/content-en/A-fungsi.md
+++ b/en/content-en/A-fungsi.md
@@ -1,18 +1,20 @@
# A.18. Fungsi
-Fungsi merupakan aspek penting dalam pemrograman. Definisi fungsi sendiri adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (singkatan dari *don't repeat yourself*), tak perlu menuliskan banyak kode yang kegunaannya berkali-kali, cukup sekali saja lalu panggil sesuai kebutuhan.
+Dalam konteks pemrograman, fungsi adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (singkatan dari *don't repeat yourself*) yang artinya kita tidak perlu menuliskan banyak kode untuk kegunaan yang sama berulang kali. Cukup deklarasikan sekali saja blok kode sebagai suatu fungsi, lalu panggil sesuai kebutuhan.
-Pada chapter ini kita akan belajar tentang penggunaan fungsi di Go.
+Pada chapter ini kita akan belajar tentang penerapannya di Go.
## A.18.1. Penerapan Fungsi
-Sebenarnya tanpa sadar, kita sudah menerapkan fungsi pada pembahasan-pembahasan sebelum ini, yaitu pada fungsi `main`. Fungsi `main` merupakan fungsi yang paling utama pada program Go.
+Mungkin pembaca sadar, bahwa sebenarnya kita sudah mengimplementasikan fungsi pada banyak praktek sebelumnya, yaitu fungsi `main()`. Fungsi `main()` sendiri merupakan fungsi utama pada program Go, yang akan dieksekusi ketika program dijalankan.
-Cara membuat fungsi cukup mudah, yaitu dengan menuliskan keyword `func`, diikuti setelahnya nama fungsi, kurung yang berisikan parameter, dan kurung kurawal untuk membungkus blok kode.
+Selain fungsi `main()`, kita juga bisa membuat fungsi lainnya. Dan caranya cukup mudah, yaitu dengan menuliskan keyword `func` kemudian diikuti nama fungsi, lalu kurung `()` (yang bisa diisi parameter), dan diakhiri dengan kurung kurawal untuk membungkus blok kode.
-Parameter sendiri adalah variabel yang disisipkan pada saat pemanggilan fungsi.
+Parameter merupakan variabel yang menempel di fungsi yang nilainya ditentukan saat pemanggilan fungsi tersebut. Parameter sifatnya opsional, suatu fungsi bisa tidak memiliki parameter, atau bisa saja memeliki satu atau banyak parameter (tergantung kebutuhan).
-Silakan lihat dan praktekan kode tentang implementasi fungsi berikut.
+> Data yang digunakan sebagai value parameter saat pemanggilan fungsi biasa disebut dengan argument parameter (atau argument).
+
+Agar lebih jelas, silakan lihat dan praktekan kode contoh implementasi fungsi berikut ini:
```go
package main
@@ -31,17 +33,22 @@ func printMessage(message string, arr []string) {
}
```
-Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`.
+Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage()` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`.
+
+Fungsi tersebut dipanggil dalam `main()`, dalam pemanggilannya disisipkan dua buah argument parameter.
-Fungsi tersebut dipanggil dalam `main`, dengan disisipkan 2 buah data sebagai parameter, data pertama adalah string `"halo"` yang ditampung parameter `message`, dan parameter ke 2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`.
+1. Argument parameter pertama adalah string `"halo"` yang ditampung parameter `message`
+2. Argument parameter ke-2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`
-Di dalam `printMessage`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`).
+Di dalam `printMessage()`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`).

## A.18.2. Fungsi Dengan Return Value / Nilai Balik
-Sebuah fungsi bisa dirancang tidak mengembalikan nilai balik (*void*), atau bisa mengembalikan suatu nilai. Fungsi yang memiliki nilai kembalian, harus ditentukan tipe data nilai baliknya pada saat deklarasi.
+Selain parameter, fungsi bisa memiliki attribute **return value** atau nilai balik. Fungsi yang memiliki return value, saat deklarasinya harus ditentukan terlebih dahulu tipe data dari nilai baliknya.
+
+> Fungsi yang tidak mengembalikan nilai apapun (contohnya seperti fungsi `main()` dan `printMessage()`) biasa disebut dengan **void function**
Program berikut merupakan contoh penerapan fungsi yang memiliki return value.
@@ -61,8 +68,10 @@ func main() {
randomValue = randomWithRange(2, 10)
fmt.Println("random number:", randomValue)
+
randomValue = randomWithRange(2, 10)
fmt.Println("random number:", randomValue)
+
randomValue = randomWithRange(2, 10)
fmt.Println("random number:", randomValue)
}
@@ -73,41 +82,39 @@ func randomWithRange(min, max int) int {
}
```
-Fungsi `randomWithRange` bertugas untuk *generate* angka acak sesuai dengan range yang ditentukan, yang kemudian angka tersebut dijadikan nilai kembalian fungsi.
+Fungsi `randomWithRange()` didesain untuk *generate* angka acak sesuai dengan range yang ditentukan lewat parameter, yang kemudian angka tersebut dijadikan nilai balik fungsi.

-Cara menentukan tipe data nilai balik fungsi adalah dengan menuliskan tipe data yang diinginkan setelah kurung parameter. Bisa dilihat pada kode di atas, bahwa `int` merupakan tipe data nilai balik fungsi `randomWithRange`.
+Cara menentukan tipe data nilai balik fungsi adalah dengan menuliskan tipe data yang diinginkan setelah kurung parameter. Bisa dilihat pada kode di atas, bahwa `int` merupakan tipe data nilai balik fungsi `randomWithRange()`.
```go
func randomWithRange(min, max int) int
```
-Sedangkan cara untuk mengembalikan nilai itu sendiri adalah dengan menggunakan keyword `return` diikuti data yang ingin dikembalikan. Pada contoh di atas, `return value` artinya nilai variabel `value` dijadikan nilai kembalian fungsi.
+Sedangkan cara untuk mengembalikan nilai itu sendiri adalah dengan menggunakan keyword `return` diikuti data yang dikembalikan. Pada contoh di atas, `return value` artinya nilai variabel `value` dijadikan nilai kembalian fungsi.
Eksekusi keyword `return` akan menjadikan proses dalam blok fungsi berhenti pada saat itu juga. Semua statement setelah keyword tersebut tidak akan dieksekusi.
----
-
Dari kode di atas mungkin ada beberapa hal yang belum pernah kita lakukan pada pembahasan-pembahasan sebelumnya, kita akan bahas satu-persatu.
## A.18.3. Penggunaan Fungsi `rand.New()`
-Fungsi ini digunakan untuk membuat object randomizer, yang dari object tersebut nilai random/acak bisa di-generate. Dalam penerapannya, fungsi `rand.New()` membutuhkan argument yaitu random source seed, yang bisa kita buat lewat statement `rand.NewSource(time.Now().Unix())`.
+Fungsi `rand.New()` digunakan untuk membuat object randomizer, yang dari object tersebut kita bisa mendapatkan nilai random/acak hasil generator. Dalam penerapannya, fungsi `rand.New()` membutuhkan argument yaitu random source seed, yang bisa kita buat lewat statement `rand.NewSource(time.Now().Unix())`.
```go
var randomizer = rand.New(rand.NewSource(time.Now().Unix()))
```
-> Dalam penggunaan fungsi `rand.NewSource`, argument bisa diisi dengan nilai apapun, salah satunya adalah `time.Now().Unix()`.
+> Dalam penggunaan fungsi `rand.NewSource()`, argument bisa diisi dengan nilai apapun, salah satunya adalah `time.Now().Unix()`.
>
-> Lebih detailnya mengenai random dibahas pada chapter [A.39. Random](A-random.html).
+> Lebih detailnya mengenai random dan apa peran seed dibahas pada chapter [A.39. Random](A-random.html).
-Fungsi `rand.New()` berada dalam package `math/rand`, yang harus di-import terlebih dahulu sebelum bisa dimanfaatkan. Package `time` juga perlu di-import karena kita menggunakan fungsi `(time.Now().Unix())` di situ.
+Fungsi `rand.New()` berada dalam package `math/rand`. Package tersebut harus di-import terlebih dahulu sebelum bisa menggunakan fungsi-fungsi yang ada didalamnya. Package `time` juga perlu di-import karena di contoh ini fungsi `(time.Now().Unix())` digunakan.
## A.18.4. Import Banyak Package
-Penulisan keyword `import` untuk banyak package bisa dilakukan dengan dua cara, dengan menuliskannya di tiap package, atau cukup sekali saja, bebas.
+Penulisan keyword `import` untuk banyak package bisa dilakukan dengan dua cara, dengan menuliskannya di tiap package, atau cukup sekali saja, bebas silakan pilih sesuai selera.
```go
import "fmt"
@@ -137,7 +144,7 @@ func randomWithRange(min, max int) int
## A.18.6. Penggunaan Keyword `return` Untuk Menghentikan Proses Dalam Fungsi
-Selain sebagai penanda nilai balik, keyword `return` juga bisa dimanfaatkan untuk menghentikan proses dalam blok fungsi di mana ia dipakai. Contohnya bisa dilihat pada kode berikut.
+Selain sebagai penanda nilai balik, keyword `return` juga bisa dimanfaatkan untuk menghentikan proses dalam blok fungsi di mana ia ditulis. Contohnya bisa dilihat pada kode berikut.
```go
package main
@@ -161,7 +168,7 @@ func divideNumber(m, n int) {
}
```
-Fungsi `divideNumber` dirancang tidak memiliki nilai balik. Fungsi ini dibuat untuk membungkus proses pembagian 2 bilangan, lalu menampilkan hasilnya.
+Fungsi `divideNumber()` dirancang tidak memiliki nilai balik. Fungsi ini dibuat untuk membungkus proses pembagian 2 bilangan, lalu menampilkan hasilnya.
Di dalamnya terdapat proses validasi nilai variabel pembagi, jika nilainya adalah 0, maka akan ditampilkan pesan bahwa pembagian tidak bisa dilakukan, lalu proses dihentikan pada saat itu juga (dengan memanfaatkan keyword `return`). Jika nilai pembagi valid, maka proses pembagian diteruskan.
From 2e94e5a92992abb1798164d62daab3898bae4b80 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:47 +0700
Subject: [PATCH 313/522] New translations a-go-command.md (English)
---
en/content-en/A-go-command.md | 43 ++++++++++++++++++-----------------
1 file changed, 22 insertions(+), 21 deletions(-)
diff --git a/en/content-en/A-go-command.md b/en/content-en/A-go-command.md
index 237ba3599..832d29a48 100644
--- a/en/content-en/A-go-command.md
+++ b/en/content-en/A-go-command.md
@@ -1,14 +1,14 @@
# A.6. Command
-Pengembangan aplikasi Go tak jauh dari hal-hal yang berbau CLI atau *Command Line Interface*. Proses inisialisasi project, kompilasi, testing, eksekusi program, semuanya dilakukan lewat command line.
+Pengembangan aplikasi Go pastinya tak akan jauh dari hal-hal yang berbau CLI atau *Command Line Interface*. Di Go, proses inisialisasi project, kompilasi, testing, eksekusi program, semuanya dilakukan lewat command line.
Go menyediakan command `go`, dan pada chapter ini kita akan mempelajari beberapa di antaranya.
-> Pada pembelajaran chapter ini, pembaca tidak harus praktek, cukup pelajari saja untuk tahu. Mengenai praktek sendiri akan dimulai pada chapter selanjutnya, yaitu [A.7. Program Pertama: Hello World](/A-hello-world.html).
+> Pada pembelajaran chapter ini, pembaca tidak harus menghafal dan mempraktekan semuanya, cukup ikuti saja pembelajaran agar mulai familiar. Perihal prakteknya sendiri akan dimulai pada chapter selanjutnya, yaitu [A.7. Program Pertama: Hello World](/A-hello-world.html).
## A.6.1. Command `go mod init`
-*Command* `go mod init` digunakan untuk inisialisasi project pada Go (menggunakan Go Modules). Untuk nama project bisa menggunakan apapun, tapi umumnya adalah disamakan dengan nama direktori.
+*Command* `go mod init` digunakan untuk inisialisasi project pada Go yang menggunakan Go Modules. Untuk nama project bisa menggunakan apapun, tapi umumnya disamakan dengan nama direktori/folder.
Nama project ini penting karena nantinya berpengaruh pada *import path sub packages* yang ada dalam project tersebut.
@@ -20,7 +20,7 @@ go mod init
## A.6.2. Command `go run`
-*Command* `go run` digunakan untuk eksekusi file program (file ber-ekstensi `.go`). Cara penggunaannya dengan menuliskan *command* tersebut diikuti argumen nama file.
+*Command* `go run` digunakan untuk eksekusi file program, yaitu file yang ber-ekstensi `.go`. Cara penggunaannya dengan menuliskan *command* tersebut diikuti argumen nama file.
Berikut adalah contoh penerapan `go run` untuk eksekusi file program `main.go` yang tersimpan di path `project-pertama` yang path tersebut sudah diinisialisasi menggunakan `go mod init`.
@@ -31,7 +31,7 @@ go run main.go

-*Command* `go run` hanya bisa digunakan pada file yang nama package-nya adalah `main`. Lebih jelasnya dibahas pada chapter selanjutnya ([A.7. Program Pertama: Hello World](/A-hello-world.html)).
+*Command* `go run` hanya bisa digunakan pada file yang nama package-nya adalah `main`. Lebih jelasnya dibahas pada chapter selanjutnya, yaitu ([A.7. Program Pertama: Hello World](/A-hello-world.html)).
Jika ada banyak file yang package-nya `main` dan file-file tersebut berada pada satu direktori level dengan file utama, maka eksekusinya adalah dengan menuliskan semua file sebagai argument *command* `go run`. Contohnya bisa dilihat pada kode berikut.
@@ -39,11 +39,9 @@ Jika ada banyak file yang package-nya `main` dan file-file tersebut berada pada
go run main.go library.go
```
-> Lebih jelasnya perihal argument dan flag akan dibahas pada chapter [A.48. Arguments & Flag](/A-command-line-args-flag.html))
-
## A.6.3. Command `go test`
-Go menyediakan package `testing`, berguna untuk keperluan unit test. File yang akan di-test harus memiliki akhiran `_test.go`.
+Go menyediakan package `testing`, berguna untuk keperluan pembuatan file test. Pada penerapannya, ada aturan yang wajib diikuti yaitu nama file test harus berakhiran `_test.go`.
Berikut adalah contoh penggunaan *command* `go test` untuk testing file `main_test.go`.
@@ -57,50 +55,53 @@ go test main_test.go
*Command* ini digunakan untuk mengkompilasi file program.
-Sebenarnya ketika eksekusi program menggunakan `go run`, terjadi proses kompilasi juga. File hasil kompilasi akan disimpan pada folder temporary untuk selanjutnya langsung dieksekusi.
+Sebenarnya ketika eksekusi program menggunakan `go run` didalamnya terjadi proses kompilasi juga. File hasil kompilasi kemudian disimpan pada folder temporary untuk selanjutnya langsung dieksekusi.
-Berbeda dengan `go build`, *command* ini menghasilkan file *executable* atau *binary* pada folder yang sedang aktif. Contohnya bisa dilihat pada kode berikut.
+Berbeda dengan `go build`, *command* ini menghasilkan file *executable* atau *binary* pada folder yang sedang aktif. Contoh praktiknya bisa dilihat di bawah ini.

-Pada contoh di atas, project `project-pertama` di-build, menghasilkan file baru pada folder yang sama, yaitu `project-pertama.exe`, yang kemudian dieksekusi. *Default*-nya nama project akan otomatis dijadikan nama *binary*.
+Di contoh, project `project-pertama` di-build, hasilnya adalah file baru bernama `project-pertama.exe` berada di folder yang sama. File *executable* tersebut kemudian dieksekusi.
-Untuk nama executable sendiri bisa diubah menggunakan flag `-o`. Contoh:
+*Default* nama file binary atau executable adalah sesuai dengan nama project. Untuk mengubah nama file executable, gunakan flag `-o`. Contoh:
```
go build -o
go build -o program.exe
```
-> Untuk sistem operasi non-windows, tidak perlu menambahkan akhiran `.exe` pada nama *binary*
+> Khusus untuk sistem operasi non-windows, tidak perlu menambahkan akhiran `.exe` pada nama *binary*
## A.6.5. Command `go get`
-*Command* `go get` digunakan untuk men-download package. Sebagai contoh saya ingin men-download package Kafka driver untuk Go pada project `project-pertama`.
+*Command* `go get` digunakan untuk men-download package atau *dependency*. Sebagai contoh, penulis ingin men-download package Kafka driver untuk Go pada project `project-pertama`, maka command-nya kurang lebih seperti berikut:
```bash
cd project-pertama
go get github.com/segmentio/kafka-go
-dir
```

-Pada contoh di atas, `github.com/segmentio/kafka-go` adalah URL package kafka-go. Package yang sudah terunduh tersimpan dalam temporary folder yang ter-link dengan project folder di mana *command* `go get` dieksekusi, menjadikan project tersebut bisa meng-*import* package terunduh.
+Pada contoh di atas, bisa dilihat bahwa URL `github.com/segmentio/kafka-go` merupakan URL package kafka-go. Package yang sudah terunduh tersimpan dalam temporary folder yang ter-link dengan project folder di mana *command* `go get` dieksekusi, menjadikan project tersebut bisa meng-*import* package yang telah di-download.
-Untuk mengunduh dependensi versi terbaru, gunakan flag `-u` pada command `go get`, misalnya:
+Untuk mengunduh package/dependency versi terbaru, gunakan flag `-u` pada command `go get`, contohnya:
```
go get -u github.com/segmentio/kafka-go
```
-Command `go get` **harus dijalankan dalam folder project**. Jika dijalankan di-luar project maka akan diunduh ke pada GOPATH.
+Command `go get` **harus dijalankan dalam folder project**. Jika dijalankan di-luar path project maka dependency yang ter-unduh akan ter-link dengan GOPATH, bukan dengan project.
+
+## A.6.6. Command `go mod download`
+
+*Command* `go mod download` digunakan untuk men-download dependency.
-## A.6.6. Command `go mod tidy`
+## A.6.7. Command `go mod tidy`
-*Command* `go mod tidy` digunakan untuk memvalidasi dependensi. Jika ada dependensi yang belum ter-download, maka akan otomatis di-download.
+*Command* `go mod tidy` digunakan untuk memvalidasi dependency sekaligus men-download-nya jika memang belum ter-download.
-## A.6.7. Command `go mod vendor`
+## A.6.8. Command `go mod vendor`
Command ini digunakan untuk vendoring. Lebih detailnya akan dibahas di akhir serial chapter A, pada chapter [A.61. Go Vendoring](/A-go-vendoring.html).
From ef211bf091e13db29ad5ddadbc4c7814f9f6133d Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:48 +0700
Subject: [PATCH 314/522] New translations a-go-vendoring.md (English)
---
en/content-en/A-go-vendoring.md | 25 ++++++++-----------------
1 file changed, 8 insertions(+), 17 deletions(-)
diff --git a/en/content-en/A-go-vendoring.md b/en/content-en/A-go-vendoring.md
index c700a0900..1da86c41c 100644
--- a/en/content-en/A-go-vendoring.md
+++ b/en/content-en/A-go-vendoring.md
@@ -1,14 +1,14 @@
# A.61. Go Vendoring
-Pada bagian ini kita akan belajar cara pemanfaatan vendoring untuk menyimpan dependensi di lokal.
+Pada bagian ini kita akan belajar cara pemanfaatan vendoring untuk menyimpan copy dependency di lokal dalam folder project.
## A.61.1. Penjelasan
-Vendoring di Go merupakan kapabilitas untuk mengunduh semua dependency atau *3rd party*, untuk disimpan di lokal dalam folder project, dalam folder bernama `vendor`.
+Vendoring di Go memberikan kita kapabilitas untuk mengunduh semua dependency atau *3rd party*, untuk disimpan di lokal dalam folder project, dalam subfolder bernama `vendor`.
-Dengan adanya folder tersebut, maka Go tidak akan *lookup* 3rd party ke cache folder, melainkan langsung mempergunakan yang ada dalam folder `vendor`. Jadi tidak perlu download lagi dari internet.
+Dengan adanya folder tersebut, maka Go tidak akan *lookup* 3rd party ke cache folder ataupun ke GOPATH, melainkan langsung mengambil dari yang ada dalam folder `vendor`. Jadi kalau dependency sudah ada di dalam `vendor`, maka kita tidak perlu download lagi dari internet menggunakan command `go mod download` ataupun `go mod tidy`.
-Ok lanjut.
+Ok lanjut ke praktek ya.
## A.61.2. Praktek Vendoring
@@ -38,7 +38,7 @@ func main() {
}
```
-Setelah itu jalankan command `go mod vendor` untuk vendoring *3rd party library* yang dipergunakan, dalam contoh ini adlah gubrak.
+Setelah itu jalankan command `go mod vendor` untuk vendoring *3rd party library* yang dipergunakan, dalam contoh ini adalah gubrak.

@@ -46,14 +46,7 @@ Bisa dilihat, sekarang library gubrak *source code*-nya disimpan dalam folder `v
## A.61.3 Build dan Run Project yang Menerapkan Vendoring
-Untuk membuat proses build lookup ke folder vendor, kita tidak perlu melakukan apa-apa, setidaknya jika versi Go yang diinstall adalah 1.14 ke atas. Maka command build maupun run masih sama.
-
-```
-go run main.go
-go build -o executable
-```
-
-Untuk yg menggunakan versi Go di bawah 1.14, penulis sarankan untuk upgrade. Atau bisa gunakan flag `-mod=vendor` untuk memaksa Go lookup ke folder `vendor`.
+Cara agar Go lookup ke folder `vendor` saat build adalah dengan menambahkan flag `-mod=vendor` sewaktu build atau run project.
```
go run -mod=vendor main.go
@@ -62,11 +55,9 @@ go build -mod=vendor -o executable
## A.61.3. Manfaat Vendoring
-Manfaat vendoring adalah pada sisi kompatibilitas dan kestabilan 3rd party. Jadi dengan vendor, misal 3rd party yang kita gunakan di itu ada update yg sifatnya tidak *backward compatible*, maka aplikasi kita tetap aman karena menggunakan yang ada dalam folder `vendor`.
-
-Jika tidak menggunakan vendoring, maka bisa saja saat `go mod tidy` sukses, namun sewaktu build error, karena ada fungsi yg tidak kompatibel lagi misalnya.
+Manfaat vendoring adalah pada sisi kompatibilitas & kestabilan 3rd party, selain itu kita tidak perlu repot mendownload dependency karena semuanya sudah ada di lokal.
-Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan saja.
+Konsekuensi penerapan vendoring adalah size project menjadi cukup besar. Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan saja.
---
From 62d810abdb170a390d7481c4791ce05c9172e80f Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo
Date: Thu, 25 Apr 2024 19:11:49 +0700
Subject: [PATCH 315/522] New translations a-golang-generics.md (English)
---
en/content-en/A-golang-generics.md | 54 ++++++++++++++++--------------
1 file changed, 28 insertions(+), 26 deletions(-)
diff --git a/en/content-en/A-golang-generics.md b/en/content-en/A-golang-generics.md
index a25e239e0..72c2b3f29 100644
--- a/en/content-en/A-golang-generics.md
+++ b/en/content-en/A-golang-generics.md
@@ -1,20 +1,20 @@
# A.65. Go Generics
-Pada chapter ini kita akan belajar tentang Generics di Go.
+Pada chapter ini kita akan belajar tentang penerapan Generics di Go.
## A.65.1. Konsep Generic Programming
-Generic Programming adalah salah satu metode dalam penulisan kode program, di mana tipe data dalam kode didefinisikan menggunakan tipe data yang tipe pastinya adalah dituliskan belakangan saat kode tersebut di-call atau dieksekusi. Konsep ini sudah cukup umum terutama pada bahasa yang static type.
+Generic Programming adalah salah satu metode dalam penulisan kode program, di mana tipe data dalam kode didefinisikan menggunakan suatu tipe yang tipe pastinya ditulis belakangan saat kode tersebut di-call atau dieksekusi. Konsep generic ini cukup umum diterapkan terutama pada bahasa pemrograman yang mengadopsi static typing.
-Di Go, kita punya tipe `interface{}` yang biasa difungsikan sebagai tipe untuk menampung data yang tidak pasti tipe datanya. Generic dan `interface{}` berbeda. Tipe `interface{}` akan membungkus data aslinya atau *underlying value*-nya, dan untuk mengakses data tersebut, kita perlu menerapkan *type assertion*, contohnya `data.(int)`.
+Di Go, kita punya tipe `any` atau `interface{}` yang biasa difungsikan sebagai penampung data yang tidak pasti tipe datanya. Generic berbeda dibanding `any`. Tipe `any` dalam prakteknya membungkus data asli atau *underlying value*-nya, dengan pengaksesan data asli tersebut dilakukan via metode *type assertion*, contohnya `data.(int)`.
-Berbeda dibanding `interface{}`, pada penggunaan generic kita perlu mendefinisikan cakupan tipe data yang kompatibel untuk dipakai saat pemanggilan kode, atau bisa juga menggunakan keyword `comparable`, yang artinya tipe data adalah kompatibel dengan tipe apapun.
+Berbeda dibanding `any`, pada Generic kita perlu mendefinisikan cakupan tipe data yang kompatibel untuk digunakan saat pemanggilan kode.
-Ok, mari kita lanjut ke pembahasan yang lebih teknis agar tidak bingung.
+Ok, mari kita lanjut ke praktek saja agar tidak makin bingung.
## A.65.2. Penerapan Generic pada Fungsi
-Mari kita mulai pembelajaran dengan kode di bawah ini:
+Mari kita mulai pembelajaran dengan kode sederhana berikut:
```go
package main
@@ -37,9 +37,11 @@ func main() {
Pada kode di atas, didefinisikan sebuah fungsi `Sum()` yang tugasnya menghitung total atau *summary* dari data slice numerik yang disisipkan di parameter. Dalam `main()`, kita panggil fungsi tersebut untuk menghitung total dari sejumlah data dengan tipe `[]int`. Saya rasa sampai sini cukup jelas.
-Fungsi `Sum()` memiliki satu limitasinya, yaitu hanya bisa digunakan pada data yang tipenya `[]int`, tidak bisa untuk tipe slice numerik lain. Bagaimana jika menggunakan tipe `interface{}`? apakah bisa? bisa saja sebenarnya, tapi pastinya lebih report karena sulit untuk menerapkan *type assertion* kalau tidak tau tipe pasti parameter `numbers` itu apa. Penggunaan `interface{}` perlu dibarengi dengan penerapan [reflection API](/A-reflect.html).
+Fungsi `Sum()` memiliki satu limitasinya, yaitu hanya bisa digunakan pada data yang tipenya `[]int`, tidak bisa untuk tipe slice numerik lain. Bagaimana jika menggunakan tipe `interface{}`? apakah bisa? bisa saja sebenarnya, tapi pastinya lebih report karena sulit untuk menerapkan *type assertion* kalau tidak tau pasti cakupan tipe yang di-support oleh parameter `numbers` itu apa saja.
-Di sini kita bisa terapkan Generic, kita akan modifikasi fungsi di atas agar bisa menampung tipe data slice numerik lainnya diluar `[]int`.
+> Alternatifnya, penggunaan `interface{}` bisa dibarengi dengan penerapan [reflection API](/A-reflect.html).
+
+Nah, agar tidak repot, di sini kita akan terapkan Generic. Kode akan dimodifikasi atas agar bisa menampung tipe data slice numerik lainnya diluar tipe `[]int`.
Ok, sekarang ubah kode fungsi `Sum` menjadi seperti di bawah ini:
@@ -93,11 +95,13 @@ func main() {
}
```
+Output program:
+

## A.65.3. Comparable Data Type pada Fungsi Generic
-Selanjutnya kita modifikasi lagi fungsi `Sum` agar tipe kompatibel `V` di sini kompatibel dengan tipe numerik lainnya seperti `float64`. Caranya sangat mudah, cukup tambahkan tipe datanya pada statement `V int` dengan delimiter pipe (`|`).
+Selanjutnya, modifikasi lagi fungsi `Sum` agar tipe kompatibel `V` di sini bisa kompatibel dengan tipe numerik lainnya seperti `float64`. Caranya sangat mudah, cukup tambahkan tipe data yang diinginkan untuk kompatibel pada statement `V int` menggunakan delimiter pipe (`|`).
```go
func Sum[V int | float32 | float64](numbers []V) V {
@@ -126,7 +130,7 @@ fmt.Println("total:", total3)

-Nice, hasilnya sesuai harapan. Sampai sini kita sudah paham bagaimana cara pendefinisian tipe kompatibel pada fungsi dan cara pemanfaatannya.
+Jos gandos, hasilnya sesuai harapan. Sampai sini kita sudah paham bagaimana cara pendefinisian tipe kompatibel pada fungsi dan cara pemanfaatannya.
## A.65.4. Tipe Argumen Saat Pemanggilan Fungsi Generic
@@ -146,15 +150,15 @@ Sum[float32]([]float32{2.5, 7.2})
Sum[float64]([]float64{1.23, 6.33, 12.6})
```
-Di case ini (dan banyak case lainnya), tipe data kompatibel tidak perlu dituliskan secara eksplisit karena secara cerdas kompiler bisa mendeteksi tipe yang kompatibel berdasarkan tipe data parameter saat pemanggilan fungsi.
+Di case ini (dan banyak case lainnya), tipe data yang sudah kompatibel tidak perlu dituliskan secara eksplisit karena kompiler secara cerdas bisa mendeteksi tipe yang kompatibel berdasarkan tipe data parameter saat pemanggilan fungsi.
## A.65.5. Keyword `comparable`
-Sekarang kita akan belajar kegunaan satu keyword penting, yaitu `comparable`. Keyword tersebut merupakan tipe data yang kompatibel dengan semua tipe yang ada.
+Sekarang kita akan belajar kegunaan satu keyword penting lainnya, yaitu `comparable`. Keyword ini merepresentasikan semua tipe data yang kompatibel.
Pada kode di atas kita menggunakan `V int | float32 | float64` untuk mendefinisikan tipe yang kompatibel dengan tipe `int`, `float32`, dan `float64`. Jika ingin membuat tipe `V` kompatibel dengan banyak tipe lainnya, tambahkan saja tipe2 yang diinginkan. Atau, jika ingin kompatibel dengan **semua tipe data** maka gunakan `comparable`, penulisannya menjadi `V comparable`.
-Ok, mari kita coba terapkan. O iya, sebelum mulai, agar pembaca makin paham perihal fungsi generic, kita siapkan 2 fungsi yang mirip berikut:
+Ok, mari kita coba terapkan. Kita tidak akan menerapkan `comparable` pada contoh di atas karena fungsi `Sum()` kita desain untuk komputasi nilai numerik. Jika `comparable` diterapkan disitu jadinya kurang pas. Oleh karena itu kita siapkan 2 fungsi baru yang mirip berikut sebagai bahan praktek selanjutnya.
```go
func SumNumbers1(m map[string]int64) int64 {
@@ -183,16 +187,16 @@ func main() {
}
```
-Dua fungsi di atas mirip, tapi memiliki beberapa perbedaan:
+Dua fungsi di atas mirip, tapi memiliki beberapa perbedaan yaitu:
-1. Penulisan `SumNumbers1` adalah non-generic, sedangkan `SumNumbers2` adalah generic.
-2. Pada `SumNumbers1`, kita menggunakan kombinasi dua tipe data untuk membentuk `map`, yaitu `string` sebagai map key dan `int64` sebagai map value.
-3. Pada `SumNumbers2`, kita breakdown pendefinisian tipe data map menjadi lebih mendetail:
+1. Penulisan `SumNumbers1()` adalah non-generic, sedangkan `SumNumbers2()` adalah generic.
+2. Pada `SumNumbers1()`, kita menggunakan kombinasi dua tipe data untuk membentuk `map`, yaitu `string` sebagai map key dan `int64` sebagai map value.
+3. Pada `SumNumbers2()`, kita breakdown pendefinisian tipe data map menjadi lebih mendetail:
- Tipe map key adalah `K` yang tipe datanya kompatibel dengan semua tipe data.
- Tipe map value adalah `V` yang tipe datanya kompatibel dengan `int64` dan `float64`.
- Yang sebelumnya `map[string]int64` kini menjadi `map[K]V`.
-Karena `SumNumbers2` menggunakan generic, maka fungsi ini mendukung sangat banyak tipe data karena menggunakan kombinasi dari tipe `K` yang kompatibel dengan semua tipe; dan tipe `V` yang kompatibel dengan `int64` dan `float64`.
+Karena `SumNumbers2()` menggunakan generic, maka fungsi ini mendukung sangat banyak tipe data karena menggunakan kombinasi dari tipe `K` yang kompatibel dengan semua tipe; dan tipe `V` yang kompatibel dengan `int64` dan `float64`.
- `map[string]int64`
- `map[interface{}]int64`
@@ -206,7 +210,7 @@ Jalankan kode, lihat hasilnya.
## A.65.6. Generic *Type Constraint*
-Selanjutnya buat fungsi `SumNumbers3`, isinya kurang lebih sama, hanya saja pada tipe data generic kita tidak menggunakan `V int64 | float64`, yang digunakan adalah `Number` yang merupakan tipe data baru (generic *type constraint*).
+Selanjutnya buat fungsi `SumNumbers3()` yang isinya kurang adalah lebih sama. Kali ini kita tidak menggunakan `V int64 | float64`, melainkan menggunakan tipe `Number` yang merupakan tipe data baru yang akan kita buat juga (generic *type constraint*).
```go
type Number interface {
@@ -222,15 +226,15 @@ func SumNumbers3[K comparable, V Number](m map[K]V) V {
}
```
-Cara pendefinisian generic *type constraint* adalah seperti pendefinisan tipe data kustom menggunakan keyword `type`, bedanya adalah di sini `interface{}` dipergunakan sebagai tipe, dan di dalamnya di-embed 2 tipe yang diinginkan untuk menjadi *comparable type*, yaitu `int64` dan `float64`. Dari sini, selanjutnya tipe `Number` bisa dimanfaatkan sebagai tipe data kompatibel dalam generic.
+Cara pendefinisian generic *type constraint* adalah seperti pendefinisan tipe data kustom menggunakan keyword `type`, bedanya adalah di sini `interface{}` dipergunakan sebagai tipe, yang di dalamnya di-embed 2 tipe yang diinginkan untuk menjadi *comparable type*, yaitu `int64` dan `float64`. Hasilnya, tipe `Number` bisa dimanfaatkan dalam penerapan generic sebagai tipe data yang kompatibel.
> Perlu diketahui, tipe yang didefinisikan menggunakan *type constraint* ini hanya bisa dimanfaatkan pada generic. Tipe jenis ini tidak bisa digunakan di luar scope kode generic. Sebagai contoh, coba deklarasikan `var s Number` dalam fungsi `main()`, hasilnya akan muncul syntax error.
-Ok, sekarang mari ubah pemanggilan fungsi `SumNumbers2` pada main menjadi `SumNumbers3` dan lihat hasilnya, jalan.
+Ok, sekarang ubah pemanggilan fungsi `SumNumbers2()` pada main menjadi `SumNumbers3()` lalu coba jalankan dan lihat hasilnya, pasti outputnya sama, menandakan bahwa kode program berjalan sesuai desain.
## A.65.7. Struct Generic
-Generic juga bisa diterapkan pada pendefinisian struct, contohnya seperti berikut:
+Generic juga bisa diterapkan pada struct, contohnya:
```go
type UserModel[T int | float64] struct {
@@ -259,7 +263,7 @@ func main() {
}
```
-Pada penulisan struct, sisipkan notasi generic. Lalu pada deklarasi variabel object, tulis secara eksplisit tipe data untuk variabel kompatibel.
+Cukup tuliskan notasi generic pada deklarasi struct. Kemudian siapkan variabel object, tulis secara eksplisit tipe data untuk variabel kompatibel.

@@ -267,9 +271,7 @@ Pada penulisan struct, sisipkan notasi generic. Lalu pada deklarasi variabel obj
Sampai artikel ini ditulis, generic tidak bisa diterapkan pada method (meski bisa diterapkan pada fungsi)
----
-
-Ok, sekian pembahasan mengenai generics. Jika ada update perihal generic API akan penulis update ke chapter ini juga.
+> Penulis akan update konten chapter ini jika ada update pada spesifikasi generic API.
---
From fca1480fe943d378c5ac31b052bc39d1f7b79bc8 Mon Sep 17 00:00:00 2001
From: Noval Agung Prayogo